Adding C++ API and implementation for STS credentials:

- marked as experimental.
- also changed the name of a field in the options struct.
pull/19587/head
Julien Boeuf 5 years ago
parent 0da6fcfa05
commit 109edca971
  1. 75
      CMakeLists.txt
  2. 80
      Makefile
  3. 21
      build.yaml
  4. 26
      include/grpc/grpc_security.h
  5. 18
      include/grpcpp/security/credentials.h
  6. 64
      include/grpcpp/security/credentials_impl.h
  7. 5
      src/core/lib/security/credentials/oauth2/oauth2_credentials.cc
  8. 148
      src/cpp/client/secure_credentials.cc
  9. 11
      src/cpp/client/secure_credentials.h
  10. 1
      test/core/security/BUILD
  11. 72
      test/core/security/fetch_oauth2.cc
  12. 157
      test/cpp/client/credentials_test.cc
  13. 33
      tools/run_tests/generated/sources_and_headers.json

@ -332,7 +332,6 @@ add_dependencies(buildtests_c grpc_channel_stack_test)
add_dependencies(buildtests_c grpc_completion_queue_test)
add_dependencies(buildtests_c grpc_completion_queue_threading_test)
add_dependencies(buildtests_c grpc_credentials_test)
add_dependencies(buildtests_c grpc_fetch_oauth2)
add_dependencies(buildtests_c grpc_ipv6_loopback_available_test)
if(_gRPC_PLATFORM_LINUX OR _gRPC_PLATFORM_MAC OR _gRPC_PLATFORM_POSIX)
add_dependencies(buildtests_c grpc_json_token_test)
@ -634,6 +633,7 @@ add_dependencies(buildtests_cxx golden_file_test)
add_dependencies(buildtests_cxx grpc_alts_credentials_options_test)
add_dependencies(buildtests_cxx grpc_cli)
add_dependencies(buildtests_cxx grpc_core_map_test)
add_dependencies(buildtests_cxx grpc_fetch_oauth2)
add_dependencies(buildtests_cxx grpc_linux_system_roots_test)
add_dependencies(buildtests_cxx grpc_tool_test)
add_dependencies(buildtests_cxx grpclb_api_test)
@ -8270,40 +8270,6 @@ target_link_libraries(grpc_credentials_test
endif (gRPC_BUILD_TESTS)
if (gRPC_BUILD_TESTS)
add_executable(grpc_fetch_oauth2
test/core/security/fetch_oauth2.cc
)
target_include_directories(grpc_fetch_oauth2
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
PRIVATE ${_gRPC_SSL_INCLUDE_DIR}
PRIVATE ${_gRPC_PROTOBUF_INCLUDE_DIR}
PRIVATE ${_gRPC_ZLIB_INCLUDE_DIR}
PRIVATE ${_gRPC_BENCHMARK_INCLUDE_DIR}
PRIVATE ${_gRPC_CARES_INCLUDE_DIR}
PRIVATE ${_gRPC_GFLAGS_INCLUDE_DIR}
PRIVATE ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
PRIVATE ${_gRPC_NANOPB_INCLUDE_DIR}
)
target_link_libraries(grpc_fetch_oauth2
${_gRPC_ALLTARGETS_LIBRARIES}
grpc_test_util
grpc
gpr
)
# avoid dependency on libstdc++
if (_gRPC_CORE_NOSTDCXX_FLAGS)
set_target_properties(grpc_fetch_oauth2 PROPERTIES LINKER_LANGUAGE C)
target_compile_options(grpc_fetch_oauth2 PRIVATE $<$<COMPILE_LANGUAGE:CXX>:${_gRPC_CORE_NOSTDCXX_FLAGS}>)
endif()
endif (gRPC_BUILD_TESTS)
if (gRPC_BUILD_TESTS)
add_executable(grpc_ipv6_loopback_available_test
test/core/iomgr/grpc_ipv6_loopback_available_test.cc
)
@ -14080,6 +14046,45 @@ endif()
endif (gRPC_BUILD_CODEGEN)
if (gRPC_BUILD_TESTS)
add_executable(grpc_fetch_oauth2
test/core/security/fetch_oauth2.cc
third_party/googletest/googletest/src/gtest-all.cc
third_party/googletest/googlemock/src/gmock-all.cc
)
target_include_directories(grpc_fetch_oauth2
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
PRIVATE ${_gRPC_SSL_INCLUDE_DIR}
PRIVATE ${_gRPC_PROTOBUF_INCLUDE_DIR}
PRIVATE ${_gRPC_ZLIB_INCLUDE_DIR}
PRIVATE ${_gRPC_BENCHMARK_INCLUDE_DIR}
PRIVATE ${_gRPC_CARES_INCLUDE_DIR}
PRIVATE ${_gRPC_GFLAGS_INCLUDE_DIR}
PRIVATE ${_gRPC_ADDRESS_SORTING_INCLUDE_DIR}
PRIVATE ${_gRPC_NANOPB_INCLUDE_DIR}
PRIVATE third_party/googletest/googletest/include
PRIVATE third_party/googletest/googletest
PRIVATE third_party/googletest/googlemock/include
PRIVATE third_party/googletest/googlemock
PRIVATE ${_gRPC_PROTO_GENS_DIR}
)
target_link_libraries(grpc_fetch_oauth2
${_gRPC_PROTOBUF_LIBRARIES}
${_gRPC_ALLTARGETS_LIBRARIES}
grpc_test_util
grpc++
grpc
gpr
${_gRPC_GFLAGS_LIBRARIES}
)
endif (gRPC_BUILD_TESTS)
if (gRPC_BUILD_TESTS)
add_executable(grpc_linux_system_roots_test
test/core/security/linux_system_roots_test.cc
third_party/googletest/googletest/src/gtest-all.cc

@ -1056,7 +1056,6 @@ grpc_completion_queue_test: $(BINDIR)/$(CONFIG)/grpc_completion_queue_test
grpc_completion_queue_threading_test: $(BINDIR)/$(CONFIG)/grpc_completion_queue_threading_test
grpc_create_jwt: $(BINDIR)/$(CONFIG)/grpc_create_jwt
grpc_credentials_test: $(BINDIR)/$(CONFIG)/grpc_credentials_test
grpc_fetch_oauth2: $(BINDIR)/$(CONFIG)/grpc_fetch_oauth2
grpc_ipv6_loopback_available_test: $(BINDIR)/$(CONFIG)/grpc_ipv6_loopback_available_test
grpc_json_token_test: $(BINDIR)/$(CONFIG)/grpc_json_token_test
grpc_jwt_verifier_test: $(BINDIR)/$(CONFIG)/grpc_jwt_verifier_test
@ -1219,6 +1218,7 @@ grpc_cli: $(BINDIR)/$(CONFIG)/grpc_cli
grpc_core_map_test: $(BINDIR)/$(CONFIG)/grpc_core_map_test
grpc_cpp_plugin: $(BINDIR)/$(CONFIG)/grpc_cpp_plugin
grpc_csharp_plugin: $(BINDIR)/$(CONFIG)/grpc_csharp_plugin
grpc_fetch_oauth2: $(BINDIR)/$(CONFIG)/grpc_fetch_oauth2
grpc_linux_system_roots_test: $(BINDIR)/$(CONFIG)/grpc_linux_system_roots_test
grpc_node_plugin: $(BINDIR)/$(CONFIG)/grpc_node_plugin
grpc_objective_c_plugin: $(BINDIR)/$(CONFIG)/grpc_objective_c_plugin
@ -1489,7 +1489,6 @@ buildtests_c: privatelibs_c \
$(BINDIR)/$(CONFIG)/grpc_completion_queue_test \
$(BINDIR)/$(CONFIG)/grpc_completion_queue_threading_test \
$(BINDIR)/$(CONFIG)/grpc_credentials_test \
$(BINDIR)/$(CONFIG)/grpc_fetch_oauth2 \
$(BINDIR)/$(CONFIG)/grpc_ipv6_loopback_available_test \
$(BINDIR)/$(CONFIG)/grpc_json_token_test \
$(BINDIR)/$(CONFIG)/grpc_jwt_verifier_test \
@ -1693,6 +1692,7 @@ buildtests_cxx: privatelibs_cxx \
$(BINDIR)/$(CONFIG)/grpc_alts_credentials_options_test \
$(BINDIR)/$(CONFIG)/grpc_cli \
$(BINDIR)/$(CONFIG)/grpc_core_map_test \
$(BINDIR)/$(CONFIG)/grpc_fetch_oauth2 \
$(BINDIR)/$(CONFIG)/grpc_linux_system_roots_test \
$(BINDIR)/$(CONFIG)/grpc_tool_test \
$(BINDIR)/$(CONFIG)/grpclb_api_test \
@ -1857,6 +1857,7 @@ buildtests_cxx: privatelibs_cxx \
$(BINDIR)/$(CONFIG)/grpc_alts_credentials_options_test \
$(BINDIR)/$(CONFIG)/grpc_cli \
$(BINDIR)/$(CONFIG)/grpc_core_map_test \
$(BINDIR)/$(CONFIG)/grpc_fetch_oauth2 \
$(BINDIR)/$(CONFIG)/grpc_linux_system_roots_test \
$(BINDIR)/$(CONFIG)/grpc_tool_test \
$(BINDIR)/$(CONFIG)/grpclb_api_test \
@ -10987,38 +10988,6 @@ endif
endif
GRPC_FETCH_OAUTH2_SRC = \
test/core/security/fetch_oauth2.cc \
GRPC_FETCH_OAUTH2_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(GRPC_FETCH_OAUTH2_SRC))))
ifeq ($(NO_SECURE),true)
# You can't build secure targets if you don't have OpenSSL.
$(BINDIR)/$(CONFIG)/grpc_fetch_oauth2: openssl_dep_error
else
$(BINDIR)/$(CONFIG)/grpc_fetch_oauth2: $(GRPC_FETCH_OAUTH2_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
$(E) "[LD] Linking $@"
$(Q) mkdir -p `dirname $@`
$(Q) $(LD) $(LDFLAGS) $(GRPC_FETCH_OAUTH2_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBS) $(LDLIBS_SECURE) -o $(BINDIR)/$(CONFIG)/grpc_fetch_oauth2
endif
$(OBJDIR)/$(CONFIG)/test/core/security/fetch_oauth2.o: $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
deps_grpc_fetch_oauth2: $(GRPC_FETCH_OAUTH2_OBJS:.o=.dep)
ifneq ($(NO_SECURE),true)
ifneq ($(NO_DEPS),true)
-include $(GRPC_FETCH_OAUTH2_OBJS:.o=.dep)
endif
endif
GRPC_IPV6_LOOPBACK_AVAILABLE_TEST_SRC = \
test/core/iomgr/grpc_ipv6_loopback_available_test.cc \
@ -17134,6 +17103,49 @@ ifneq ($(NO_DEPS),true)
endif
GRPC_FETCH_OAUTH2_SRC = \
test/core/security/fetch_oauth2.cc \
GRPC_FETCH_OAUTH2_OBJS = $(addprefix $(OBJDIR)/$(CONFIG)/, $(addsuffix .o, $(basename $(GRPC_FETCH_OAUTH2_SRC))))
ifeq ($(NO_SECURE),true)
# You can't build secure targets if you don't have OpenSSL.
$(BINDIR)/$(CONFIG)/grpc_fetch_oauth2: openssl_dep_error
else
ifeq ($(NO_PROTOBUF),true)
# You can't build the protoc plugins or protobuf-enabled targets if you don't have protobuf 3.5.0+.
$(BINDIR)/$(CONFIG)/grpc_fetch_oauth2: protobuf_dep_error
else
$(BINDIR)/$(CONFIG)/grpc_fetch_oauth2: $(PROTOBUF_DEP) $(GRPC_FETCH_OAUTH2_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
$(E) "[LD] Linking $@"
$(Q) mkdir -p `dirname $@`
$(Q) $(LDXX) $(LDFLAGS) $(GRPC_FETCH_OAUTH2_OBJS) $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a $(LDLIBSXX) $(LDLIBS_PROTOBUF) $(LDLIBS) $(LDLIBS_SECURE) $(GTEST_LIB) -o $(BINDIR)/$(CONFIG)/grpc_fetch_oauth2
endif
endif
$(OBJDIR)/$(CONFIG)/test/core/security/fetch_oauth2.o: $(LIBDIR)/$(CONFIG)/libgrpc_test_util.a $(LIBDIR)/$(CONFIG)/libgrpc++.a $(LIBDIR)/$(CONFIG)/libgrpc.a $(LIBDIR)/$(CONFIG)/libgpr.a
deps_grpc_fetch_oauth2: $(GRPC_FETCH_OAUTH2_OBJS:.o=.dep)
ifneq ($(NO_SECURE),true)
ifneq ($(NO_DEPS),true)
-include $(GRPC_FETCH_OAUTH2_OBJS:.o=.dep)
endif
endif
GRPC_LINUX_SYSTEM_ROOTS_TEST_SRC = \
test/core/security/linux_system_roots_test.cc \

@ -2863,16 +2863,6 @@ targets:
- grpc_test_util
- grpc
- gpr
- name: grpc_fetch_oauth2
build: test
run: false
language: c
src:
- test/core/security/fetch_oauth2.cc
deps:
- grpc_test_util
- grpc
- gpr
- name: grpc_ipv6_loopback_available_test
build: test
language: c
@ -4945,6 +4935,17 @@ targets:
deps:
- grpc_plugin_support
secure: false
- name: grpc_fetch_oauth2
build: test
run: false
language: c++
src:
- test/core/security/fetch_oauth2.cc
deps:
- grpc_test_util
- grpc++
- grpc
- gpr
- name: grpc_linux_system_roots_test
gtest: true
build: test

@ -330,20 +330,20 @@ GRPCAPI grpc_call_credentials* grpc_google_iam_credentials_create(
/** Options for creating STS Oauth Token Exchange credentials following the IETF
draft https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-16.
Optional fields may be set to NULL. It is the responsibility of the caller to
ensure that the subject and actor tokens are refreshed on disk at the
specified paths. This API is used for experimental purposes for now and may
change in the future. */
Optional fields may be set to NULL or empty string. It is the responsibility
of the caller to ensure that the subject and actor tokens are refreshed on
disk at the specified paths. This API is used for experimental purposes for
now and may change in the future. */
typedef struct {
const char* sts_endpoint_url; /* Required. */
const char* resource; /* Optional. */
const char* audience; /* Optional. */
const char* scope; /* Optional. */
const char* requested_token_type; /* Optional. */
const char* subject_token_path; /* Required. */
const char* subject_token_type; /* Required. */
const char* actor_token_path; /* Optional. */
const char* actor_token_type; /* Optional. */
const char* token_exchange_service_uri; /* Required. */
const char* resource; /* Optional. */
const char* audience; /* Optional. */
const char* scope; /* Optional. */
const char* requested_token_type; /* Optional. */
const char* subject_token_path; /* Required. */
const char* subject_token_type; /* Required. */
const char* actor_token_path; /* Optional. */
const char* actor_token_type; /* Optional. */
} grpc_sts_credentials_options;
/** Creates an STS credentials following the STS Token Exchanged specifed in the

@ -106,6 +106,24 @@ MetadataCredentialsFromPlugin(
namespace experimental {
typedef ::grpc_impl::experimental::StsCredentialsOptions StsCredentialsOptions;
static inline grpc::Status StsCredentialsOptionsFromJson(
const grpc::string& json_string, StsCredentialsOptions* options) {
return ::grpc_impl::experimental::StsCredentialsOptionsFromJson(json_string,
options);
}
static inline grpc::Status StsCredentialsOptionsFromEnv(
StsCredentialsOptions* options) {
return grpc_impl::experimental::StsCredentialsOptionsFromEnv(options);
}
static inline std::shared_ptr<grpc_impl::CallCredentials> StsCredentials(
const StsCredentialsOptions& options) {
return grpc_impl::experimental::StsCredentials(options);
}
typedef ::grpc_impl::experimental::AltsCredentialsOptions
AltsCredentialsOptions;

@ -259,6 +259,70 @@ std::shared_ptr<CallCredentials> MetadataCredentialsFromPlugin(
namespace experimental {
/// Options for creating STS Oauth Token Exchange credentials following the IETF
/// draft https://tools.ietf.org/html/draft-ietf-oauth-token-exchange-16.
/// Optional fields may be set to empty string. It is the responsibility of the
/// caller to ensure that the subject and actor tokens are refreshed on disk at
/// the specified paths.
struct StsCredentialsOptions {
grpc::string token_exchange_service_uri; // Required.
grpc::string resource; // Optional.
grpc::string audience; // Optional.
grpc::string scope; // Optional.
grpc::string requested_token_type; // Optional.
grpc::string subject_token_path; // Required.
grpc::string subject_token_type; // Required.
grpc::string actor_token_path; // Optional.
grpc::string actor_token_type; // Optional.
};
/// Creates STS Options from a JSON string. The JSON schema is as follows:
/// {
/// "title": "STS Credentials Config",
/// "type": "object",
/// "required": ["token_exchange_service_uri", "subject_token_path",
/// "subject_token_type"],
/// "properties": {
/// "token_exchange_service_uri": {
/// "type": "string"
/// },
/// "resource": {
/// "type": "string"
/// },
/// "audience": {
/// "type": "string"
/// },
/// "scope": {
/// "type": "string"
/// },
/// "requested_token_type": {
/// "type": "string"
/// },
/// "subject_token_path": {
/// "type": "string"
/// },
/// "subject_token_type": {
/// "type": "string"
/// },
/// "actor_token_path" : {
/// "type": "string"
/// },
/// "actor_token_type": {
/// "type": "string"
/// }
/// }
/// }
grpc::Status StsCredentialsOptionsFromJson(const grpc::string& json_string,
StsCredentialsOptions* options);
/// Creates STS credentials options from the $STS_CREDENTIALS environment
/// variable. This environment variable points to the path of a JSON file
/// comforming to the schema described above.
grpc::Status StsCredentialsOptionsFromEnv(StsCredentialsOptions* options);
std::shared_ptr<CallCredentials> StsCredentials(
const StsCredentialsOptions& options);
/// Options used to build AltsCredentials.
struct AltsCredentialsOptions {
/// service accounts of target endpoint that will be acceptable

@ -18,6 +18,7 @@
#include <grpc/support/port_platform.h>
#include "src/core/lib/json/json.h"
#include "src/core/lib/security/credentials/oauth2/oauth2_credentials.h"
#include <string.h>
@ -641,8 +642,8 @@ grpc_error* ValidateStsCredentialsOptions(
*sts_url_out = nullptr;
InlinedVector<grpc_error*, 3> error_list;
UniquePtr<grpc_uri, GrpcUriDeleter> sts_url(
options->sts_endpoint_url != nullptr
? grpc_uri_parse(options->sts_endpoint_url, false)
options->token_exchange_service_uri != nullptr
? grpc_uri_parse(options->token_exchange_service_uri, false)
: nullptr);
if (sts_url == nullptr) {
error_list.push_back(GRPC_ERROR_CREATE_FROM_STATIC_STRING(

@ -17,13 +17,23 @@
*/
#include "src/cpp/client/secure_credentials.h"
#include <grpc/impl/codegen/slice.h>
#include <grpc/slice.h>
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
#include <grpcpp/channel.h>
#include <grpcpp/impl/codegen/status_code_enum.h>
#include <grpcpp/impl/grpc_library.h>
#include <grpcpp/support/channel_arguments.h>
#include "src/core/lib/gpr/env.h"
#include "src/core/lib/iomgr/error.h"
#include "src/core/lib/iomgr/executor.h"
#include "src/core/lib/iomgr/load_file.h"
#include "src/core/lib/json/json.h"
#include "src/core/lib/security/transport/auth_filters.h"
#include "src/core/lib/security/util/json_util.h"
#include "src/cpp/client/create_channel_internal.h"
#include "src/cpp/common/secure_auth_context.h"
@ -105,6 +115,144 @@ std::shared_ptr<ChannelCredentials> SslCredentials(
namespace experimental {
namespace {
void ClearStsCredentialsOptions(StsCredentialsOptions* options) {
if (options == nullptr) return;
options->token_exchange_service_uri.clear();
options->resource.clear();
options->audience.clear();
options->scope.clear();
options->requested_token_type.clear();
options->subject_token_path.clear();
options->subject_token_type.clear();
options->actor_token_path.clear();
options->actor_token_type.clear();
}
} // namespace
// Builds STS credentials options from JSON.
grpc::Status StsCredentialsOptionsFromJson(const grpc::string& json_string,
StsCredentialsOptions* options) {
struct GrpcJsonDeleter {
void operator()(grpc_json* json) { grpc_json_destroy(json); }
};
if (options == nullptr) {
return grpc::Status(grpc::INVALID_ARGUMENT, "options cannot be nullptr.");
}
ClearStsCredentialsOptions(options);
std::vector<char> scratchpad(json_string.c_str(),
json_string.c_str() + json_string.size() + 1);
std::unique_ptr<grpc_json, GrpcJsonDeleter> json(
grpc_json_parse_string(&scratchpad[0]));
if (json == nullptr) {
return grpc::Status(grpc::INVALID_ARGUMENT, "Invalid json.");
}
// Required fields.
const char* value = grpc_json_get_string_property(
json.get(), "token_exchange_service_uri", nullptr);
if (value == nullptr) {
ClearStsCredentialsOptions(options);
return grpc::Status(grpc::INVALID_ARGUMENT,
"token_exchange_service_uri must be specified.");
}
options->token_exchange_service_uri.assign(value);
value =
grpc_json_get_string_property(json.get(), "subject_token_path", nullptr);
if (value == nullptr) {
ClearStsCredentialsOptions(options);
return grpc::Status(grpc::INVALID_ARGUMENT,
"subject_token_path must be specified.");
}
options->subject_token_path.assign(value);
value =
grpc_json_get_string_property(json.get(), "subject_token_type", nullptr);
if (value == nullptr) {
ClearStsCredentialsOptions(options);
return grpc::Status(grpc::INVALID_ARGUMENT,
"subject_token_type must be specified.");
}
options->subject_token_type.assign(value);
// Optional fields.
value = grpc_json_get_string_property(json.get(), "resource", nullptr);
if (value != nullptr) options->resource.assign(value);
value = grpc_json_get_string_property(json.get(), "audience", nullptr);
if (value != nullptr) options->audience.assign(value);
value = grpc_json_get_string_property(json.get(), "scope", nullptr);
if (value != nullptr) options->scope.assign(value);
value = grpc_json_get_string_property(json.get(), "requested_token_type",
nullptr);
if (value != nullptr) options->requested_token_type.assign(value);
value =
grpc_json_get_string_property(json.get(), "actor_token_path", nullptr);
if (value != nullptr) options->actor_token_path.assign(value);
value =
grpc_json_get_string_property(json.get(), "actor_token_type", nullptr);
if (value != nullptr) options->actor_token_type.assign(value);
return grpc::Status();
}
// Builds STS credentials Options from the $STS_CREDENTIALS env var.
grpc::Status StsCredentialsOptionsFromEnv(StsCredentialsOptions* options) {
if (options == nullptr) {
return grpc::Status(grpc::INVALID_ARGUMENT, "options cannot be nullptr.");
}
ClearStsCredentialsOptions(options);
grpc_slice json_string = grpc_empty_slice();
char* sts_creds_path = gpr_getenv("STS_CREDENTIALS");
grpc_error* error = GRPC_ERROR_NONE;
grpc::Status status;
auto cleanup = [&json_string, &sts_creds_path, &error, &status]() {
grpc_slice_unref_internal(json_string);
gpr_free(sts_creds_path);
GRPC_ERROR_UNREF(error);
return status;
};
if (sts_creds_path == nullptr) {
status = grpc::Status(grpc::NOT_FOUND,
"STS_CREDENTIALS environment variable not set.");
return cleanup();
}
error = grpc_load_file(sts_creds_path, 1, &json_string);
if (error != GRPC_ERROR_NONE) {
status = grpc::Status(grpc::NOT_FOUND, grpc_error_string(error));
return cleanup();
}
status = StsCredentialsOptionsFromJson(
reinterpret_cast<const char*>(GRPC_SLICE_START_PTR(json_string)),
options);
return cleanup();
}
// C++ to Core STS Credentials options.
grpc_sts_credentials_options StsCredentialsCppToCoreOptions(
const StsCredentialsOptions& options) {
grpc_sts_credentials_options opts;
memset(&opts, 0, sizeof(opts));
opts.token_exchange_service_uri = options.token_exchange_service_uri.c_str();
opts.resource = options.resource.c_str();
opts.audience = options.audience.c_str();
opts.scope = options.scope.c_str();
opts.requested_token_type = options.requested_token_type.c_str();
opts.subject_token_path = options.subject_token_path.c_str();
opts.subject_token_type = options.subject_token_type.c_str();
opts.actor_token_path = options.actor_token_path.c_str();
opts.actor_token_type = options.actor_token_type.c_str();
return opts;
}
// Builds STS credentials.
std::shared_ptr<CallCredentials> StsCredentials(
const StsCredentialsOptions& options) {
auto opts = StsCredentialsCppToCoreOptions(options);
return WrapCallCredentials(grpc_sts_credentials_create(&opts, nullptr));
}
// Builds ALTS Credentials given ALTS specific options
std::shared_ptr<ChannelCredentials> AltsCredentials(
const AltsCredentialsOptions& options) {

@ -22,6 +22,7 @@
#include <grpc/grpc_security.h>
#include <grpcpp/security/credentials.h>
#include <grpcpp/security/credentials_impl.h>
#include <grpcpp/support/config.h>
#include "src/core/lib/security/credentials/credentials.h"
@ -68,6 +69,16 @@ class SecureCallCredentials final : public CallCredentials {
grpc_call_credentials* const c_creds_;
};
namespace experimental {
// Transforms C++ STS Credentials options to core options. The pointers of the
// resulting core options point to the memory held by the C++ options so C++
// options need to be kept alive until after the core credentials creation.
grpc_sts_credentials_options StsCredentialsCppToCoreOptions(
const StsCredentialsOptions& options);
} // namespace experimental
} // namespace grpc_impl
namespace grpc {

@ -171,6 +171,7 @@ grpc_cc_binary(
":oauth2_utils",
"//:gpr",
"//:grpc",
"//:grpc++",
"//test/core/util:grpc_test_util",
],
)

@ -26,53 +26,40 @@
#include <grpc/support/log.h>
#include <grpc/support/sync.h>
#include "grpcpp/security/credentials_impl.h"
#include "src/core/lib/iomgr/error.h"
#include "src/core/lib/iomgr/load_file.h"
#include "src/core/lib/security/credentials/credentials.h"
#include "src/core/lib/security/util/json_util.h"
#include "src/cpp/client/secure_credentials.h"
#include "test/core/security/oauth2_utils.h"
#include "test/core/util/cmdline.h"
static grpc_sts_credentials_options sts_options_from_json(grpc_json* json) {
grpc_sts_credentials_options options;
memset(&options, 0, sizeof(options));
grpc_error* error = GRPC_ERROR_NONE;
options.sts_endpoint_url =
grpc_json_get_string_property(json, "sts_endpoint_url", &error);
GRPC_LOG_IF_ERROR("STS credentials parsing", error);
options.resource = grpc_json_get_string_property(json, "resource", nullptr);
options.audience = grpc_json_get_string_property(json, "audience", nullptr);
options.scope = grpc_json_get_string_property(json, "scope", nullptr);
options.requested_token_type =
grpc_json_get_string_property(json, "requested_token_type", nullptr);
options.subject_token_path =
grpc_json_get_string_property(json, "subject_token_path", &error);
GRPC_LOG_IF_ERROR("STS credentials parsing", error);
options.subject_token_type =
grpc_json_get_string_property(json, "subject_token_type", &error);
GRPC_LOG_IF_ERROR("STS credentials parsing", error);
options.actor_token_path =
grpc_json_get_string_property(json, "actor_token_path", nullptr);
options.actor_token_type =
grpc_json_get_string_property(json, "actor_token_type", nullptr);
return options;
}
static grpc_call_credentials* create_sts_creds(const char* json_file_path) {
grpc_slice sts_options_slice;
GPR_ASSERT(GRPC_LOG_IF_ERROR(
"load_file", grpc_load_file(json_file_path, 1, &sts_options_slice)));
grpc_json* json = grpc_json_parse_string(
reinterpret_cast<char*>(GRPC_SLICE_START_PTR(sts_options_slice)));
if (json == nullptr) {
gpr_log(GPR_ERROR, "Invalid json");
return nullptr;
grpc_impl::experimental::StsCredentialsOptions options;
if (strlen(json_file_path) == 0) {
auto status =
grpc_impl::experimental::StsCredentialsOptionsFromEnv(&options);
if (!status.ok()) {
gpr_log(GPR_ERROR, "%s", status.error_message().c_str());
return nullptr;
}
} else {
grpc_slice sts_options_slice;
GPR_ASSERT(GRPC_LOG_IF_ERROR(
"load_file", grpc_load_file(json_file_path, 1, &sts_options_slice)));
auto status = grpc_impl::experimental::StsCredentialsOptionsFromJson(
reinterpret_cast<const char*>(GRPC_SLICE_START_PTR(sts_options_slice)),
&options);
gpr_slice_unref(sts_options_slice);
if (!status.ok()) {
gpr_log(GPR_ERROR, "%s", status.error_message().c_str());
return nullptr;
}
}
grpc_sts_credentials_options options = sts_options_from_json(json);
grpc_call_credentials* result =
grpc_sts_credentials_create(&options, nullptr);
grpc_json_destroy(json);
gpr_slice_unref(sts_options_slice);
grpc_sts_credentials_options opts =
grpc_impl::experimental::StsCredentialsCppToCoreOptions(options);
grpc_call_credentials* result = grpc_sts_credentials_create(&opts, nullptr);
return result;
}
@ -99,9 +86,12 @@ int main(int argc, char** argv) {
gpr_cmdline_add_string(cl, "json_refresh_token",
"File path of the json refresh token.",
&json_refresh_token_file_path);
gpr_cmdline_add_string(cl, "json_sts_options",
"File path of the json sts options.",
&json_sts_options_file_path);
gpr_cmdline_add_string(
cl, "json_sts_options",
"File path of the json sts options. If the path is empty, the program "
"will attempt to use the $STS_CREDENTIALS environment variable to access "
"a file containing the options.",
&json_sts_options_file_path);
gpr_cmdline_add_flag(
cl, "gce",
"Get a token from the GCE metadata server (only works in GCE).",

@ -20,9 +20,14 @@
#include <memory>
#include <gmock/gmock.h>
#include <grpc/grpc.h>
#include <gtest/gtest.h>
#include "src/core/lib/gpr/env.h"
#include "src/core/lib/gpr/tmpfile.h"
#include "src/cpp/client/secure_credentials.h"
namespace grpc {
namespace testing {
@ -39,6 +44,158 @@ TEST_F(CredentialsTest, DefaultCredentials) {
auto creds = GoogleDefaultCredentials();
}
TEST_F(CredentialsTest, StsCredentialsOptionsCppToCore) {
grpc::experimental::StsCredentialsOptions options;
options.token_exchange_service_uri = "https://foo.com/exchange";
options.resource = "resource";
options.audience = "audience";
options.scope = "scope";
// options.requested_token_type explicitly not set.
options.subject_token_path = "/foo/bar";
options.subject_token_type = "nice_token_type";
options.actor_token_path = "/foo/baz";
options.actor_token_type = "even_nicer_token_type";
grpc_sts_credentials_options core_opts =
grpc_impl::experimental::StsCredentialsCppToCoreOptions(options);
EXPECT_EQ(options.token_exchange_service_uri,
core_opts.token_exchange_service_uri);
EXPECT_EQ(options.resource, core_opts.resource);
EXPECT_EQ(options.audience, core_opts.audience);
EXPECT_EQ(options.scope, core_opts.scope);
EXPECT_EQ(options.requested_token_type, core_opts.requested_token_type);
EXPECT_EQ(options.subject_token_path, core_opts.subject_token_path);
EXPECT_EQ(options.subject_token_type, core_opts.subject_token_type);
EXPECT_EQ(options.actor_token_path, core_opts.actor_token_path);
EXPECT_EQ(options.actor_token_type, core_opts.actor_token_type);
}
TEST_F(CredentialsTest, StsCredentialsOptionsJson) {
const char valid_json[] = R"(
{
"token_exchange_service_uri": "https://foo/exchange",
"resource": "resource",
"audience": "audience",
"scope": "scope",
"requested_token_type": "requested_token_type",
"subject_token_path": "subject_token_path",
"subject_token_type": "subject_token_type",
"actor_token_path": "actor_token_path",
"actor_token_type": "actor_token_type"
})";
grpc::experimental::StsCredentialsOptions options;
EXPECT_TRUE(
grpc::experimental::StsCredentialsOptionsFromJson(valid_json, &options)
.ok());
EXPECT_EQ(options.token_exchange_service_uri, "https://foo/exchange");
EXPECT_EQ(options.resource, "resource");
EXPECT_EQ(options.audience, "audience");
EXPECT_EQ(options.scope, "scope");
EXPECT_EQ(options.requested_token_type, "requested_token_type");
EXPECT_EQ(options.subject_token_path, "subject_token_path");
EXPECT_EQ(options.subject_token_type, "subject_token_type");
EXPECT_EQ(options.actor_token_path, "actor_token_path");
EXPECT_EQ(options.actor_token_type, "actor_token_type");
const char minimum_valid_json[] = R"(
{
"token_exchange_service_uri": "https://foo/exchange",
"subject_token_path": "subject_token_path",
"subject_token_type": "subject_token_type"
})";
EXPECT_TRUE(grpc::experimental::StsCredentialsOptionsFromJson(
minimum_valid_json, &options)
.ok());
EXPECT_EQ(options.token_exchange_service_uri, "https://foo/exchange");
EXPECT_EQ(options.resource, "");
EXPECT_EQ(options.audience, "");
EXPECT_EQ(options.scope, "");
EXPECT_EQ(options.requested_token_type, "");
EXPECT_EQ(options.subject_token_path, "subject_token_path");
EXPECT_EQ(options.subject_token_type, "subject_token_type");
EXPECT_EQ(options.actor_token_path, "");
EXPECT_EQ(options.actor_token_type, "");
const char invalid_json[] = R"(
I'm not a valid JSON.
)";
EXPECT_EQ(
grpc::INVALID_ARGUMENT,
grpc::experimental::StsCredentialsOptionsFromJson(invalid_json, &options)
.error_code());
const char invalid_json_missing_subject_token_type[] = R"(
{
"token_exchange_service_uri": "https://foo/exchange",
"subject_token_path": "subject_token_path"
})";
auto status = grpc::experimental::StsCredentialsOptionsFromJson(
invalid_json_missing_subject_token_type, &options);
EXPECT_EQ(grpc::INVALID_ARGUMENT, status.error_code());
EXPECT_THAT(status.error_message(),
::testing::HasSubstr("subject_token_type"));
const char invalid_json_missing_subject_token_path[] = R"(
{
"token_exchange_service_uri": "https://foo/exchange",
"subject_token_type": "subject_token_type"
})";
status = grpc::experimental::StsCredentialsOptionsFromJson(
invalid_json_missing_subject_token_path, &options);
EXPECT_EQ(grpc::INVALID_ARGUMENT, status.error_code());
EXPECT_THAT(status.error_message(),
::testing::HasSubstr("subject_token_path"));
const char invalid_json_missing_token_exchange_uri[] = R"(
{
"subject_token_path": "subject_token_path",
"subject_token_type": "subject_token_type"
})";
status = grpc::experimental::StsCredentialsOptionsFromJson(
invalid_json_missing_token_exchange_uri, &options);
EXPECT_EQ(grpc::INVALID_ARGUMENT, status.error_code());
EXPECT_THAT(status.error_message(),
::testing::HasSubstr("token_exchange_service_uri"));
}
TEST_F(CredentialsTest, StsCredentialsOptionsFromEnv) {
// Unset env and check expected failure.
gpr_unsetenv("STS_CREDENTIALS");
grpc::experimental::StsCredentialsOptions options;
auto status = grpc::experimental::StsCredentialsOptionsFromEnv(&options);
EXPECT_EQ(grpc::NOT_FOUND, status.error_code());
// Set env and check for success.
const char valid_json[] = R"(
{
"token_exchange_service_uri": "https://foo/exchange",
"subject_token_path": "subject_token_path",
"subject_token_type": "subject_token_type"
})";
char* creds_file_name;
FILE* creds_file = gpr_tmpfile("sts_creds_options", &creds_file_name);
ASSERT_NE(creds_file_name, nullptr);
ASSERT_NE(creds_file, nullptr);
ASSERT_EQ(sizeof(valid_json),
fwrite(valid_json, 1, sizeof(valid_json), creds_file));
fclose(creds_file);
gpr_setenv("STS_CREDENTIALS", creds_file_name);
gpr_free(creds_file_name);
status = grpc::experimental::StsCredentialsOptionsFromEnv(&options);
EXPECT_TRUE(status.ok());
EXPECT_EQ(options.token_exchange_service_uri, "https://foo/exchange");
EXPECT_EQ(options.resource, "");
EXPECT_EQ(options.audience, "");
EXPECT_EQ(options.scope, "");
EXPECT_EQ(options.requested_token_type, "");
EXPECT_EQ(options.subject_token_path, "subject_token_path");
EXPECT_EQ(options.subject_token_type, "subject_token_type");
EXPECT_EQ(options.actor_token_path, "");
EXPECT_EQ(options.actor_token_type, "");
// Cleanup.
gpr_unsetenv("STS_CREDENTIALS");
}
} // namespace testing
} // namespace grpc

@ -1024,22 +1024,6 @@
"third_party": false,
"type": "target"
},
{
"deps": [
"gpr",
"grpc",
"grpc_test_util"
],
"headers": [],
"is_filegroup": false,
"language": "c",
"name": "grpc_fetch_oauth2",
"src": [
"test/core/security/fetch_oauth2.cc"
],
"third_party": false,
"type": "target"
},
{
"deps": [
"gpr",
@ -3878,6 +3862,23 @@
"third_party": false,
"type": "target"
},
{
"deps": [
"gpr",
"grpc",
"grpc++",
"grpc_test_util"
],
"headers": [],
"is_filegroup": false,
"language": "c++",
"name": "grpc_fetch_oauth2",
"src": [
"test/core/security/fetch_oauth2.cc"
],
"third_party": false,
"type": "target"
},
{
"deps": [
"gpr",

Loading…
Cancel
Save