From 79c9a67dee5bbbd65031296097994e222b885d22 Mon Sep 17 00:00:00 2001 From: aeitzman Date: Mon, 27 Nov 2023 16:46:39 -0800 Subject: [PATCH] [core/security] Adding metrics header to sts request for external account credentials (#34661) Closes #34661 COPYBARA_INTEGRATE_REVIEW=https://github.com/grpc/grpc/pull/34661 from aeitzman:metrics 9f53992ed4c8aef4f753e9c1ec3d65cb7eab1dba PiperOrigin-RevId: 585796526 --- .../aws_external_account_credentials.cc | 4 + .../aws_external_account_credentials.h | 4 + .../external/external_account_credentials.cc | 48 ++++---- .../external/external_account_credentials.h | 4 + .../file_external_account_credentials.cc | 4 + .../file_external_account_credentials.h | 4 + .../url_external_account_credentials.cc | 4 + .../url_external_account_credentials.h | 4 + test/core/security/credentials_test.cc | 111 ++++++++++++++++-- 9 files changed, 158 insertions(+), 29 deletions(-) diff --git a/src/core/lib/security/credentials/external/aws_external_account_credentials.cc b/src/core/lib/security/credentials/external/aws_external_account_credentials.cc index 68ddaaeb903..cd7d117b28e 100644 --- a/src/core/lib/security/credentials/external/aws_external_account_credentials.cc +++ b/src/core/lib/security/credentials/external/aws_external_account_credentials.cc @@ -525,4 +525,8 @@ void AwsExternalAccountCredentials::FinishRetrieveSubjectToken( } } +absl::string_view AwsExternalAccountCredentials::CredentialSourceType() { + return "aws"; +} + } // namespace grpc_core diff --git a/src/core/lib/security/credentials/external/aws_external_account_credentials.h b/src/core/lib/security/credentials/external/aws_external_account_credentials.h index e8adc02452f..a3a41236615 100644 --- a/src/core/lib/security/credentials/external/aws_external_account_credentials.h +++ b/src/core/lib/security/credentials/external/aws_external_account_credentials.h @@ -24,6 +24,8 @@ #include #include +#include "absl/strings/string_view.h" + #include "src/core/lib/gprpp/orphanable.h" #include "src/core/lib/gprpp/ref_counted_ptr.h" #include "src/core/lib/http/httpcli.h" @@ -72,6 +74,8 @@ class AwsExternalAccountCredentials final : public ExternalAccountCredentials { void AddMetadataRequestHeaders(grpc_http_request* request); + absl::string_view CredentialSourceType() override; + std::string audience_; OrphanablePtr http_request_; diff --git a/src/core/lib/security/credentials/external/external_account_credentials.cc b/src/core/lib/security/credentials/external/external_account_credentials.cc index 93228dbca4e..3ffd2acb0ef 100644 --- a/src/core/lib/security/credentials/external/external_account_credentials.cc +++ b/src/core/lib/security/credentials/external/external_account_credentials.cc @@ -26,6 +26,7 @@ #include "absl/status/status.h" #include "absl/status/statusor.h" +#include "absl/strings/escaping.h" #include "absl/strings/match.h" #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" @@ -53,7 +54,6 @@ #include "src/core/lib/security/credentials/external/file_external_account_credentials.h" #include "src/core/lib/security/credentials/external/url_external_account_credentials.h" #include "src/core/lib/security/util/json_util.h" -#include "src/core/lib/slice/b64.h" #include "src/core/lib/uri/uri_parser.h" #define EXTERNAL_ACCOUNT_CREDENTIALS_GRANT_TYPE \ @@ -271,6 +271,20 @@ std::string ExternalAccountCredentials::debug_string() { grpc_oauth2_token_fetcher_credentials::debug_string()); } +std::string ExternalAccountCredentials::MetricsHeaderValue() { + return absl::StrFormat( + "gl-cpp/unknown auth/%s google-byoid-sdk source/%s sa-impersonation/%v " + "config-lifetime/%v", + grpc_version_string(), CredentialSourceType(), + !options_.service_account_impersonation_url.empty(), + options_.service_account_impersonation.token_lifetime_seconds != + IMPERSONATED_CRED_DEFAULT_LIFETIME_IN_SECONDS); +} + +absl::string_view ExternalAccountCredentials::CredentialSourceType() { + return "unknown"; +} + // The token fetching flow: // 1. Retrieve subject token - Subclass's RetrieveSubjectToken() gets called // and the subject token is received in OnRetrieveSubjectTokenInternal(). @@ -317,27 +331,21 @@ void ExternalAccountCredentials::ExchangeToken( } grpc_http_request request; memset(&request, 0, sizeof(grpc_http_request)); - grpc_http_header* headers = nullptr; - if (!options_.client_id.empty() && !options_.client_secret.empty()) { - request.hdr_count = 2; - headers = static_cast( - gpr_malloc(sizeof(grpc_http_header) * request.hdr_count)); - headers[0].key = gpr_strdup("Content-Type"); - headers[0].value = gpr_strdup("application/x-www-form-urlencoded"); + const bool add_authorization_header = + !options_.client_id.empty() && !options_.client_secret.empty(); + request.hdr_count = add_authorization_header ? 3 : 2; + auto* headers = static_cast( + gpr_malloc(sizeof(grpc_http_header) * request.hdr_count)); + headers[0].key = gpr_strdup("Content-Type"); + headers[0].value = gpr_strdup("application/x-www-form-urlencoded"); + headers[1].key = gpr_strdup("x-goog-api-client"); + headers[1].value = gpr_strdup(MetricsHeaderValue().c_str()); + if (add_authorization_header) { std::string raw_cred = absl::StrFormat("%s:%s", options_.client_id, options_.client_secret); - char* encoded_cred = - grpc_base64_encode(raw_cred.c_str(), raw_cred.length(), 0, 0); - std::string str = absl::StrFormat("Basic %s", std::string(encoded_cred)); - headers[1].key = gpr_strdup("Authorization"); - headers[1].value = gpr_strdup(str.c_str()); - gpr_free(encoded_cred); - } else { - request.hdr_count = 1; - headers = static_cast( - gpr_malloc(sizeof(grpc_http_header) * request.hdr_count)); - headers[0].key = gpr_strdup("Content-Type"); - headers[0].value = gpr_strdup("application/x-www-form-urlencoded"); + std::string str = absl::StrFormat("Basic %s", absl::Base64Escape(raw_cred)); + headers[2].key = gpr_strdup("Authorization"); + headers[2].value = gpr_strdup(str.c_str()); } request.hdrs = headers; std::vector body_parts; diff --git a/src/core/lib/security/credentials/external/external_account_credentials.h b/src/core/lib/security/credentials/external/external_account_credentials.h index bcbd33b272b..13749db8567 100644 --- a/src/core/lib/security/credentials/external/external_account_credentials.h +++ b/src/core/lib/security/credentials/external/external_account_credentials.h @@ -101,6 +101,10 @@ class ExternalAccountCredentials HTTPRequestContext* ctx, const Options& options, std::function cb) = 0; + virtual absl::string_view CredentialSourceType(); + + std::string MetricsHeaderValue(); + private: // This method implements the common token fetch logic and it will be called // when grpc_oauth2_token_fetcher_credentials request a new access token. diff --git a/src/core/lib/security/credentials/external/file_external_account_credentials.cc b/src/core/lib/security/credentials/external/file_external_account_credentials.cc index e70131bfeef..0fb3ce5091d 100644 --- a/src/core/lib/security/credentials/external/file_external_account_credentials.cc +++ b/src/core/lib/security/credentials/external/file_external_account_credentials.cc @@ -137,4 +137,8 @@ void FileExternalAccountCredentials::RetrieveSubjectToken( cb(std::string(content), absl::OkStatus()); } +absl::string_view FileExternalAccountCredentials::CredentialSourceType() { + return "file"; +} + } // namespace grpc_core diff --git a/src/core/lib/security/credentials/external/file_external_account_credentials.h b/src/core/lib/security/credentials/external/file_external_account_credentials.h index 40f0e9f23f4..c8cf12177bd 100644 --- a/src/core/lib/security/credentials/external/file_external_account_credentials.h +++ b/src/core/lib/security/credentials/external/file_external_account_credentials.h @@ -23,6 +23,8 @@ #include #include +#include "absl/strings/string_view.h" + #include "src/core/lib/gprpp/ref_counted_ptr.h" #include "src/core/lib/iomgr/error.h" #include "src/core/lib/security/credentials/external/external_account_credentials.h" @@ -44,6 +46,8 @@ class FileExternalAccountCredentials final : public ExternalAccountCredentials { HTTPRequestContext* ctx, const Options& options, std::function cb) override; + absl::string_view CredentialSourceType() override; + // Fields of credential source std::string file_; std::string format_type_; diff --git a/src/core/lib/security/credentials/external/url_external_account_credentials.cc b/src/core/lib/security/credentials/external/url_external_account_credentials.cc index 0e4cb8afba1..4b977c045ee 100644 --- a/src/core/lib/security/credentials/external/url_external_account_credentials.cc +++ b/src/core/lib/security/credentials/external/url_external_account_credentials.cc @@ -240,4 +240,8 @@ void UrlExternalAccountCredentials::FinishRetrieveSubjectToken( } } +absl::string_view UrlExternalAccountCredentials::CredentialSourceType() { + return "url"; +} + } // namespace grpc_core diff --git a/src/core/lib/security/credentials/external/url_external_account_credentials.h b/src/core/lib/security/credentials/external/url_external_account_credentials.h index 71b734e591e..9331412d321 100644 --- a/src/core/lib/security/credentials/external/url_external_account_credentials.h +++ b/src/core/lib/security/credentials/external/url_external_account_credentials.h @@ -24,6 +24,8 @@ #include #include +#include "absl/strings/string_view.h" + #include "src/core/lib/gprpp/orphanable.h" #include "src/core/lib/gprpp/ref_counted_ptr.h" #include "src/core/lib/http/httpcli.h" @@ -48,6 +50,8 @@ class UrlExternalAccountCredentials final : public ExternalAccountCredentials { HTTPRequestContext* ctx, const Options& options, std::function cb) override; + absl::string_view CredentialSourceType() override; + static void OnRetrieveSubjectToken(void* arg, grpc_error_handle error); void OnRetrieveSubjectTokenInternal(grpc_error_handle error); diff --git a/test/core/security/credentials_test.cc b/test/core/security/credentials_test.cc index f0c0543275c..ad58ccd394f 100644 --- a/test/core/security/credentials_test.cc +++ b/test/core/security/credentials_test.cc @@ -2127,12 +2127,12 @@ void validate_external_account_creds_token_exchage_request( // Check the rest of the request. GPR_ASSERT(strcmp(host, "foo.com:5555") == 0); GPR_ASSERT(strcmp(path, "/token") == 0); - GPR_ASSERT(request->hdr_count == 2); + GPR_ASSERT(request->hdr_count == 3); GPR_ASSERT(strcmp(request->hdrs[0].key, "Content-Type") == 0); GPR_ASSERT( strcmp(request->hdrs[0].value, "application/x-www-form-urlencoded") == 0); - GPR_ASSERT(strcmp(request->hdrs[1].key, "Authorization") == 0); - GPR_ASSERT(strcmp(request->hdrs[1].value, + GPR_ASSERT(strcmp(request->hdrs[2].key, "Authorization") == 0); + GPR_ASSERT(strcmp(request->hdrs[2].value, "Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=") == 0); } @@ -2155,12 +2155,12 @@ void validate_external_account_creds_token_exchage_request_with_url_encode( // Check the rest of the request. GPR_ASSERT(strcmp(host, "foo.com:5555") == 0); GPR_ASSERT(strcmp(path, "/token_url_encode") == 0); - GPR_ASSERT(request->hdr_count == 2); + GPR_ASSERT(request->hdr_count == 3); GPR_ASSERT(strcmp(request->hdrs[0].key, "Content-Type") == 0); GPR_ASSERT( strcmp(request->hdrs[0].value, "application/x-www-form-urlencoded") == 0); - GPR_ASSERT(strcmp(request->hdrs[1].key, "Authorization") == 0); - GPR_ASSERT(strcmp(request->hdrs[1].value, + GPR_ASSERT(strcmp(request->hdrs[2].key, "Authorization") == 0); + GPR_ASSERT(strcmp(request->hdrs[2].value, "Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=") == 0); } @@ -2311,12 +2311,18 @@ void validate_aws_external_account_creds_token_exchage_request( // Check the rest of the request. GPR_ASSERT(strcmp(host, "foo.com:5555") == 0); GPR_ASSERT(strcmp(path, "/token") == 0); - GPR_ASSERT(request->hdr_count == 2); + GPR_ASSERT(request->hdr_count == 3); GPR_ASSERT(strcmp(request->hdrs[0].key, "Content-Type") == 0); GPR_ASSERT( strcmp(request->hdrs[0].value, "application/x-www-form-urlencoded") == 0); - GPR_ASSERT(strcmp(request->hdrs[1].key, "Authorization") == 0); - GPR_ASSERT(strcmp(request->hdrs[1].value, + GPR_ASSERT(strcmp(request->hdrs[1].key, "x-goog-api-client") == 0); + EXPECT_EQ( + request->hdrs[1].value, + absl::StrFormat("gl-cpp/unknown auth/%s google-byoid-sdk source/aws " + "sa-impersonation/false config-lifetime/false", + grpc_version_string())); + GPR_ASSERT(strcmp(request->hdrs[2].key, "Authorization") == 0); + GPR_ASSERT(strcmp(request->hdrs[2].value, "Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=") == 0); } @@ -2385,6 +2391,8 @@ class TestExternalAccountCredentials final : public ExternalAccountCredentials { std::vector scopes) : ExternalAccountCredentials(std::move(options), std::move(scopes)) {} + std::string GetMetricsValue() { return MetricsHeaderValue(); } + protected: void RetrieveSubjectToken( HTTPRequestContext* /*ctx*/, const Options& /*options*/, @@ -2393,6 +2401,91 @@ class TestExternalAccountCredentials final : public ExternalAccountCredentials { } }; +TEST(CredentialsTest, TestExternalAccountCredsMetricsHeader) { + Json credential_source = Json::FromString(""); + TestExternalAccountCredentials::ServiceAccountImpersonation + service_account_impersonation; + service_account_impersonation.token_lifetime_seconds = 3600; + TestExternalAccountCredentials::Options options = { + "external_account", // type; + "audience", // audience; + "subject_token_type", // subject_token_type; + "", // service_account_impersonation_url; + service_account_impersonation, // service_account_impersonation; + "https://foo.com:5555/token", // token_url; + "https://foo.com:5555/token_info", // token_info_url; + credential_source, // credential_source; + "quota_project_id", // quota_project_id; + "client_id", // client_id; + "client_secret", // client_secret; + "", // workforce_pool_user_project; + }; + TestExternalAccountCredentials creds(options, {}); + + EXPECT_EQ( + creds.GetMetricsValue(), + absl::StrFormat("gl-cpp/unknown auth/%s google-byoid-sdk source/unknown " + "sa-impersonation/false config-lifetime/false", + grpc_version_string())); +} + +TEST(CredentialsTest, + TestExternalAccountCredsMetricsHeaderWithServiceAccountImpersonation) { + Json credential_source = Json::FromString(""); + TestExternalAccountCredentials::ServiceAccountImpersonation + service_account_impersonation; + service_account_impersonation.token_lifetime_seconds = 3600; + TestExternalAccountCredentials::Options options = { + "external_account", // type; + "audience", // audience; + "subject_token_type", // subject_token_type; + "https://foo.com:5555/service_account_impersonation", // service_account_impersonation_url; + service_account_impersonation, // service_account_impersonation; + "https://foo.com:5555/token", // token_url; + "https://foo.com:5555/token_info", // token_info_url; + credential_source, // credential_source; + "quota_project_id", // quota_project_id; + "client_id", // client_id; + "client_secret", // client_secret; + "", // workforce_pool_user_project; + }; + TestExternalAccountCredentials creds(options, {}); + + EXPECT_EQ( + creds.GetMetricsValue(), + absl::StrFormat("gl-cpp/unknown auth/%s google-byoid-sdk source/unknown " + "sa-impersonation/true config-lifetime/false", + grpc_version_string())); +} + +TEST(CredentialsTest, TestExternalAccountCredsMetricsHeaderWithConfigLifetime) { + Json credential_source = Json::FromString(""); + TestExternalAccountCredentials::ServiceAccountImpersonation + service_account_impersonation; + service_account_impersonation.token_lifetime_seconds = 5000; + TestExternalAccountCredentials::Options options = { + "external_account", // type; + "audience", // audience; + "subject_token_type", // subject_token_type; + "https://foo.com:5555/service_account_impersonation", // service_account_impersonation_url; + service_account_impersonation, // service_account_impersonation; + "https://foo.com:5555/token", // token_url; + "https://foo.com:5555/token_info", // token_info_url; + credential_source, // credential_source; + "quota_project_id", // quota_project_id; + "client_id", // client_id; + "client_secret", // client_secret; + "", // workforce_pool_user_project; + }; + TestExternalAccountCredentials creds(options, {}); + + EXPECT_EQ( + creds.GetMetricsValue(), + absl::StrFormat("gl-cpp/unknown auth/%s google-byoid-sdk source/unknown " + "sa-impersonation/true config-lifetime/true", + grpc_version_string())); +} + TEST(CredentialsTest, TestExternalAccountCredsSuccess) { ExecCtx exec_ctx; Json credential_source = Json::FromString("");