diff --git a/include/grpc/grpc_security.h b/include/grpc/grpc_security.h index 9edd70f39d7..6b7628e0966 100644 --- a/include/grpc/grpc_security.h +++ b/include/grpc/grpc_security.h @@ -159,9 +159,18 @@ GRPCAPI void grpc_channel_credentials_release(grpc_channel_credentials* creds); If nullptr is supplied, the returned channel credentials object will use a call credentials object based on the Application Default Credentials mechanism. + + user_provided_audience is an optional field for user to override the + audience in the JWT token if used. If user_provided_audience is nullptr, + the service URL will be used as the audience. Note that + user_provided_audience will only be used if a service account JWT access + credential is created by the application default credentials mechanism. Also + note that user_provided_audience will be ignored if the call_credentials is + not nullptr. */ GRPCAPI grpc_channel_credentials* grpc_google_default_credentials_create( - grpc_call_credentials* call_credentials); + grpc_call_credentials* call_credentials, + const char* user_provided_audience); /** Callback for getting the SSL roots override from the application. In case of success, *pem_roots_certs must be set to a NULL terminated string @@ -324,11 +333,14 @@ GRPCAPI gpr_timespec grpc_max_auth_token_lifetime(void); - json_key is the JSON key string containing the client's private key. - token_lifetime is the lifetime of each Json Web Token (JWT) created with this credentials. It should not exceed grpc_max_auth_token_lifetime or - will be cropped to this value. */ + will be cropped to this value. + - user_provided_audience is an optional field for user to override the + auidence in the JWT token. If user_provided_audience is nullptr, the + service URL will be used as the audience. */ GRPCAPI grpc_call_credentials* -grpc_service_account_jwt_access_credentials_create(const char* json_key, - gpr_timespec token_lifetime, - void* reserved); +grpc_service_account_jwt_access_credentials_create( + const char* json_key, gpr_timespec token_lifetime, + const char* user_provided_audience); /** Builds External Account credentials. - json_string is the JSON string containing the credentials options. diff --git a/include/grpcpp/security/credentials.h b/include/grpcpp/security/credentials.h index 1b1f994c1a9..0ffc4ebe9fc 100644 --- a/include/grpcpp/security/credentials.h +++ b/include/grpcpp/security/credentials.h @@ -172,11 +172,18 @@ struct SslCredentialsOptions { /// Builds credentials with reasonable defaults. /// +/// user_provided_audience is an optional field for user to override the +/// auidence in the JWT token. If user_provided_audience is empty, the service +/// URL will be used as the audience. Note that user_provided_audience will +/// only be used if a service account JWT access credential is created by +/// the application default credentials mechanism. +/// /// \warning Only use these credentials when connecting to a Google endpoint. /// Using these credentials to connect to any other service may result in this /// service being able to impersonate your client for requests to Google /// services. -std::shared_ptr GoogleDefaultCredentials(); +std::shared_ptr GoogleDefaultCredentials( + const grpc::string& user_provided_audience = ""); /// Builds SSL Credentials given SSL specific options std::shared_ptr SslCredentials( @@ -197,9 +204,13 @@ constexpr long kMaxAuthTokenLifetimeSecs = 3600; /// token_lifetime_seconds is the lifetime in seconds of each Json Web Token /// (JWT) created with this credentials. It should not exceed /// \a kMaxAuthTokenLifetimeSecs or will be cropped to this value. +/// user_provided_audience is an optional field for user to override the +/// auidence in the JWT token. If user_provided_audience is empty, the service +/// URL will be used as the audience. std::shared_ptr ServiceAccountJWTAccessCredentials( const grpc::string& json_key, - long token_lifetime_seconds = kMaxAuthTokenLifetimeSecs); + long token_lifetime_seconds = kMaxAuthTokenLifetimeSecs, + const grpc::string& user_provided_audience = ""); /// Builds refresh token credentials. /// json_refresh_token is the JSON string containing the refresh token along diff --git a/src/core/ext/xds/xds_bootstrap.cc b/src/core/ext/xds/xds_bootstrap.cc index 33cf276d2c1..acbe6d74f91 100644 --- a/src/core/ext/xds/xds_bootstrap.cc +++ b/src/core/ext/xds/xds_bootstrap.cc @@ -59,7 +59,7 @@ RefCountedPtr XdsChannelCredsRegistry::MakeChannelCreds(const std::string& creds_type, const Json& /*config*/) { if (creds_type == "google_default") { - return grpc_google_default_credentials_create(nullptr); + return grpc_google_default_credentials_create(nullptr, nullptr); } else if (creds_type == "insecure") { return grpc_insecure_credentials_create(); } else if (creds_type == "fake") { diff --git a/src/core/lib/security/credentials/google_default/google_default_credentials.cc b/src/core/lib/security/credentials/google_default/google_default_credentials.cc index 93fdd5323e1..98799b60156 100644 --- a/src/core/lib/security/credentials/google_default/google_default_credentials.cc +++ b/src/core/lib/security/credentials/google_default/google_default_credentials.cc @@ -224,7 +224,7 @@ static int is_metadata_server_reachable() { /* Takes ownership of creds_path if not NULL. */ static grpc_error_handle create_default_creds_from_path( - const std::string& creds_path, + const std::string& creds_path, const char* user_provided_audience, grpc_core::RefCountedPtr* creds) { grpc_auth_json_key key; grpc_auth_refresh_token token; @@ -250,9 +250,10 @@ static grpc_error_handle create_default_creds_from_path( /* First, try an auth json key. */ key = grpc_auth_json_key_create_from_json(json); if (grpc_auth_json_key_is_valid(&key)) { + if (user_provided_audience == nullptr) user_provided_audience = ""; result = grpc_service_account_jwt_access_credentials_create_from_auth_json_key( - key, grpc_max_auth_token_lifetime()); + key, grpc_max_auth_token_lifetime(), user_provided_audience); if (result == nullptr) { error = GRPC_ERROR_CREATE_FROM_STATIC_STRING( "grpc_service_account_jwt_access_credentials_create_from_auth_json_" @@ -306,14 +307,15 @@ static bool metadata_server_available() { } static grpc_core::RefCountedPtr make_default_call_creds( - grpc_error_handle* error) { + const char* user_provided_audience, grpc_error_handle* error) { grpc_core::RefCountedPtr call_creds; grpc_error_handle err; /* First, try the environment variable. */ char* path_from_env = gpr_getenv(GRPC_GOOGLE_CREDENTIALS_ENV_VAR); if (path_from_env != nullptr) { - err = create_default_creds_from_path(path_from_env, &call_creds); + err = create_default_creds_from_path(path_from_env, user_provided_audience, + &call_creds); gpr_free(path_from_env); if (err == GRPC_ERROR_NONE) return call_creds; *error = grpc_error_add_child(*error, err); @@ -321,7 +323,8 @@ static grpc_core::RefCountedPtr make_default_call_creds( /* Then the well-known file. */ err = create_default_creds_from_path( - grpc_get_well_known_google_credentials_file_path(), &call_creds); + grpc_get_well_known_google_credentials_file_path(), + user_provided_audience, &call_creds); if (err == GRPC_ERROR_NONE) return call_creds; *error = grpc_error_add_child(*error, err); @@ -343,7 +346,8 @@ static grpc_core::RefCountedPtr make_default_call_creds( } grpc_channel_credentials* grpc_google_default_credentials_create( - grpc_call_credentials* call_credentials) { + grpc_call_credentials* call_credentials, + const char* user_provided_audience) { grpc_channel_credentials* result = nullptr; grpc_core::RefCountedPtr call_creds(call_credentials); grpc_error_handle error = GRPC_ERROR_NONE; @@ -353,7 +357,7 @@ grpc_channel_credentials* grpc_google_default_credentials_create( (call_credentials)); if (call_creds == nullptr) { - call_creds = make_default_call_creds(&error); + call_creds = make_default_call_creds(user_provided_audience, &error); } if (call_creds != nullptr) { diff --git a/src/core/lib/security/credentials/jwt/jwt_credentials.cc b/src/core/lib/security/credentials/jwt/jwt_credentials.cc index 27590f4baf5..4483db417ee 100644 --- a/src/core/lib/security/credentials/jwt/jwt_credentials.cc +++ b/src/core/lib/security/credentials/jwt/jwt_credentials.cc @@ -42,10 +42,7 @@ using grpc_core::Json; void grpc_service_account_jwt_access_credentials::reset_cache() { GRPC_MDELEM_UNREF(cached_.jwt_md); cached_.jwt_md = GRPC_MDNULL; - if (cached_.service_url != nullptr) { - gpr_free(cached_.service_url); - cached_.service_url = nullptr; - } + cached_.audience.clear(); cached_.jwt_expiration = gpr_inf_past(GPR_CLOCK_REALTIME); } @@ -62,13 +59,14 @@ bool grpc_service_account_jwt_access_credentials::get_request_metadata( grpc_closure* /*on_request_metadata*/, grpc_error_handle* error) { gpr_timespec refresh_threshold = gpr_time_from_seconds( GRPC_SECURE_TOKEN_REFRESH_THRESHOLD_SECS, GPR_TIMESPAN); - + absl::string_view audience = user_provided_audience_.empty() + ? absl::string_view(context.service_url) + : user_provided_audience_; /* See if we can return a cached jwt. */ grpc_mdelem jwt_md = GRPC_MDNULL; { gpr_mu_lock(&cache_mu_); - if (cached_.service_url != nullptr && - strcmp(cached_.service_url, context.service_url) == 0 && + if (!cached_.audience.empty() && cached_.audience == audience && !GRPC_MDISNULL(cached_.jwt_md) && (gpr_time_cmp( gpr_time_sub(cached_.jwt_expiration, gpr_now(GPR_CLOCK_REALTIME)), @@ -83,14 +81,14 @@ bool grpc_service_account_jwt_access_credentials::get_request_metadata( /* Generate a new jwt. */ gpr_mu_lock(&cache_mu_); reset_cache(); - jwt = grpc_jwt_encode_and_sign(&key_, context.service_url, jwt_lifetime_, + jwt = grpc_jwt_encode_and_sign(&key_, audience.data(), jwt_lifetime_, nullptr); if (jwt != nullptr) { std::string md_value = absl::StrCat("Bearer ", jwt); gpr_free(jwt); cached_.jwt_expiration = gpr_time_add(gpr_now(GPR_CLOCK_REALTIME), jwt_lifetime_); - cached_.service_url = gpr_strdup(context.service_url); + cached_.audience = std::string(audience); cached_.jwt_md = grpc_mdelem_from_slices( grpc_slice_from_static_string(GRPC_AUTHORIZATION_METADATA_KEY), grpc_slice_from_cpp_string(std::move(md_value))); @@ -114,9 +112,12 @@ void grpc_service_account_jwt_access_credentials::cancel_get_request_metadata( } grpc_service_account_jwt_access_credentials:: - grpc_service_account_jwt_access_credentials(grpc_auth_json_key key, - gpr_timespec token_lifetime) - : grpc_call_credentials(GRPC_CALL_CREDENTIALS_TYPE_JWT), key_(key) { + grpc_service_account_jwt_access_credentials( + grpc_auth_json_key key, gpr_timespec token_lifetime, + std::string user_provided_audience) + : grpc_call_credentials(GRPC_CALL_CREDENTIALS_TYPE_JWT), + key_(key), + user_provided_audience_(std::move(user_provided_audience)) { gpr_timespec max_token_lifetime = grpc_max_auth_token_lifetime(); if (gpr_time_cmp(token_lifetime, max_token_lifetime) > 0) { gpr_log(GPR_INFO, @@ -131,13 +132,14 @@ grpc_service_account_jwt_access_credentials:: grpc_core::RefCountedPtr grpc_service_account_jwt_access_credentials_create_from_auth_json_key( - grpc_auth_json_key key, gpr_timespec token_lifetime) { + grpc_auth_json_key key, gpr_timespec token_lifetime, + std::string user_provided_audience) { if (!grpc_auth_json_key_is_valid(&key)) { gpr_log(GPR_ERROR, "Invalid input for jwt credentials creation"); return nullptr; } return grpc_core::MakeRefCounted( - key, token_lifetime); + key, token_lifetime, std::move(user_provided_audience)); } static char* redact_private_key(const char* json_key) { @@ -152,7 +154,8 @@ static char* redact_private_key(const char* json_key) { } grpc_call_credentials* grpc_service_account_jwt_access_credentials_create( - const char* json_key, gpr_timespec token_lifetime, void* reserved) { + const char* json_key, gpr_timespec token_lifetime, + const char* user_provided_audience) { if (GRPC_TRACE_FLAG_ENABLED(grpc_api_trace)) { char* clean_json = redact_private_key(json_key); gpr_log(GPR_INFO, @@ -161,15 +164,17 @@ grpc_call_credentials* grpc_service_account_jwt_access_credentials_create( "token_lifetime=" "gpr_timespec { tv_sec: %" PRId64 ", tv_nsec: %d, clock_type: %d }, " - "reserved=%p)", + "user_provided_audience=%s)", clean_json, token_lifetime.tv_sec, token_lifetime.tv_nsec, - static_cast(token_lifetime.clock_type), reserved); + static_cast(token_lifetime.clock_type), + user_provided_audience); gpr_free(clean_json); } - GPR_ASSERT(reserved == nullptr); grpc_core::ApplicationCallbackExecCtx callback_exec_ctx; grpc_core::ExecCtx exec_ctx; + if (user_provided_audience == nullptr) user_provided_audience = ""; return grpc_service_account_jwt_access_credentials_create_from_auth_json_key( - grpc_auth_json_key_create_from_string(json_key), token_lifetime) + grpc_auth_json_key_create_from_string(json_key), token_lifetime, + user_provided_audience) .release(); } diff --git a/src/core/lib/security/credentials/jwt/jwt_credentials.h b/src/core/lib/security/credentials/jwt/jwt_credentials.h index 5ae4c1f41fd..85462913d34 100644 --- a/src/core/lib/security/credentials/jwt/jwt_credentials.h +++ b/src/core/lib/security/credentials/jwt/jwt_credentials.h @@ -33,8 +33,9 @@ class grpc_service_account_jwt_access_credentials : public grpc_call_credentials { public: - grpc_service_account_jwt_access_credentials(grpc_auth_json_key key, - gpr_timespec token_lifetime); + grpc_service_account_jwt_access_credentials( + grpc_auth_json_key key, gpr_timespec token_lifetime, + std::string user_provided_audience); ~grpc_service_account_jwt_access_credentials() override; bool get_request_metadata(grpc_polling_entity* pollent, @@ -48,7 +49,9 @@ class grpc_service_account_jwt_access_credentials const gpr_timespec& jwt_lifetime() const { return jwt_lifetime_; } const grpc_auth_json_key& key() const { return key_; } - + const std::string& user_provided_audience() const { + return user_provided_audience_; + } std::string debug_string() override { return absl::StrFormat( "JWTAccessCredentials{ExpirationTime:%s}", @@ -60,22 +63,24 @@ class grpc_service_account_jwt_access_credentials void reset_cache(); // Have a simple cache for now with just 1 entry. We could have a map based on - // the service_url for a more sophisticated one. + // the audience for a more sophisticated one. gpr_mu cache_mu_; struct { grpc_mdelem jwt_md = GRPC_MDNULL; - char* service_url = nullptr; + std::string audience; gpr_timespec jwt_expiration; } cached_; grpc_auth_json_key key_; gpr_timespec jwt_lifetime_; + std::string user_provided_audience_; }; // Private constructor for jwt credentials from an already parsed json key. // Takes ownership of the key. grpc_core::RefCountedPtr grpc_service_account_jwt_access_credentials_create_from_auth_json_key( - grpc_auth_json_key key, gpr_timespec token_lifetime); + grpc_auth_json_key key, gpr_timespec token_lifetime, + std::string user_provided_audience); #endif /* GRPC_CORE_LIB_SECURITY_CREDENTIALS_JWT_JWT_CREDENTIALS_H */ diff --git a/src/cpp/client/secure_credentials.cc b/src/cpp/client/secure_credentials.cc index be99d54272f..1f3555fe463 100644 --- a/src/cpp/client/secure_credentials.cc +++ b/src/cpp/client/secure_credentials.cc @@ -103,10 +103,14 @@ std::shared_ptr WrapCallCredentials( } } // namespace -std::shared_ptr GoogleDefaultCredentials() { +std::shared_ptr GoogleDefaultCredentials( + const grpc::string& user_provided_audience) { grpc::GrpcLibraryCodegen init; // To call grpc_init(). return internal::WrapChannelCredentials( - grpc_google_default_credentials_create(nullptr)); + grpc_google_default_credentials_create( + nullptr, user_provided_audience.empty() + ? nullptr + : user_provided_audience.c_str())); } std::shared_ptr ExternalAccountCredentials( @@ -320,7 +324,8 @@ std::shared_ptr GoogleComputeEngineCredentials() { // Builds JWT credentials. std::shared_ptr ServiceAccountJWTAccessCredentials( - const std::string& json_key, long token_lifetime_seconds) { + const std::string& json_key, long token_lifetime_seconds, + const grpc::string& user_provided_audience) { grpc::GrpcLibraryCodegen init; // To call grpc_init(). if (token_lifetime_seconds <= 0) { gpr_log(GPR_ERROR, @@ -330,7 +335,9 @@ std::shared_ptr ServiceAccountJWTAccessCredentials( gpr_timespec lifetime = gpr_time_from_seconds(token_lifetime_seconds, GPR_TIMESPAN); return WrapCallCredentials(grpc_service_account_jwt_access_credentials_create( - json_key.c_str(), lifetime, nullptr)); + json_key.c_str(), lifetime, + user_provided_audience.empty() ? nullptr + : user_provided_audience.c_str())); } // Builds refresh token credentials. diff --git a/src/php/ext/grpc/channel_credentials.c b/src/php/ext/grpc/channel_credentials.c index 34b4c826085..ddf0881d0f2 100644 --- a/src/php/ext/grpc/channel_credentials.c +++ b/src/php/ext/grpc/channel_credentials.c @@ -131,7 +131,7 @@ PHP_METHOD(ChannelCredentials, invalidateDefaultRootsPem) { * @return ChannelCredentials The new default channel credentials object */ PHP_METHOD(ChannelCredentials, createDefault) { - grpc_channel_credentials *creds = grpc_google_default_credentials_create(NULL); + grpc_channel_credentials *creds = grpc_google_default_credentials_create(NULL, NULL); zval *creds_object = grpc_php_wrap_channel_credentials(creds, NULL, false TSRMLS_CC); RETURN_DESTROY_ZVAL(creds_object); diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pyx.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pyx.pxi index 23de3a0b188..3d3804f985c 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pyx.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/credentials.pyx.pxi @@ -434,7 +434,7 @@ cdef class ComputeEngineChannelCredentials(ChannelCredentials): raise ValueError("Call credentials may not be NULL.") cdef grpc_channel_credentials *c(self) except *: - self._c_creds = grpc_google_default_credentials_create(self._call_creds) + self._c_creds = grpc_google_default_credentials_create(self._call_creds, NULL) return self._c_creds diff --git a/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi b/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi index 2ef4eb26834..5931099dfb6 100644 --- a/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi +++ b/src/python/grpcio/grpc/_cython/_cygrpc/grpc.pxi @@ -525,7 +525,7 @@ cdef extern from "grpc/grpc_security.h": void grpc_set_ssl_roots_override_callback( grpc_ssl_roots_override_callback cb) nogil - grpc_channel_credentials *grpc_google_default_credentials_create(grpc_call_credentials* call_credentials) nogil + grpc_channel_credentials *grpc_google_default_credentials_create(grpc_call_credentials* call_credentials, const char *user_provided_audience) nogil grpc_channel_credentials *grpc_ssl_credentials_create( const char *pem_root_certs, grpc_ssl_pem_key_cert_pair *pem_key_cert_pair, verify_peer_options *verify_options, void *reserved) nogil @@ -551,7 +551,7 @@ cdef extern from "grpc/grpc_security.h": void *reserved) nogil grpc_call_credentials *grpc_service_account_jwt_access_credentials_create( const char *json_key, - gpr_timespec token_lifetime, void *reserved) nogil + gpr_timespec token_lifetime, const char* user_provided_audience) nogil grpc_call_credentials *grpc_google_refresh_token_credentials_create( const char *json_refresh_token, void *reserved) nogil grpc_call_credentials *grpc_google_iam_credentials_create( diff --git a/src/ruby/ext/grpc/rb_grpc_imports.generated.h b/src/ruby/ext/grpc/rb_grpc_imports.generated.h index 0d32f4d0d65..b319c2b27cb 100644 --- a/src/ruby/ext/grpc/rb_grpc_imports.generated.h +++ b/src/ruby/ext/grpc/rb_grpc_imports.generated.h @@ -353,7 +353,7 @@ extern grpc_call_credentials_release_type grpc_call_credentials_release_import; typedef void(*grpc_channel_credentials_release_type)(grpc_channel_credentials* creds); extern grpc_channel_credentials_release_type grpc_channel_credentials_release_import; #define grpc_channel_credentials_release grpc_channel_credentials_release_import -typedef grpc_channel_credentials*(*grpc_google_default_credentials_create_type)(grpc_call_credentials* call_credentials); +typedef grpc_channel_credentials*(*grpc_google_default_credentials_create_type)(grpc_call_credentials* call_credentials, const char* user_provided_audience); extern grpc_google_default_credentials_create_type grpc_google_default_credentials_create_import; #define grpc_google_default_credentials_create grpc_google_default_credentials_create_import typedef void(*grpc_set_ssl_roots_override_callback_type)(grpc_ssl_roots_override_callback cb); @@ -377,7 +377,7 @@ extern grpc_google_compute_engine_credentials_create_type grpc_google_compute_en typedef gpr_timespec(*grpc_max_auth_token_lifetime_type)(void); extern grpc_max_auth_token_lifetime_type grpc_max_auth_token_lifetime_import; #define grpc_max_auth_token_lifetime grpc_max_auth_token_lifetime_import -typedef grpc_call_credentials*(*grpc_service_account_jwt_access_credentials_create_type)(const char* json_key, gpr_timespec token_lifetime, void* reserved); +typedef grpc_call_credentials*(*grpc_service_account_jwt_access_credentials_create_type)(const char* json_key, gpr_timespec token_lifetime, const char* user_provided_audience); extern grpc_service_account_jwt_access_credentials_create_type grpc_service_account_jwt_access_credentials_create_import; #define grpc_service_account_jwt_access_credentials_create grpc_service_account_jwt_access_credentials_create_import typedef grpc_call_credentials*(*grpc_external_account_credentials_create_type)(const char* json_string, const char* scopes_string); diff --git a/test/core/security/credentials_test.cc b/test/core/security/credentials_test.cc index 0533247a182..1925a8f138d 100644 --- a/test/core/security/credentials_test.cc +++ b/test/core/security/credentials_test.cc @@ -129,6 +129,9 @@ static const char test_signed_jwt_token_type[] = static const char test_signed_jwt2[] = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImY0OTRkN2M1YWU2MGRmOTcyNmM5YW" "U2MDcyZTViYTdnZDkwODg5YzcifQ"; +static const char test_signed_jwt3[] = + "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImY0OTRkN2M1YWU2MGRmOTcyNmM6YW" + "U3MDcyZTViYTdnZDkwODg5YzcifQ"; static const char test_signed_jwt_token_type2[] = "urn:ietf:params:oauth:token-type:jwt"; static const char test_signed_jwt_path_prefix[] = "test_sign_jwt"; @@ -136,6 +139,8 @@ static const char test_signed_jwt_path_prefix[] = "test_sign_jwt"; static const char test_service_url[] = "https://foo.com/foo.v1"; static const char other_test_service_url[] = "https://bar.com/bar.v1"; +static const char test_user_provided_audience[] = "https://baz.com/baz.v2"; + static const char test_sts_endpoint_url[] = "https://foo.com:5555/v1/token-exchange"; @@ -1296,6 +1301,16 @@ static char* encode_and_sign_jwt_success(const grpc_auth_json_key* json_key, return gpr_strdup(test_signed_jwt); } +static char* encode_and_sign_jwt_user_provided_audience_success( + const grpc_auth_json_key* json_key, const char* audience, + gpr_timespec token_lifetime, const char* scope) { + if (strcmp(audience, test_user_provided_audience) == 0) { + validate_jwt_encode_and_sign_params(json_key, scope, token_lifetime); + return gpr_strdup(test_signed_jwt3); + } + return nullptr; +} + static char* encode_and_sign_jwt_failure(const grpc_auth_json_key* json_key, const char* /*audience*/, gpr_timespec token_lifetime, @@ -1408,6 +1423,56 @@ static void test_jwt_creds_success(void) { grpc_jwt_encode_and_sign_set_override(nullptr); } +static void test_jwt_creds_user_provided_audience_success(void) { + const char expected_creds_debug_string_prefix[] = + "JWTAccessCredentials{ExpirationTime:"; + + char* json_key_string = test_json_key_str(); + grpc_core::ExecCtx exec_ctx; + grpc_auth_metadata_context auth_md_ctx = {test_user_provided_audience, + test_method, nullptr, nullptr}; + std::string expected_md_value = absl::StrCat("Bearer ", test_signed_jwt3); + expected_md emd[] = {{"authorization", expected_md_value.c_str()}}; + grpc_call_credentials* creds = + grpc_service_account_jwt_access_credentials_create( + json_key_string, grpc_max_auth_token_lifetime(), + test_user_provided_audience); + + /* First request: jwt_encode_and_sign should be called. */ + request_metadata_state* state = + make_request_metadata_state(GRPC_ERROR_NONE, emd, GPR_ARRAY_SIZE(emd)); + grpc_jwt_encode_and_sign_set_override( + encode_and_sign_jwt_user_provided_audience_success); + run_request_metadata_test(creds, auth_md_ctx, state); + grpc_core::ExecCtx::Get()->Flush(); + + /* Second request: the cached token should be served directly. */ + state = + make_request_metadata_state(GRPC_ERROR_NONE, emd, GPR_ARRAY_SIZE(emd)); + grpc_jwt_encode_and_sign_set_override( + encode_and_sign_jwt_should_not_be_called); + run_request_metadata_test(creds, auth_md_ctx, state); + grpc_core::ExecCtx::Get()->Flush(); + + /* Third request: If service url is provided, it should keep using + * user_provided_audience and the cached token should be served. + */ + state = + make_request_metadata_state(GRPC_ERROR_NONE, emd, GPR_ARRAY_SIZE(emd)); + auth_md_ctx.service_url = test_service_url; + grpc_jwt_encode_and_sign_set_override( + encode_and_sign_jwt_should_not_be_called); + run_request_metadata_test(creds, auth_md_ctx, state); + grpc_core::ExecCtx::Get()->Flush(); + GPR_ASSERT(strncmp(expected_creds_debug_string_prefix, + creds->debug_string().c_str(), + strlen(expected_creds_debug_string_prefix)) == 0); + + creds->Unref(); + gpr_free(json_key_string); + grpc_jwt_encode_and_sign_set_override(nullptr); +} + static void test_jwt_creds_signing_failure(void) { const char expected_creds_debug_string_prefix[] = "JWTAccessCredentials{ExpirationTime:"; @@ -1464,7 +1529,7 @@ static void test_google_default_creds_auth_key(void) { "json_key_google_default_creds", json_key); gpr_free(json_key); creds = reinterpret_cast( - grpc_google_default_credentials_create(nullptr)); + grpc_google_default_credentials_create(nullptr, nullptr)); auto* default_creds = reinterpret_cast( creds->inner_creds()); @@ -1488,7 +1553,7 @@ static void test_google_default_creds_refresh_token(void) { set_google_default_creds_env_var_with_file_contents( "refresh_token_google_default_creds", test_refresh_token_str); creds = reinterpret_cast( - grpc_google_default_credentials_create(nullptr)); + grpc_google_default_credentials_create(nullptr, nullptr)); auto* default_creds = reinterpret_cast( creds->inner_creds()); @@ -1539,7 +1604,7 @@ static void test_google_default_creds_gce(void) { /* Simulate a successful detection of GCE. */ grpc_composite_channel_credentials* creds = reinterpret_cast( - grpc_google_default_credentials_create(nullptr)); + grpc_google_default_credentials_create(nullptr, nullptr)); /* Verify that the default creds actually embeds a GCE creds. */ GPR_ASSERT(creds != nullptr); @@ -1557,6 +1622,49 @@ static void test_google_default_creds_gce(void) { grpc_override_well_known_credentials_path_getter(nullptr); } +static void test_google_default_creds_user_provided_audience(void) { + grpc_core::ExecCtx exec_ctx; + grpc_composite_channel_credentials* creds; + char* json_key = test_json_key_str(); + grpc_flush_cached_google_default_credentials(); + set_gce_tenancy_checker_for_testing(test_gce_tenancy_checker); + g_test_gce_tenancy_checker_called = false; + g_test_is_on_gce = true; + set_google_default_creds_env_var_with_file_contents( + "json_key_google_default_creds", json_key); + gpr_free(json_key); + creds = reinterpret_cast( + grpc_google_default_credentials_create(nullptr, + test_user_provided_audience)); + auto* default_creds = + reinterpret_cast( + creds->inner_creds()); + GPR_ASSERT(default_creds->ssl_creds() != nullptr); + grpc_auth_metadata_context auth_md_ctx = {test_user_provided_audience, + test_method, nullptr, nullptr}; + std::string expected_md_value = absl::StrCat("Bearer ", test_signed_jwt3); + expected_md emd[] = {{"authorization", expected_md_value.c_str()}}; + request_metadata_state* state = + make_request_metadata_state(GRPC_ERROR_NONE, emd, GPR_ARRAY_SIZE(emd)); + grpc_jwt_encode_and_sign_set_override( + encode_and_sign_jwt_user_provided_audience_success); + run_request_metadata_test( + const_cast(creds->call_creds()), auth_md_ctx, + state); + grpc_core::ExecCtx::Get()->Flush(); + auto* jwt = + reinterpret_cast( + creds->call_creds()); + GPR_ASSERT( + strcmp(jwt->key().client_id, + "777-abaslkan11hlb6nmim3bpspl31ud.apps.googleusercontent.com") == + 0); + + GPR_ASSERT(g_test_gce_tenancy_checker_called == false); + creds->Unref(); + gpr_setenv(GRPC_GOOGLE_CREDENTIALS_ENV_VAR, ""); /* Reset. */ +} + static void test_google_default_creds_non_gce(void) { grpc_core::ExecCtx exec_ctx; expected_md emd[] = { @@ -1578,7 +1686,7 @@ static void test_google_default_creds_non_gce(void) { httpcli_post_should_not_be_called); grpc_composite_channel_credentials* creds = reinterpret_cast( - grpc_google_default_credentials_create(nullptr)); + grpc_google_default_credentials_create(nullptr, nullptr)); /* Verify that the default creds actually embeds a GCE creds. */ GPR_ASSERT(creds != nullptr); GPR_ASSERT(creds->call_creds() != nullptr); @@ -1616,10 +1724,12 @@ static void test_no_google_default_creds(void) { default_creds_gce_detection_httpcli_get_failure_override, httpcli_post_should_not_be_called); /* Simulate a successful detection of GCE. */ - GPR_ASSERT(grpc_google_default_credentials_create(nullptr) == nullptr); + GPR_ASSERT(grpc_google_default_credentials_create(nullptr, nullptr) == + nullptr); /* Try a second one. GCE detection should occur again. */ g_test_gce_tenancy_checker_called = false; - GPR_ASSERT(grpc_google_default_credentials_create(nullptr) == nullptr); + GPR_ASSERT(grpc_google_default_credentials_create(nullptr, nullptr) == + nullptr); GPR_ASSERT(g_test_gce_tenancy_checker_called == true); /* Cleanup. */ grpc_override_well_known_credentials_path_getter(nullptr); @@ -1645,7 +1755,7 @@ static void test_google_default_creds_call_creds_specified(void) { httpcli_post_should_not_be_called); grpc_composite_channel_credentials* channel_creds = reinterpret_cast( - grpc_google_default_credentials_create(call_creds)); + grpc_google_default_credentials_create(call_creds, nullptr)); GPR_ASSERT(g_test_gce_tenancy_checker_called == false); GPR_ASSERT(channel_creds != nullptr); GPR_ASSERT(channel_creds->call_creds() != nullptr); @@ -1704,7 +1814,8 @@ static void test_google_default_creds_not_default(void) { httpcli_post_should_not_be_called); grpc_composite_channel_credentials* channel_creds = reinterpret_cast( - grpc_google_default_credentials_create(call_creds.release())); + grpc_google_default_credentials_create(call_creds.release(), + nullptr)); GPR_ASSERT(g_test_gce_tenancy_checker_called == false); GPR_ASSERT(channel_creds != nullptr); GPR_ASSERT(channel_creds->call_creds() != nullptr); @@ -3390,12 +3501,14 @@ int main(int argc, char** argv) { test_sts_creds_token_file_not_found(); test_jwt_creds_lifetime(); test_jwt_creds_success(); + test_jwt_creds_user_provided_audience_success(); test_jwt_creds_signing_failure(); test_google_default_creds_auth_key(); test_google_default_creds_refresh_token(); test_google_default_creds_gce(); test_google_default_creds_non_gce(); test_no_google_default_creds(); + test_google_default_creds_user_provided_audience(); test_google_default_creds_call_creds_specified(); test_google_default_creds_not_default(); test_metadata_plugin_success();