From 109edca9710ade3b67af6cf5a59cd48caf0da795 Mon Sep 17 00:00:00 2001 From: Julien Boeuf Date: Sun, 7 Jul 2019 22:44:33 -0700 Subject: [PATCH] Adding C++ API and implementation for STS credentials: - marked as experimental. - also changed the name of a field in the options struct. --- CMakeLists.txt | 75 +++++---- Makefile | 80 +++++---- build.yaml | 21 +-- include/grpc/grpc_security.h | 26 +-- include/grpcpp/security/credentials.h | 18 ++ include/grpcpp/security/credentials_impl.h | 64 +++++++ .../credentials/oauth2/oauth2_credentials.cc | 5 +- src/cpp/client/secure_credentials.cc | 148 +++++++++++++++++ src/cpp/client/secure_credentials.h | 11 ++ test/core/security/BUILD | 1 + test/core/security/fetch_oauth2.cc | 72 ++++---- test/cpp/client/credentials_test.cc | 157 ++++++++++++++++++ .../generated/sources_and_headers.json | 33 ++-- 13 files changed, 560 insertions(+), 151 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 22c926df537..8dfa4e8009a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 $<$:${_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 diff --git a/Makefile b/Makefile index c11462f419a..2a1bfad1f35 100644 --- a/Makefile +++ b/Makefile @@ -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 \ diff --git a/build.yaml b/build.yaml index 1c7be4e23ec..4134ecd8880 100644 --- a/build.yaml +++ b/build.yaml @@ -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 diff --git a/include/grpc/grpc_security.h b/include/grpc/grpc_security.h index 8e4f26a2854..777142f38c6 100644 --- a/include/grpc/grpc_security.h +++ b/include/grpc/grpc_security.h @@ -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 diff --git a/include/grpcpp/security/credentials.h b/include/grpcpp/security/credentials.h index b124d3d37be..5190b1b3393 100644 --- a/include/grpcpp/security/credentials.h +++ b/include/grpcpp/security/credentials.h @@ -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 StsCredentials( + const StsCredentialsOptions& options) { + return grpc_impl::experimental::StsCredentials(options); +} + typedef ::grpc_impl::experimental::AltsCredentialsOptions AltsCredentialsOptions; diff --git a/include/grpcpp/security/credentials_impl.h b/include/grpcpp/security/credentials_impl.h index 34920a55bbe..e236512f43e 100644 --- a/include/grpcpp/security/credentials_impl.h +++ b/include/grpcpp/security/credentials_impl.h @@ -259,6 +259,70 @@ std::shared_ptr 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 StsCredentials( + const StsCredentialsOptions& options); + /// Options used to build AltsCredentials. struct AltsCredentialsOptions { /// service accounts of target endpoint that will be acceptable diff --git a/src/core/lib/security/credentials/oauth2/oauth2_credentials.cc b/src/core/lib/security/credentials/oauth2/oauth2_credentials.cc index 06fa8bbdb4b..7a584835b96 100644 --- a/src/core/lib/security/credentials/oauth2/oauth2_credentials.cc +++ b/src/core/lib/security/credentials/oauth2/oauth2_credentials.cc @@ -18,6 +18,7 @@ #include +#include "src/core/lib/json/json.h" #include "src/core/lib/security/credentials/oauth2/oauth2_credentials.h" #include @@ -641,8 +642,8 @@ grpc_error* ValidateStsCredentialsOptions( *sts_url_out = nullptr; InlinedVector error_list; UniquePtr 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( diff --git a/src/cpp/client/secure_credentials.cc b/src/cpp/client/secure_credentials.cc index d73b3e035c8..5de5a76194e 100644 --- a/src/cpp/client/secure_credentials.cc +++ b/src/cpp/client/secure_credentials.cc @@ -17,13 +17,23 @@ */ #include "src/cpp/client/secure_credentials.h" + +#include +#include #include #include #include +#include #include #include + +#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 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 scratchpad(json_string.c_str(), + json_string.c_str() + json_string.size() + 1); + std::unique_ptr 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(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 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 AltsCredentials( const AltsCredentialsOptions& options) { diff --git a/src/cpp/client/secure_credentials.h b/src/cpp/client/secure_credentials.h index dd379ca657d..ed14df4938e 100644 --- a/src/cpp/client/secure_credentials.h +++ b/src/cpp/client/secure_credentials.h @@ -22,6 +22,7 @@ #include #include +#include #include #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 { diff --git a/test/core/security/BUILD b/test/core/security/BUILD index d8dcdc25231..835c0ad7b65 100644 --- a/test/core/security/BUILD +++ b/test/core/security/BUILD @@ -171,6 +171,7 @@ grpc_cc_binary( ":oauth2_utils", "//:gpr", "//:grpc", + "//:grpc++", "//test/core/util:grpc_test_util", ], ) diff --git a/test/core/security/fetch_oauth2.cc b/test/core/security/fetch_oauth2.cc index d404368b8b9..1aa999758b0 100644 --- a/test/core/security/fetch_oauth2.cc +++ b/test/core/security/fetch_oauth2.cc @@ -26,53 +26,40 @@ #include #include +#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(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(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).", diff --git a/test/cpp/client/credentials_test.cc b/test/cpp/client/credentials_test.cc index e64e260a46c..d560fb6d8e8 100644 --- a/test/cpp/client/credentials_test.cc +++ b/test/cpp/client/credentials_test.cc @@ -20,9 +20,14 @@ #include +#include #include #include +#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 diff --git a/tools/run_tests/generated/sources_and_headers.json b/tools/run_tests/generated/sources_and_headers.json index 92b3757c65e..0f560fd1063 100644 --- a/tools/run_tests/generated/sources_and_headers.json +++ b/tools/run_tests/generated/sources_and_headers.json @@ -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",