1 //
2 //
3 // Copyright 2015 gRPC authors.
4 //
5 // Licensed under the Apache License, Version 2.0 (the "License");
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
8 //
9 // http://www.apache.org/licenses/LICENSE-2.0
10 //
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
16 //
17 //
18
19 #include "src/core/lib/security/credentials/credentials.h"
20
21 #include <gmock/gmock.h>
22 #include <grpc/credentials.h>
23 #include <grpc/grpc_security.h>
24 #include <grpc/slice.h>
25 #include <grpc/support/alloc.h>
26 #include <grpc/support/port_platform.h>
27 #include <grpc/support/string_util.h>
28 #include <grpc/support/time.h>
29 #include <openssl/rsa.h>
30 #include <stdlib.h>
31 #include <string.h>
32
33 #include <string>
34
35 #include "absl/log/check.h"
36 #include "absl/log/log.h"
37 #include "absl/strings/match.h"
38 #include "absl/strings/str_cat.h"
39 #include "absl/strings/str_format.h"
40 #include "absl/strings/str_replace.h"
41 #include "src/core/lib/channel/channel_args.h"
42 #include "src/core/lib/iomgr/error.h"
43 #include "src/core/lib/iomgr/timer_manager.h"
44 #include "src/core/lib/promise/exec_ctx_wakeup_scheduler.h"
45 #include "src/core/lib/promise/map.h"
46 #include "src/core/lib/promise/promise.h"
47 #include "src/core/lib/promise/seq.h"
48 #include "src/core/lib/security/context/security_context.h"
49 #include "src/core/lib/security/credentials/composite/composite_credentials.h"
50 #include "src/core/lib/security/credentials/external/aws_external_account_credentials.h"
51 #include "src/core/lib/security/credentials/external/external_account_credentials.h"
52 #include "src/core/lib/security/credentials/external/file_external_account_credentials.h"
53 #include "src/core/lib/security/credentials/external/url_external_account_credentials.h"
54 #include "src/core/lib/security/credentials/fake/fake_credentials.h"
55 #include "src/core/lib/security/credentials/gcp_service_account_identity/gcp_service_account_identity_credentials.h"
56 #include "src/core/lib/security/credentials/google_default/google_default_credentials.h"
57 #include "src/core/lib/security/credentials/iam/iam_credentials.h"
58 #include "src/core/lib/security/credentials/jwt/jwt_credentials.h"
59 #include "src/core/lib/security/credentials/oauth2/oauth2_credentials.h"
60 #include "src/core/lib/security/credentials/tls/grpc_tls_credentials_options.h"
61 #include "src/core/lib/security/credentials/xds/xds_credentials.h"
62 #include "src/core/lib/security/transport/auth_filters.h"
63 #include "src/core/lib/transport/error_utils.h"
64 #include "src/core/util/crash.h"
65 #include "src/core/util/env.h"
66 #include "src/core/util/host_port.h"
67 #include "src/core/util/http_client/httpcli.h"
68 #include "src/core/util/http_client/httpcli_ssl_credentials.h"
69 #include "src/core/util/json/json_reader.h"
70 #include "src/core/util/string.h"
71 #include "src/core/util/time.h"
72 #include "src/core/util/tmpfile.h"
73 #include "src/core/util/unique_type_name.h"
74 #include "src/core/util/uri.h"
75 #include "test/core/event_engine/event_engine_test_utils.h"
76 #include "test/core/event_engine/fuzzing_event_engine/fuzzing_event_engine.h"
77 #include "test/core/test_util/test_config.h"
78
79 namespace grpc_core {
80
81 using grpc_event_engine::experimental::FuzzingEventEngine;
82 using internal::grpc_flush_cached_google_default_credentials;
83 using internal::set_gce_tenancy_checker_for_testing;
84
85 namespace {
86
87 // -- Constants. --
88
89 const char test_google_iam_authorization_token[] = "blahblahblhahb";
90 const char test_google_iam_authority_selector[] = "respectmyauthoritah";
91 const char test_oauth2_bearer_token[] = "Bearer blaaslkdjfaslkdfasdsfasf";
92
93 // This JSON key was generated with the GCE console and revoked immediately.
94 // The identifiers have been changed as well.
95 // Maximum size for a string literal is 509 chars in C89, yay!
96 const char test_json_key_str_part1[] =
97 "{ \"private_key\": \"-----BEGIN PRIVATE KEY-----"
98 "\\nMIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAOEvJsnoHnyHkXcp\\n7mJE"
99 "qg"
100 "WGjiw71NfXByguekSKho65FxaGbsnSM9SMQAqVk7Q2rG+I0OpsT0LrWQtZ\\nyjSeg/"
101 "rWBQvS4hle4LfijkP3J5BG+"
102 "IXDMP8RfziNRQsenAXDNPkY4kJCvKux2xdD\\nOnVF6N7dL3nTYZg+"
103 "uQrNsMTz9UxVAgMBAAECgYEAzbLewe1xe9vy+2GoSsfib+28\\nDZgSE6Bu/"
104 "zuFoPrRc6qL9p2SsnV7txrunTyJkkOnPLND9ABAXybRTlcVKP/sGgza\\n/"
105 "8HpCqFYM9V8f34SBWfD4fRFT+n/"
106 "73cfRUtGXdXpseva2lh8RilIQfPhNZAncenU\\ngqXjDvpkypEusgXAykECQQD+";
107 const char test_json_key_str_part2[] =
108 "53XxNVnxBHsYb+AYEfklR96yVi8HywjVHP34+OQZ\\nCslxoHQM8s+"
109 "dBnjfScLu22JqkPv04xyxmt0QAKm9+vTdAkEA4ib7YvEAn2jXzcCI\\nEkoy2L/"
110 "XydR1GCHoacdfdAwiL2npOdnbvi4ZmdYRPY1LSTO058tQHKVXV7NLeCa3\\nAARh2QJBAMKeDA"
111 "G"
112 "W303SQv2cZTdbeaLKJbB5drz3eo3j7dDKjrTD9JupixFbzcGw\\n8FZi5c8idxiwC36kbAL6Hz"
113 "A"
114 "ZoX+ofI0CQE6KCzPJTtYNqyShgKAZdJ8hwOcvCZtf\\n6z8RJm0+"
115 "6YBd38lfh5j8mZd7aHFf6I17j5AQY7oPEc47TjJj/"
116 "5nZ68ECQQDvYuI3\\nLyK5fS8g0SYbmPOL9TlcHDOqwG0mrX9qpg5DC2fniXNSrrZ64GTDKdzZ"
117 "Y"
118 "Ap6LI9W\\nIqv4vr6y38N79TTC\\n-----END PRIVATE KEY-----\\n\", ";
119 const char test_json_key_str_part3[] =
120 "\"private_key_id\": \"e6b5137873db8d2ef81e06a47289e6434ec8a165\", "
121 "\"client_email\": "
122 "\"777-abaslkan11hlb6nmim3bpspl31ud@developer.gserviceaccount."
123 "com\", \"client_id\": "
124 "\"777-abaslkan11hlb6nmim3bpspl31ud.apps.googleusercontent."
125 "com\", \"type\": \"service_account\" }";
126
127 // Test refresh token.
128 const char test_refresh_token_str[] =
129 "{ \"client_id\": \"32555999999.apps.googleusercontent.com\","
130 " \"client_secret\": \"EmssLNjJy1332hD4KFsecret\","
131 " \"refresh_token\": \"1/Blahblasj424jladJDSGNf-u4Sua3HDA2ngjd42\","
132 " \"type\": \"authorized_user\"}";
133
134 const char test_external_account_credentials_psc_sts_str[] =
135 "{\"type\":\"external_account\",\"audience\":\"audience\",\"subject_"
136 "token_type\":\"subject_token_type\",\"service_account_impersonation_"
137 "url\":\"https://sts-xyz.p.googleapis.com:5555/"
138 "service_account_impersonation_url\",\"token_url\":\"https://"
139 "sts-xyz-123.p.googleapis.com:5555/token\",\"token_info_url\":\"https://"
140 "sts-xyz.p.googleapis.com:5555/introspect"
141 "token_info\",\"credential_source\":{\"file\":\"credentials_file_path\"},"
142 "\"quota_project_id\":\"quota_"
143 "project_id\",\"client_id\":\"client_id\",\"client_secret\":\"client_"
144 "secret\"}";
145
146 const char test_external_account_credentials_psc_iam_str[] =
147 "{\"type\":\"external_account\",\"audience\":\"audience\",\"subject_"
148 "token_type\":\"subject_token_type\",\"service_account_impersonation_"
149 "url\":\"https://iamcredentials-xyz.p.googleapis.com:5555/"
150 "service_account_impersonation_url\",\"token_url\":\"https://"
151 "iamcredentials-xyz-123.p.googleapis.com:5555/"
152 "token\",\"token_info_url\":\"https://"
153 "iamcredentials-xyz-123.p.googleapis.com:5555/introspect"
154 "token_info\",\"credential_source\":{\"file\":\"credentials_file_path\"},"
155 "\"quota_project_id\":\"quota_"
156 "project_id\",\"client_id\":\"client_id\",\"client_secret\":\"client_"
157 "secret\"}";
158
159 const char valid_oauth2_json_response[] =
160 "{\"access_token\":\"ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_\","
161 " \"expires_in\":3599, "
162 " \"token_type\":\"Bearer\"}";
163
164 const char valid_sts_json_response[] =
165 "{\"access_token\":\"ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_\","
166 " \"expires_in\":3599, "
167 " \"issued_token_type\":\"urn:ietf:params:oauth:token-type:access_token\", "
168 " \"token_type\":\"Bearer\"}";
169
170 const char test_scope[] = "perm1 perm2";
171
172 const char test_signed_jwt[] =
173 "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImY0OTRkN2M1YWU2MGRmOTcyNmM4YW"
174 "U0MDcyZTViYTdmZDkwODg2YzcifQ";
175 const char test_signed_jwt_token_type[] =
176 "urn:ietf:params:oauth:token-type:id_token";
177 const char test_signed_jwt2[] =
178 "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImY0OTRkN2M1YWU2MGRmOTcyNmM5YW"
179 "U2MDcyZTViYTdnZDkwODg5YzcifQ";
180 const char test_signed_jwt_token_type2[] =
181 "urn:ietf:params:oauth:token-type:jwt";
182 const char test_signed_jwt_path_prefix[] = "test_sign_jwt";
183
184 const char test_service_url[] = "https://foo.com/foo.v1";
185 const char test_service_url_no_service_name[] = "https://foo.com/";
186 const char other_test_service_url_no_service_name[] = "https://bar.com/";
187 const char test_method[] = "ThisIsNotAMethod";
188
189 const char kTestUrlScheme[] = "https";
190 const char kTestAuthority[] = "foo.com";
191 const char kTestPath[] = "/foo.v1/ThisIsNotAMethod";
192 const char kTestOtherAuthority[] = "bar.com";
193 const char kTestOtherPath[] = "/bar.v1/ThisIsNotAMethod";
194
195 const char test_sts_endpoint_url[] = "https://foo.com:5555/v1/token-exchange";
196
197 const char valid_external_account_creds_token_exchange_response[] =
198 "{\"access_token\":\"token_exchange_access_token\","
199 " \"expires_in\":3599,"
200 " \"token_type\":\"Bearer\"}";
201
202 const char
203 valid_external_account_creds_service_account_impersonation_response[] =
204 "{\"accessToken\":\"service_account_impersonation_access_token\","
205 " \"expireTime\":\"2050-01-01T00:00:00Z\"}";
206
207 const char
208 valid_url_external_account_creds_options_credential_source_format_text[] =
209 "{\"url\":\"https://foo.com:5555/generate_subject_token_format_text\","
210 "\"headers\":{\"Metadata-Flavor\":\"Google\"}}";
211
212 const char
213 valid_url_external_account_creds_options_credential_source_with_query_params_format_text
214 [] = "{\"url\":\"https://foo.com:5555/"
215 "path/to/url/creds?p1=v1&p2=v2\","
216 "\"headers\":{\"Metadata-Flavor\":\"Google\"}}";
217
218 const char
219 valid_url_external_account_creds_retrieve_subject_token_response_format_text
220 [] = "test_subject_token";
221
222 const char
223 valid_url_external_account_creds_options_credential_source_format_json[] =
224 "{\"url\":\"https://foo.com:5555/generate_subject_token_format_json\","
225 "\"headers\":{\"Metadata-Flavor\":\"Google\"},"
226 "\"format\":{\"type\":\"json\",\"subject_token_field_name\":\"access_"
227 "token\"}}";
228
229 const char
230 valid_url_external_account_creds_retrieve_subject_token_response_format_json
231 [] = "{\"access_token\":\"test_subject_token\"}";
232
233 const char invalid_url_external_account_creds_options_credential_source[] =
234 "{\"url\":\"invalid_credential_source_url\","
235 "\"headers\":{\"Metadata-Flavor\":\"Google\"}}";
236
237 const char valid_aws_external_account_creds_retrieve_signing_keys_response[] =
238 "{\"AccessKeyId\":\"test_access_key_id\",\"SecretAccessKey\":"
239 "\"test_secret_access_key\",\"Token\":\"test_token\"}";
240
241 const char aws_imdsv2_session_token[] = "imdsv2_session_token";
242
243 const char valid_aws_external_account_creds_options_credential_source[] =
244 "{\"environment_id\":\"aws1\","
245 "\"region_url\":\"https://169.254.169.254:5555/region_url\","
246 "\"url\":\"https://169.254.169.254:5555/url\","
247 "\"regional_cred_verification_url\":\"https://foo.com:5555/"
248 "regional_cred_verification_url_{region}\"}";
249
250 const char valid_aws_imdsv2_external_account_creds_options_credential_source[] =
251 "{\"environment_id\":\"aws1\","
252 "\"region_url\":\"http://169.254.169.254:5555/region_url\","
253 "\"url\":\"https://169.254.169.254:5555/url\","
254 "\"imdsv2_session_token_url\":\"https://169.254.169.254/"
255 "imdsv2_session_token_url\","
256 "\"regional_cred_verification_url\":\"https://foo.com:5555/"
257 "regional_cred_verification_url_{region}\"}";
258
259 const char valid_aws_external_account_creds_options_credential_source_ipv6[] =
260 "{\"environment_id\":\"aws1\","
261 "\"region_url\":\"https://[fd00:ec2::254]:5555/region_url\","
262 "\"url\":\"http://[fd00:ec2::254]:5555/url\","
263 "\"imdsv2_session_token_url\":\"https://[fd00:ec2::254]/"
264 "imdsv2_session_token_url\","
265 "\"regional_cred_verification_url\":\"https://foo.com:5555/"
266 "regional_cred_verification_url_{region}\"}";
267
268 const char
269 invalid_aws_external_account_creds_options_credential_source_unmatched_environment_id
270 [] = "{\"environment_id\":\"unsupported_aws_version\","
271 "\"region_url\":\"https://169.254.169.254:5555/region_url\","
272 "\"url\":\"https://169.254.169.254:5555/url\","
273 "\"regional_cred_verification_url\":\"https://foo.com:5555/"
274 "regional_cred_verification_url_{region}\"}";
275
276 const char
277 invalid_aws_external_account_creds_options_credential_source_invalid_regional_cred_verification_url
278 [] = "{\"environment_id\":\"aws1\","
279 "\"region_url\":\"https://169.254.169.254:5555/region_url\","
280 "\"url\":\"https://169.254.169.254:5555/url\","
281 "\"regional_cred_verification_url\":\"invalid_regional_cred_"
282 "verification_url\"}";
283
284 const char
285 invalid_aws_external_account_creds_options_credential_source_missing_role_name
286 [] = "{\"environment_id\":\"aws1\","
287 "\"region_url\":\"https://169.254.169.254:5555/region_url\","
288 "\"url\":\"https://169.254.169.254:5555/url_no_role_name\","
289 "\"regional_cred_verification_url\":\"https://foo.com:5555/"
290 "regional_cred_verification_url_{region}\"}";
291
292 // -- Global state flags. --
293
294 bool g_test_is_on_gce = false;
295
296 bool g_test_gce_tenancy_checker_called = false;
297
298 // -- Utils. --
299
test_json_key_str(void)300 char* test_json_key_str(void) {
301 size_t result_len = strlen(test_json_key_str_part1) +
302 strlen(test_json_key_str_part2) +
303 strlen(test_json_key_str_part3);
304 char* result = static_cast<char*>(gpr_malloc(result_len + 1));
305 char* current = result;
306 strcpy(result, test_json_key_str_part1);
307 current += strlen(test_json_key_str_part1);
308 strcpy(current, test_json_key_str_part2);
309 current += strlen(test_json_key_str_part2);
310 strcpy(current, test_json_key_str_part3);
311 return result;
312 }
313
http_response(int status,const char * body)314 grpc_http_response http_response(int status, const char* body) {
315 grpc_http_response response;
316 response = {};
317 response.status = status;
318 response.body = gpr_strdup(const_cast<char*>(body));
319 response.body_length = strlen(body);
320 return response;
321 }
322
323 // -- Tests. --
324
325 class CredentialsTest : public ::testing::Test {
326 protected:
SetUp()327 void SetUp() override { grpc_init(); }
328
TearDown()329 void TearDown() override { grpc_shutdown_blocking(); }
330 };
331
TEST_F(CredentialsTest,TestOauth2TokenFetcherCredsParsingOk)332 TEST_F(CredentialsTest, TestOauth2TokenFetcherCredsParsingOk) {
333 ExecCtx exec_ctx;
334 absl::optional<Slice> token_value;
335 Duration token_lifetime;
336 grpc_http_response response = http_response(200, valid_oauth2_json_response);
337 CHECK(grpc_oauth2_token_fetcher_credentials_parse_server_response(
338 &response, &token_value, &token_lifetime) == GRPC_CREDENTIALS_OK);
339 CHECK(token_lifetime == Duration::Seconds(3599));
340 CHECK(token_value->as_string_view() ==
341 "Bearer ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_");
342 grpc_http_response_destroy(&response);
343 }
344
TEST_F(CredentialsTest,TestOauth2TokenFetcherCredsParsingBadHttpStatus)345 TEST_F(CredentialsTest, TestOauth2TokenFetcherCredsParsingBadHttpStatus) {
346 ExecCtx exec_ctx;
347 absl::optional<Slice> token_value;
348 Duration token_lifetime;
349 grpc_http_response response = http_response(401, valid_oauth2_json_response);
350 CHECK(grpc_oauth2_token_fetcher_credentials_parse_server_response(
351 &response, &token_value, &token_lifetime) ==
352 GRPC_CREDENTIALS_ERROR);
353 grpc_http_response_destroy(&response);
354 }
355
TEST_F(CredentialsTest,TestOauth2TokenFetcherCredsParsingEmptyHttpBody)356 TEST_F(CredentialsTest, TestOauth2TokenFetcherCredsParsingEmptyHttpBody) {
357 ExecCtx exec_ctx;
358 absl::optional<Slice> token_value;
359 Duration token_lifetime;
360 grpc_http_response response = http_response(200, "");
361 CHECK(grpc_oauth2_token_fetcher_credentials_parse_server_response(
362 &response, &token_value, &token_lifetime) ==
363 GRPC_CREDENTIALS_ERROR);
364 grpc_http_response_destroy(&response);
365 }
366
TEST_F(CredentialsTest,TestOauth2TokenFetcherCredsParsingInvalidJson)367 TEST_F(CredentialsTest, TestOauth2TokenFetcherCredsParsingInvalidJson) {
368 ExecCtx exec_ctx;
369 absl::optional<Slice> token_value;
370 Duration token_lifetime;
371 grpc_http_response response =
372 http_response(200,
373 "{\"access_token\":\"ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_\","
374 " \"expires_in\":3599, "
375 " \"token_type\":\"Bearer\"");
376 CHECK(grpc_oauth2_token_fetcher_credentials_parse_server_response(
377 &response, &token_value, &token_lifetime) ==
378 GRPC_CREDENTIALS_ERROR);
379 grpc_http_response_destroy(&response);
380 }
381
TEST_F(CredentialsTest,TestOauth2TokenFetcherCredsParsingMissingToken)382 TEST_F(CredentialsTest, TestOauth2TokenFetcherCredsParsingMissingToken) {
383 ExecCtx exec_ctx;
384 absl::optional<Slice> token_value;
385 Duration token_lifetime;
386 grpc_http_response response = http_response(200,
387 "{"
388 " \"expires_in\":3599, "
389 " \"token_type\":\"Bearer\"}");
390 CHECK(grpc_oauth2_token_fetcher_credentials_parse_server_response(
391 &response, &token_value, &token_lifetime) ==
392 GRPC_CREDENTIALS_ERROR);
393 grpc_http_response_destroy(&response);
394 }
395
TEST_F(CredentialsTest,TestOauth2TokenFetcherCredsParsingMissingTokenType)396 TEST_F(CredentialsTest, TestOauth2TokenFetcherCredsParsingMissingTokenType) {
397 ExecCtx exec_ctx;
398 absl::optional<Slice> token_value;
399 Duration token_lifetime;
400 grpc_http_response response =
401 http_response(200,
402 "{\"access_token\":\"ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_\","
403 " \"expires_in\":3599, "
404 "}");
405 CHECK(grpc_oauth2_token_fetcher_credentials_parse_server_response(
406 &response, &token_value, &token_lifetime) ==
407 GRPC_CREDENTIALS_ERROR);
408 grpc_http_response_destroy(&response);
409 }
410
TEST_F(CredentialsTest,TestOauth2TokenFetcherCredsParsingMissingTokenLifetime)411 TEST_F(CredentialsTest,
412 TestOauth2TokenFetcherCredsParsingMissingTokenLifetime) {
413 ExecCtx exec_ctx;
414 absl::optional<Slice> token_value;
415 Duration token_lifetime;
416 grpc_http_response response =
417 http_response(200,
418 "{\"access_token\":\"ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_\","
419 " \"token_type\":\"Bearer\"}");
420 CHECK(grpc_oauth2_token_fetcher_credentials_parse_server_response(
421 &response, &token_value, &token_lifetime) ==
422 GRPC_CREDENTIALS_ERROR);
423 grpc_http_response_destroy(&response);
424 }
425
426 class RequestMetadataState : public RefCounted<RequestMetadataState> {
427 public:
NewInstance(grpc_error_handle expected_error,std::string expected,absl::optional<bool> expect_delay=absl::nullopt)428 static RefCountedPtr<RequestMetadataState> NewInstance(
429 grpc_error_handle expected_error, std::string expected,
430 absl::optional<bool> expect_delay = absl::nullopt) {
431 return MakeRefCounted<RequestMetadataState>(
432 expected_error, std::move(expected), expect_delay,
433 grpc_polling_entity_create_from_pollset_set(grpc_pollset_set_create()));
434 }
435
RequestMetadataState(grpc_error_handle expected_error,std::string expected,absl::optional<bool> expect_delay,grpc_polling_entity pollent)436 RequestMetadataState(grpc_error_handle expected_error, std::string expected,
437 absl::optional<bool> expect_delay,
438 grpc_polling_entity pollent)
439 : expected_error_(expected_error),
440 expected_(std::move(expected)),
441 expect_delay_(expect_delay),
442 pollent_(pollent) {}
443
~RequestMetadataState()444 ~RequestMetadataState() override {
445 grpc_pollset_set_destroy(grpc_polling_entity_pollset_set(&pollent_));
446 }
447
RunRequestMetadataTest(grpc_call_credentials * creds,const char * url_scheme,const char * authority,const char * path)448 void RunRequestMetadataTest(grpc_call_credentials* creds,
449 const char* url_scheme, const char* authority,
450 const char* path) {
451 auto self = Ref();
452 get_request_metadata_args_.security_connector =
453 MakeRefCounted<BogusSecurityConnector>(url_scheme);
454 md_.Set(HttpAuthorityMetadata(), Slice::FromStaticString(authority));
455 md_.Set(HttpPathMetadata(), Slice::FromStaticString(path));
456 activity_ = MakeActivity(
457 [this, creds] {
458 return Seq(
459 CheckDelayed(creds->GetRequestMetadata(
460 ClientMetadataHandle(&md_, Arena::PooledDeleter(nullptr)),
461 &get_request_metadata_args_)),
462 [this](std::tuple<absl::StatusOr<ClientMetadataHandle>, bool>
463 metadata_and_delayed) {
464 auto& metadata = std::get<0>(metadata_and_delayed);
465 const bool delayed = std::get<1>(metadata_and_delayed);
466 if (expect_delay_.has_value()) {
467 EXPECT_EQ(delayed, *expect_delay_);
468 }
469 if (metadata.ok()) {
470 EXPECT_EQ(metadata->get(), &md_);
471 }
472 return metadata.status();
473 });
474 },
475 ExecCtxWakeupScheduler(),
476 [self](absl::Status status) mutable {
477 self->CheckRequestMetadata(
478 absl_status_to_grpc_error(std::move(status)));
479 self.reset();
480 },
481 arena_.get(), &pollent_);
482 }
483
484 private:
485 // No-op security connector, exists only to inject url_scheme.
486 class BogusSecurityConnector : public grpc_channel_security_connector {
487 public:
BogusSecurityConnector(absl::string_view url_scheme)488 explicit BogusSecurityConnector(absl::string_view url_scheme)
489 : grpc_channel_security_connector(url_scheme, nullptr, nullptr) {}
490
check_peer(tsi_peer,grpc_endpoint *,const ChannelArgs &,RefCountedPtr<grpc_auth_context> *,grpc_closure *)491 void check_peer(tsi_peer, grpc_endpoint*, const ChannelArgs&,
492 RefCountedPtr<grpc_auth_context>*, grpc_closure*) override {
493 Crash("unreachable");
494 }
495
cancel_check_peer(grpc_closure *,grpc_error_handle)496 void cancel_check_peer(grpc_closure*, grpc_error_handle) override {
497 Crash("unreachable");
498 }
499
cmp(const grpc_security_connector *) const500 int cmp(const grpc_security_connector*) const override {
501 GPR_UNREACHABLE_CODE(return 0);
502 }
503
CheckCallHost(absl::string_view,grpc_auth_context *)504 ArenaPromise<absl::Status> CheckCallHost(absl::string_view,
505 grpc_auth_context*) override {
506 GPR_UNREACHABLE_CODE(
507 return Immediate(absl::PermissionDeniedError("should never happen")));
508 }
509
add_handshakers(const ChannelArgs &,grpc_pollset_set *,HandshakeManager *)510 void add_handshakers(const ChannelArgs&, grpc_pollset_set*,
511 HandshakeManager*) override {
512 Crash("unreachable");
513 }
514 };
515
CheckRequestMetadata(grpc_error_handle error)516 void CheckRequestMetadata(grpc_error_handle error) {
517 if (expected_error_.ok()) {
518 ASSERT_TRUE(error.ok()) << error;
519 } else {
520 grpc_status_code actual_code;
521 std::string actual_message;
522 grpc_error_get_status(error, Timestamp::InfFuture(), &actual_code,
523 &actual_message, nullptr, nullptr);
524 EXPECT_EQ(absl::Status(static_cast<absl::StatusCode>(actual_code),
525 actual_message),
526 expected_error_);
527 }
528 md_.Remove(HttpAuthorityMetadata());
529 md_.Remove(HttpPathMetadata());
530 LOG(INFO) << "expected metadata: " << expected_;
531 LOG(INFO) << "actual metadata: " << md_.DebugString();
532 }
533
534 grpc_error_handle expected_error_;
535 std::string expected_;
536 absl::optional<bool> expect_delay_;
537 RefCountedPtr<Arena> arena_ = SimpleArenaAllocator()->MakeArena();
538 grpc_metadata_batch md_;
539 grpc_call_credentials::GetRequestMetadataArgs get_request_metadata_args_;
540 grpc_polling_entity pollent_;
541 ActivityPtr activity_;
542 };
543
TEST_F(CredentialsTest,TestGoogleIamCreds)544 TEST_F(CredentialsTest, TestGoogleIamCreds) {
545 ExecCtx exec_ctx;
546 auto state = RequestMetadataState::NewInstance(
547 absl::OkStatus(),
548 absl::StrCat(GRPC_IAM_AUTHORIZATION_TOKEN_METADATA_KEY, ": ",
549 test_google_iam_authorization_token, ", ",
550 GRPC_IAM_AUTHORITY_SELECTOR_METADATA_KEY, ": ",
551 test_google_iam_authority_selector));
552 grpc_call_credentials* creds = grpc_google_iam_credentials_create(
553 test_google_iam_authorization_token, test_google_iam_authority_selector,
554 nullptr);
555 // Check security level.
556 CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
557 state->RunRequestMetadataTest(creds, kTestUrlScheme, kTestAuthority,
558 kTestPath);
559 creds->Unref();
560 }
561
TEST_F(CredentialsTest,TestAccessTokenCreds)562 TEST_F(CredentialsTest, TestAccessTokenCreds) {
563 ExecCtx exec_ctx;
564 auto state = RequestMetadataState::NewInstance(absl::OkStatus(),
565 "authorization: Bearer blah");
566 grpc_call_credentials* creds =
567 grpc_access_token_credentials_create("blah", nullptr);
568 CHECK(creds->type() == grpc_access_token_credentials::Type());
569 // Check security level.
570 CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
571 state->RunRequestMetadataTest(creds, kTestUrlScheme, kTestAuthority,
572 kTestPath);
573 creds->Unref();
574 }
575
576 class check_channel_oauth2 final : public grpc_channel_credentials {
577 public:
create_security_connector(RefCountedPtr<grpc_call_credentials> call_creds,const char *,ChannelArgs *)578 RefCountedPtr<grpc_channel_security_connector> create_security_connector(
579 RefCountedPtr<grpc_call_credentials> call_creds, const char* /*target*/,
580 ChannelArgs* /*new_args*/) override {
581 CHECK(type() == Type());
582 CHECK(call_creds != nullptr);
583 CHECK(call_creds->type() == grpc_access_token_credentials::Type());
584 return nullptr;
585 }
586
Type()587 static UniqueTypeName Type() {
588 static UniqueTypeName::Factory kFactory("check_channel_oauth2");
589 return kFactory.Create();
590 }
591
type() const592 UniqueTypeName type() const override { return Type(); }
593
594 private:
cmp_impl(const grpc_channel_credentials * other) const595 int cmp_impl(const grpc_channel_credentials* other) const override {
596 // TODO(yashykt): Check if we can do something better here
597 return QsortCompare(static_cast<const grpc_channel_credentials*>(this),
598 other);
599 }
600 };
601
TEST_F(CredentialsTest,TestChannelOauth2CompositeCreds)602 TEST_F(CredentialsTest, TestChannelOauth2CompositeCreds) {
603 ExecCtx exec_ctx;
604 ChannelArgs new_args;
605 grpc_channel_credentials* channel_creds = new check_channel_oauth2();
606 grpc_call_credentials* oauth2_creds =
607 grpc_access_token_credentials_create("blah", nullptr);
608 grpc_channel_credentials* channel_oauth2_creds =
609 grpc_composite_channel_credentials_create(channel_creds, oauth2_creds,
610 nullptr);
611 grpc_channel_credentials_release(channel_creds);
612 grpc_call_credentials_release(oauth2_creds);
613 channel_oauth2_creds->create_security_connector(nullptr, nullptr, &new_args);
614 grpc_channel_credentials_release(channel_oauth2_creds);
615 }
616
TEST_F(CredentialsTest,TestOauth2GoogleIamCompositeCreds)617 TEST_F(CredentialsTest, TestOauth2GoogleIamCompositeCreds) {
618 ExecCtx exec_ctx;
619 auto state = RequestMetadataState::NewInstance(
620 absl::OkStatus(),
621 absl::StrCat(GRPC_AUTHORIZATION_METADATA_KEY, ": ",
622 test_oauth2_bearer_token, ", ",
623 GRPC_IAM_AUTHORIZATION_TOKEN_METADATA_KEY, ": ",
624 test_google_iam_authorization_token, ", ",
625 GRPC_IAM_AUTHORITY_SELECTOR_METADATA_KEY, ": ",
626 test_google_iam_authority_selector));
627 grpc_call_credentials* oauth2_creds = grpc_md_only_test_credentials_create(
628 "authorization", test_oauth2_bearer_token);
629
630 // Check security level of fake credentials.
631 CHECK_EQ(oauth2_creds->min_security_level(), GRPC_SECURITY_NONE);
632
633 grpc_call_credentials* google_iam_creds = grpc_google_iam_credentials_create(
634 test_google_iam_authorization_token, test_google_iam_authority_selector,
635 nullptr);
636 grpc_call_credentials* composite_creds =
637 grpc_composite_call_credentials_create(oauth2_creds, google_iam_creds,
638 nullptr);
639 // Check security level of composite credentials.
640 CHECK_EQ(composite_creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
641
642 oauth2_creds->Unref();
643 google_iam_creds->Unref();
644 CHECK(composite_creds->type() == grpc_composite_call_credentials::Type());
645 const grpc_composite_call_credentials::CallCredentialsList& creds_list =
646 static_cast<const grpc_composite_call_credentials*>(composite_creds)
647 ->inner();
648 CHECK_EQ(creds_list.size(), 2);
649 CHECK(creds_list[0]->type() == grpc_md_only_test_credentials::Type());
650 CHECK(creds_list[1]->type() == grpc_google_iam_credentials::Type());
651 state->RunRequestMetadataTest(composite_creds, kTestUrlScheme, kTestAuthority,
652 kTestPath);
653 composite_creds->Unref();
654 }
655
656 class check_channel_oauth2_google_iam final : public grpc_channel_credentials {
657 public:
create_security_connector(RefCountedPtr<grpc_call_credentials> call_creds,const char *,ChannelArgs *)658 RefCountedPtr<grpc_channel_security_connector> create_security_connector(
659 RefCountedPtr<grpc_call_credentials> call_creds, const char* /*target*/,
660 ChannelArgs* /*new_args*/) override {
661 CHECK(type() == Type());
662 CHECK(call_creds != nullptr);
663 CHECK(call_creds->type() == grpc_composite_call_credentials::Type());
664 const grpc_composite_call_credentials::CallCredentialsList& creds_list =
665 static_cast<const grpc_composite_call_credentials*>(call_creds.get())
666 ->inner();
667 CHECK(creds_list[0]->type() == grpc_access_token_credentials::Type());
668 CHECK(creds_list[1]->type() == grpc_google_iam_credentials::Type());
669 return nullptr;
670 }
671
Type()672 static UniqueTypeName Type() {
673 static UniqueTypeName::Factory kFactory("check_channel_oauth2_google_iam");
674 return kFactory.Create();
675 }
676
type() const677 UniqueTypeName type() const override { return Type(); }
678
679 private:
cmp_impl(const grpc_channel_credentials * other) const680 int cmp_impl(const grpc_channel_credentials* other) const override {
681 // TODO(yashykt): Check if we can do something better here
682 return QsortCompare(static_cast<const grpc_channel_credentials*>(this),
683 other);
684 }
685 };
686
TEST_F(CredentialsTest,TestChannelOauth2GoogleIamCompositeCreds)687 TEST_F(CredentialsTest, TestChannelOauth2GoogleIamCompositeCreds) {
688 ExecCtx exec_ctx;
689 ChannelArgs new_args;
690 grpc_channel_credentials* channel_creds =
691 new check_channel_oauth2_google_iam();
692 grpc_call_credentials* oauth2_creds =
693 grpc_access_token_credentials_create("blah", nullptr);
694 grpc_channel_credentials* channel_oauth2_creds =
695 grpc_composite_channel_credentials_create(channel_creds, oauth2_creds,
696 nullptr);
697 grpc_call_credentials* google_iam_creds = grpc_google_iam_credentials_create(
698 test_google_iam_authorization_token, test_google_iam_authority_selector,
699 nullptr);
700
701 grpc_channel_credentials* channel_oauth2_iam_creds =
702 grpc_composite_channel_credentials_create(channel_oauth2_creds,
703 google_iam_creds, nullptr);
704 grpc_channel_credentials_release(channel_creds);
705 grpc_call_credentials_release(oauth2_creds);
706 grpc_channel_credentials_release(channel_oauth2_creds);
707 grpc_call_credentials_release(google_iam_creds);
708
709 channel_oauth2_iam_creds->create_security_connector(nullptr, nullptr,
710 &new_args);
711
712 grpc_channel_credentials_release(channel_oauth2_iam_creds);
713 }
714
validate_compute_engine_http_request(const grpc_http_request * request,const URI & uri)715 void validate_compute_engine_http_request(const grpc_http_request* request,
716 const URI& uri) {
717 EXPECT_EQ(uri.authority(), "metadata.google.internal.");
718 EXPECT_EQ(uri.path(),
719 "/computeMetadata/v1/instance/service-accounts/default/token");
720 ASSERT_EQ(request->hdr_count, 1);
721 EXPECT_EQ(absl::string_view(request->hdrs[0].key), "Metadata-Flavor");
722 EXPECT_EQ(absl::string_view(request->hdrs[0].value), "Google");
723 }
724
compute_engine_httpcli_get_success_override(const grpc_http_request * request,const URI & uri,Timestamp,grpc_closure * on_done,grpc_http_response * response)725 int compute_engine_httpcli_get_success_override(
726 const grpc_http_request* request, const URI& uri, Timestamp /*deadline*/,
727 grpc_closure* on_done, grpc_http_response* response) {
728 validate_compute_engine_http_request(request, uri);
729 *response = http_response(200, valid_oauth2_json_response);
730 ExecCtx::Run(DEBUG_LOCATION, on_done, absl::OkStatus());
731 return 1;
732 }
733
compute_engine_httpcli_get_failure_override(const grpc_http_request * request,const URI & uri,Timestamp,grpc_closure * on_done,grpc_http_response * response)734 int compute_engine_httpcli_get_failure_override(
735 const grpc_http_request* request, const URI& uri, Timestamp /*deadline*/,
736 grpc_closure* on_done, grpc_http_response* response) {
737 validate_compute_engine_http_request(request, uri);
738 *response = http_response(403, "Not Authorized.");
739 ExecCtx::Run(DEBUG_LOCATION, on_done, absl::OkStatus());
740 return 1;
741 }
742
httpcli_post_should_not_be_called(const grpc_http_request *,const URI &,absl::string_view,Timestamp,grpc_closure *,grpc_http_response *)743 int httpcli_post_should_not_be_called(const grpc_http_request* /*request*/,
744 const URI& /*uri*/,
745 absl::string_view /*body*/,
746 Timestamp /*deadline*/,
747 grpc_closure* /*on_done*/,
748 grpc_http_response* /*response*/) {
749 CHECK(false) << "HTTP POST should not be called";
750 return 1;
751 }
752
httpcli_get_should_not_be_called(const grpc_http_request *,const URI &,Timestamp,grpc_closure *,grpc_http_response *)753 int httpcli_get_should_not_be_called(const grpc_http_request* /*request*/,
754 const URI& /*uri*/, Timestamp /*deadline*/,
755 grpc_closure* /*on_done*/,
756 grpc_http_response* /*response*/) {
757 CHECK(false) << "HTTP GET should not be called";
758 return 1;
759 }
760
httpcli_put_should_not_be_called(const grpc_http_request *,const URI &,absl::string_view,Timestamp,grpc_closure *,grpc_http_response *)761 int httpcli_put_should_not_be_called(const grpc_http_request* /*request*/,
762 const URI& /*uri*/,
763 absl::string_view /*body*/,
764 Timestamp /*deadline*/,
765 grpc_closure* /*on_done*/,
766 grpc_http_response* /*response*/) {
767 CHECK(false) << "HTTP PUT should not be called";
768 return 1;
769 }
770
TEST_F(CredentialsTest,TestComputeEngineCredsSuccess)771 TEST_F(CredentialsTest, TestComputeEngineCredsSuccess) {
772 ExecCtx exec_ctx;
773 std::string emd = "authorization: Bearer ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_";
774 const char expected_creds_debug_string[] =
775 "GoogleComputeEngineTokenFetcherCredentials{"
776 "OAuth2TokenFetcherCredentials}";
777 grpc_call_credentials* creds =
778 grpc_google_compute_engine_credentials_create(nullptr);
779 // Check security level.
780 CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
781
782 // First request: http get should be called.
783 auto state = RequestMetadataState::NewInstance(absl::OkStatus(), emd);
784 HttpRequest::SetOverride(compute_engine_httpcli_get_success_override,
785 httpcli_post_should_not_be_called,
786 httpcli_put_should_not_be_called);
787 state->RunRequestMetadataTest(creds, kTestUrlScheme, kTestAuthority,
788 kTestPath);
789 ExecCtx::Get()->Flush();
790
791 // Second request: the cached token should be served directly.
792 state = RequestMetadataState::NewInstance(absl::OkStatus(), emd);
793 HttpRequest::SetOverride(httpcli_get_should_not_be_called,
794 httpcli_post_should_not_be_called,
795 httpcli_put_should_not_be_called);
796 state->RunRequestMetadataTest(creds, kTestUrlScheme, kTestAuthority,
797 kTestPath);
798 ExecCtx::Get()->Flush();
799
800 CHECK_EQ(strcmp(creds->debug_string().c_str(), expected_creds_debug_string),
801 0);
802 creds->Unref();
803 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
804 }
805
TEST_F(CredentialsTest,TestComputeEngineCredsFailure)806 TEST_F(CredentialsTest, TestComputeEngineCredsFailure) {
807 ExecCtx exec_ctx;
808 const char expected_creds_debug_string[] =
809 "GoogleComputeEngineTokenFetcherCredentials{"
810 "OAuth2TokenFetcherCredentials}";
811 auto state = RequestMetadataState::NewInstance(
812 // TODO(roth): This should return UNAUTHENTICATED.
813 absl::UnavailableError("error parsing oauth2 token"), {});
814 grpc_call_credentials* creds =
815 grpc_google_compute_engine_credentials_create(nullptr);
816 HttpRequest::SetOverride(compute_engine_httpcli_get_failure_override,
817 httpcli_post_should_not_be_called,
818 httpcli_put_should_not_be_called);
819 state->RunRequestMetadataTest(creds, kTestUrlScheme, kTestAuthority,
820 kTestPath);
821 CHECK_EQ(strcmp(creds->debug_string().c_str(), expected_creds_debug_string),
822 0);
823 creds->Unref();
824 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
825 }
826
validate_refresh_token_http_request(const grpc_http_request * request,const URI & uri,absl::string_view body)827 void validate_refresh_token_http_request(const grpc_http_request* request,
828 const URI& uri,
829 absl::string_view body) {
830 // The content of the assertion is tested extensively in json_token_test.
831 EXPECT_EQ(body, absl::StrFormat(GRPC_REFRESH_TOKEN_POST_BODY_FORMAT_STRING,
832 "32555999999.apps.googleusercontent.com",
833 "EmssLNjJy1332hD4KFsecret",
834 "1/Blahblasj424jladJDSGNf-u4Sua3HDA2ngjd42"));
835 EXPECT_EQ(uri.authority(), GRPC_GOOGLE_OAUTH2_SERVICE_HOST);
836 EXPECT_EQ(uri.path(), GRPC_GOOGLE_OAUTH2_SERVICE_TOKEN_PATH);
837 ASSERT_EQ(request->hdr_count, 1);
838 EXPECT_EQ(absl::string_view(request->hdrs[0].key), "Content-Type");
839 EXPECT_EQ(absl::string_view(request->hdrs[0].value),
840 "application/x-www-form-urlencoded");
841 }
842
refresh_token_httpcli_post_success(const grpc_http_request * request,const URI & uri,absl::string_view body,Timestamp,grpc_closure * on_done,grpc_http_response * response)843 int refresh_token_httpcli_post_success(const grpc_http_request* request,
844 const URI& uri, absl::string_view body,
845 Timestamp /*deadline*/,
846 grpc_closure* on_done,
847 grpc_http_response* response) {
848 validate_refresh_token_http_request(request, uri, body);
849 *response = http_response(200, valid_oauth2_json_response);
850 ExecCtx::Run(DEBUG_LOCATION, on_done, absl::OkStatus());
851 return 1;
852 }
853
token_httpcli_post_failure(const grpc_http_request *,const URI &,absl::string_view,Timestamp,grpc_closure * on_done,grpc_http_response * response)854 int token_httpcli_post_failure(const grpc_http_request* /*request*/,
855 const URI& /*uri*/, absl::string_view /*body*/,
856 Timestamp /*deadline*/, grpc_closure* on_done,
857 grpc_http_response* response) {
858 *response = http_response(403, "Not Authorized.");
859 ExecCtx::Run(DEBUG_LOCATION, on_done, absl::OkStatus());
860 return 1;
861 }
862
TEST_F(CredentialsTest,TestRefreshTokenCredsSuccess)863 TEST_F(CredentialsTest, TestRefreshTokenCredsSuccess) {
864 ExecCtx exec_ctx;
865 std::string emd = "authorization: Bearer ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_";
866 const char expected_creds_debug_string[] =
867 "GoogleRefreshToken{ClientID:32555999999.apps.googleusercontent.com,"
868 "OAuth2TokenFetcherCredentials}";
869 grpc_call_credentials* creds = grpc_google_refresh_token_credentials_create(
870 test_refresh_token_str, nullptr);
871
872 // Check security level.
873 CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
874
875 // First request: http put should be called.
876 auto state = RequestMetadataState::NewInstance(absl::OkStatus(), emd);
877 HttpRequest::SetOverride(httpcli_get_should_not_be_called,
878 refresh_token_httpcli_post_success,
879 httpcli_put_should_not_be_called);
880 state->RunRequestMetadataTest(creds, kTestUrlScheme, kTestAuthority,
881 kTestPath);
882 ExecCtx::Get()->Flush();
883
884 // Second request: the cached token should be served directly.
885 state = RequestMetadataState::NewInstance(absl::OkStatus(), emd);
886 HttpRequest::SetOverride(httpcli_get_should_not_be_called,
887 httpcli_post_should_not_be_called,
888 httpcli_put_should_not_be_called);
889 state->RunRequestMetadataTest(creds, kTestUrlScheme, kTestAuthority,
890 kTestPath);
891 ExecCtx::Get()->Flush();
892 CHECK_EQ(strcmp(creds->debug_string().c_str(), expected_creds_debug_string),
893 0);
894
895 creds->Unref();
896 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
897 }
898
TEST_F(CredentialsTest,TestRefreshTokenCredsFailure)899 TEST_F(CredentialsTest, TestRefreshTokenCredsFailure) {
900 ExecCtx exec_ctx;
901 const char expected_creds_debug_string[] =
902 "GoogleRefreshToken{ClientID:32555999999.apps.googleusercontent.com,"
903 "OAuth2TokenFetcherCredentials}";
904 auto state = RequestMetadataState::NewInstance(
905 // TODO(roth): This should return UNAUTHENTICATED.
906 absl::UnavailableError("error parsing oauth2 token"), {});
907 grpc_call_credentials* creds = grpc_google_refresh_token_credentials_create(
908 test_refresh_token_str, nullptr);
909 HttpRequest::SetOverride(httpcli_get_should_not_be_called,
910 token_httpcli_post_failure,
911 httpcli_put_should_not_be_called);
912 state->RunRequestMetadataTest(creds, kTestUrlScheme, kTestAuthority,
913 kTestPath);
914 CHECK_EQ(strcmp(creds->debug_string().c_str(), expected_creds_debug_string),
915 0);
916
917 creds->Unref();
918 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
919 }
920
TEST_F(CredentialsTest,TestValidStsCredsOptions)921 TEST_F(CredentialsTest, TestValidStsCredsOptions) {
922 grpc_sts_credentials_options valid_options = {
923 test_sts_endpoint_url, // sts_endpoint_url
924 nullptr, // resource
925 nullptr, // audience
926 nullptr, // scope
927 nullptr, // requested_token_type
928 test_signed_jwt_path_prefix, // subject_token_path
929 test_signed_jwt_token_type, // subject_token_type
930 nullptr, // actor_token_path
931 nullptr // actor_token_type
932 };
933 absl::StatusOr<URI> sts_url = ValidateStsCredentialsOptions(&valid_options);
934 CHECK_OK(sts_url);
935 absl::string_view host;
936 absl::string_view port;
937 CHECK(SplitHostPort(sts_url->authority(), &host, &port));
938 CHECK(host == "foo.com");
939 CHECK(port == "5555");
940 }
941
TEST_F(CredentialsTest,TestInvalidStsCredsOptions)942 TEST_F(CredentialsTest, TestInvalidStsCredsOptions) {
943 grpc_sts_credentials_options invalid_options = {
944 test_sts_endpoint_url, // sts_endpoint_url
945 nullptr, // resource
946 nullptr, // audience
947 nullptr, // scope
948 nullptr, // requested_token_type
949 nullptr, // subject_token_path (Required)
950 test_signed_jwt_token_type, // subject_token_type
951 nullptr, // actor_token_path
952 nullptr // actor_token_type
953 };
954 absl::StatusOr<URI> url_should_be_invalid =
955 ValidateStsCredentialsOptions(&invalid_options);
956 CHECK(!url_should_be_invalid.ok());
957
958 invalid_options = {
959 test_sts_endpoint_url, // sts_endpoint_url
960 nullptr, // resource
961 nullptr, // audience
962 nullptr, // scope
963 nullptr, // requested_token_type
964 test_signed_jwt_path_prefix, // subject_token_path
965 nullptr, // subject_token_type (Required)
966 nullptr, // actor_token_path
967 nullptr // actor_token_type
968 };
969 url_should_be_invalid = ValidateStsCredentialsOptions(&invalid_options);
970 CHECK(!url_should_be_invalid.ok());
971
972 invalid_options = {
973 nullptr, // sts_endpoint_url (Required)
974 nullptr, // resource
975 nullptr, // audience
976 nullptr, // scope
977 nullptr, // requested_token_type
978 test_signed_jwt_path_prefix, // subject_token_path
979 test_signed_jwt_token_type, // subject_token_type (Required)
980 nullptr, // actor_token_path
981 nullptr // actor_token_type
982 };
983 url_should_be_invalid = ValidateStsCredentialsOptions(&invalid_options);
984 CHECK(!url_should_be_invalid.ok());
985
986 invalid_options = {
987 "not_a_valid_uri", // sts_endpoint_url
988 nullptr, // resource
989 nullptr, // audience
990 nullptr, // scope
991 nullptr, // requested_token_type
992 test_signed_jwt_path_prefix, // subject_token_path
993 test_signed_jwt_token_type, // subject_token_type (Required)
994 nullptr, // actor_token_path
995 nullptr // actor_token_type
996 };
997 url_should_be_invalid = ValidateStsCredentialsOptions(&invalid_options);
998 CHECK(!url_should_be_invalid.ok());
999
1000 invalid_options = {
1001 "ftp://ftp.is.not.a.valid.scheme/bar", // sts_endpoint_url
1002 nullptr, // resource
1003 nullptr, // audience
1004 nullptr, // scope
1005 nullptr, // requested_token_type
1006 test_signed_jwt_path_prefix, // subject_token_path
1007 test_signed_jwt_token_type, // subject_token_type (Required)
1008 nullptr, // actor_token_path
1009 nullptr // actor_token_type
1010 };
1011 url_should_be_invalid = ValidateStsCredentialsOptions(&invalid_options);
1012 CHECK(!url_should_be_invalid.ok());
1013 }
1014
assert_query_parameters(const URI & uri,absl::string_view expected_key,absl::string_view expected_val)1015 void assert_query_parameters(const URI& uri, absl::string_view expected_key,
1016 absl::string_view expected_val) {
1017 const auto it = uri.query_parameter_map().find(expected_key);
1018 CHECK(it != uri.query_parameter_map().end());
1019 if (it->second != expected_val) {
1020 LOG(ERROR) << it->second << "!=" << expected_val;
1021 }
1022 CHECK(it->second == expected_val);
1023 }
1024
validate_sts_token_http_request(const grpc_http_request * request,const URI & uri,absl::string_view body,bool expect_actor_token)1025 void validate_sts_token_http_request(const grpc_http_request* request,
1026 const URI& uri, absl::string_view body,
1027 bool expect_actor_token) {
1028 // Check that the body is constructed properly.
1029 std::string get_url_equivalent =
1030 absl::StrFormat("%s?%s", test_sts_endpoint_url, body);
1031 absl::StatusOr<URI> url = URI::Parse(get_url_equivalent);
1032 if (!url.ok()) {
1033 LOG(ERROR) << url.status();
1034 CHECK_OK(url);
1035 }
1036 assert_query_parameters(*url, "resource", "resource");
1037 assert_query_parameters(*url, "audience", "audience");
1038 assert_query_parameters(*url, "scope", "scope");
1039 assert_query_parameters(*url, "requested_token_type", "requested_token_type");
1040 assert_query_parameters(*url, "subject_token", test_signed_jwt);
1041 assert_query_parameters(*url, "subject_token_type",
1042 test_signed_jwt_token_type);
1043 if (expect_actor_token) {
1044 assert_query_parameters(*url, "actor_token", test_signed_jwt2);
1045 assert_query_parameters(*url, "actor_token_type",
1046 test_signed_jwt_token_type2);
1047 } else {
1048 CHECK(url->query_parameter_map().find("actor_token") ==
1049 url->query_parameter_map().end());
1050 CHECK(url->query_parameter_map().find("actor_token_type") ==
1051 url->query_parameter_map().end());
1052 }
1053
1054 // Check the rest of the request.
1055 EXPECT_EQ(uri.authority(), "foo.com:5555");
1056 EXPECT_EQ(uri.path(), "/v1/token-exchange");
1057 ASSERT_EQ(request->hdr_count, 1);
1058 EXPECT_EQ(absl::string_view(request->hdrs[0].key), "Content-Type");
1059 EXPECT_EQ(absl::string_view(request->hdrs[0].value),
1060 "application/x-www-form-urlencoded");
1061 }
1062
sts_token_httpcli_post_success(const grpc_http_request * request,const URI & uri,absl::string_view body,Timestamp,grpc_closure * on_done,grpc_http_response * response)1063 int sts_token_httpcli_post_success(const grpc_http_request* request,
1064 const URI& uri, absl::string_view body,
1065 Timestamp /*deadline*/,
1066 grpc_closure* on_done,
1067 grpc_http_response* response) {
1068 validate_sts_token_http_request(request, uri, body, true);
1069 *response = http_response(200, valid_sts_json_response);
1070 ExecCtx::Run(DEBUG_LOCATION, on_done, absl::OkStatus());
1071 return 1;
1072 }
1073
sts_token_httpcli_post_success_no_actor_token(const grpc_http_request * request,const URI & uri,absl::string_view body,Timestamp,grpc_closure * on_done,grpc_http_response * response)1074 int sts_token_httpcli_post_success_no_actor_token(
1075 const grpc_http_request* request, const URI& uri, absl::string_view body,
1076 Timestamp /*deadline*/, grpc_closure* on_done,
1077 grpc_http_response* response) {
1078 validate_sts_token_http_request(request, uri, body, false);
1079 *response = http_response(200, valid_sts_json_response);
1080 ExecCtx::Run(DEBUG_LOCATION, on_done, absl::OkStatus());
1081 return 1;
1082 }
1083
write_tmp_jwt_file(const char * jwt_contents)1084 char* write_tmp_jwt_file(const char* jwt_contents) {
1085 char* path;
1086 FILE* tmp = gpr_tmpfile(test_signed_jwt_path_prefix, &path);
1087 CHECK_NE(path, nullptr);
1088 CHECK_NE(tmp, nullptr);
1089 size_t jwt_length = strlen(jwt_contents);
1090 CHECK_EQ(fwrite(jwt_contents, 1, jwt_length, tmp), jwt_length);
1091 fclose(tmp);
1092 return path;
1093 }
1094
TEST_F(CredentialsTest,TestStsCredsSuccess)1095 TEST_F(CredentialsTest, TestStsCredsSuccess) {
1096 ExecCtx exec_ctx;
1097 std::string emd = "authorization: Bearer ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_";
1098 const char expected_creds_debug_string[] =
1099 "StsTokenFetcherCredentials{Path:/v1/"
1100 "token-exchange,Authority:foo.com:5555,OAuth2TokenFetcherCredentials}";
1101 char* subject_token_path = write_tmp_jwt_file(test_signed_jwt);
1102 char* actor_token_path = write_tmp_jwt_file(test_signed_jwt2);
1103 grpc_sts_credentials_options valid_options = {
1104 test_sts_endpoint_url, // sts_endpoint_url
1105 "resource", // resource
1106 "audience", // audience
1107 "scope", // scope
1108 "requested_token_type", // requested_token_type
1109 subject_token_path, // subject_token_path
1110 test_signed_jwt_token_type, // subject_token_type
1111 actor_token_path, // actor_token_path
1112 test_signed_jwt_token_type2 // actor_token_type
1113 };
1114 grpc_call_credentials* creds =
1115 grpc_sts_credentials_create(&valid_options, nullptr);
1116
1117 // Check security level.
1118 CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
1119
1120 // First request: http put should be called.
1121 auto state = RequestMetadataState::NewInstance(absl::OkStatus(), emd);
1122 HttpRequest::SetOverride(httpcli_get_should_not_be_called,
1123 sts_token_httpcli_post_success,
1124 httpcli_put_should_not_be_called);
1125 state->RunRequestMetadataTest(creds, kTestUrlScheme, kTestAuthority,
1126 kTestPath);
1127 ExecCtx::Get()->Flush();
1128
1129 // Second request: the cached token should be served directly.
1130 state = RequestMetadataState::NewInstance(absl::OkStatus(), emd);
1131 HttpRequest::SetOverride(httpcli_get_should_not_be_called,
1132 httpcli_post_should_not_be_called,
1133 httpcli_put_should_not_be_called);
1134 state->RunRequestMetadataTest(creds, kTestUrlScheme, kTestAuthority,
1135 kTestPath);
1136 ExecCtx::Get()->Flush();
1137 CHECK_EQ(strcmp(creds->debug_string().c_str(), expected_creds_debug_string),
1138 0);
1139
1140 creds->Unref();
1141 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
1142 gpr_free(subject_token_path);
1143 gpr_free(actor_token_path);
1144 }
1145
TEST_F(CredentialsTest,TestStsCredsTokenFileNotFound)1146 TEST_F(CredentialsTest, TestStsCredsTokenFileNotFound) {
1147 ExecCtx exec_ctx;
1148 grpc_sts_credentials_options valid_options = {
1149 test_sts_endpoint_url, // sts_endpoint_url
1150 "resource", // resource
1151 "audience", // audience
1152 "scope", // scope
1153 "requested_token_type", // requested_token_type
1154 "/some/completely/random/path", // subject_token_path
1155 test_signed_jwt_token_type, // subject_token_type
1156 "", // actor_token_path
1157 "" // actor_token_type
1158 };
1159 grpc_call_credentials* creds =
1160 grpc_sts_credentials_create(&valid_options, nullptr);
1161
1162 // Check security level.
1163 CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
1164
1165 auto state = RequestMetadataState::NewInstance(
1166 // TODO(roth): This should return UNAVAILABLE.
1167 absl::InternalError(
1168 "Failed to load file: /some/completely/random/path due to "
1169 "error(fdopen): No such file or directory"),
1170 {});
1171 HttpRequest::SetOverride(httpcli_get_should_not_be_called,
1172 httpcli_post_should_not_be_called,
1173 httpcli_put_should_not_be_called);
1174 state->RunRequestMetadataTest(creds, kTestUrlScheme, kTestAuthority,
1175 kTestPath);
1176 ExecCtx::Get()->Flush();
1177
1178 // Cleanup.
1179 creds->Unref();
1180 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
1181 }
1182
TEST_F(CredentialsTest,TestStsCredsNoActorTokenSuccess)1183 TEST_F(CredentialsTest, TestStsCredsNoActorTokenSuccess) {
1184 ExecCtx exec_ctx;
1185 std::string emd = "authorization: Bearer ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_";
1186 const char expected_creds_debug_string[] =
1187 "StsTokenFetcherCredentials{Path:/v1/"
1188 "token-exchange,Authority:foo.com:5555,OAuth2TokenFetcherCredentials}";
1189 char* subject_token_path = write_tmp_jwt_file(test_signed_jwt);
1190 grpc_sts_credentials_options valid_options = {
1191 test_sts_endpoint_url, // sts_endpoint_url
1192 "resource", // resource
1193 "audience", // audience
1194 "scope", // scope
1195 "requested_token_type", // requested_token_type
1196 subject_token_path, // subject_token_path
1197 test_signed_jwt_token_type, // subject_token_type
1198 "", // actor_token_path
1199 "" // actor_token_type
1200 };
1201 grpc_call_credentials* creds =
1202 grpc_sts_credentials_create(&valid_options, nullptr);
1203
1204 // Check security level.
1205 CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
1206
1207 // First request: http put should be called.
1208 auto state = RequestMetadataState::NewInstance(absl::OkStatus(), emd);
1209 HttpRequest::SetOverride(httpcli_get_should_not_be_called,
1210 sts_token_httpcli_post_success_no_actor_token,
1211 httpcli_put_should_not_be_called);
1212 state->RunRequestMetadataTest(creds, kTestUrlScheme, kTestAuthority,
1213 kTestPath);
1214 ExecCtx::Get()->Flush();
1215
1216 // Second request: the cached token should be served directly.
1217 state = RequestMetadataState::NewInstance(absl::OkStatus(), emd);
1218 HttpRequest::SetOverride(httpcli_get_should_not_be_called,
1219 httpcli_post_should_not_be_called,
1220 httpcli_put_should_not_be_called);
1221 state->RunRequestMetadataTest(creds, kTestUrlScheme, kTestAuthority,
1222 kTestPath);
1223 ExecCtx::Get()->Flush();
1224 CHECK_EQ(strcmp(creds->debug_string().c_str(), expected_creds_debug_string),
1225 0);
1226
1227 creds->Unref();
1228 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
1229 gpr_free(subject_token_path);
1230 }
1231
TEST_F(CredentialsTest,TestStsCredsLoadTokenFailure)1232 TEST_F(CredentialsTest, TestStsCredsLoadTokenFailure) {
1233 const char expected_creds_debug_string[] =
1234 "StsTokenFetcherCredentials{Path:/v1/"
1235 "token-exchange,Authority:foo.com:5555,OAuth2TokenFetcherCredentials}";
1236 ExecCtx exec_ctx;
1237 auto state = RequestMetadataState::NewInstance(
1238 // TODO(roth): This should return UNAVAILABLE.
1239 absl::InternalError("Failed to load file: invalid_path due to "
1240 "error(fdopen): No such file or directory"),
1241 {});
1242 char* test_signed_jwt_path = write_tmp_jwt_file(test_signed_jwt);
1243 grpc_sts_credentials_options options = {
1244 test_sts_endpoint_url, // sts_endpoint_url
1245 "resource", // resource
1246 "audience", // audience
1247 "scope", // scope
1248 "requested_token_type", // requested_token_type
1249 "invalid_path", // subject_token_path
1250 test_signed_jwt_token_type, // subject_token_type
1251 nullptr, // actor_token_path
1252 nullptr // actor_token_type
1253 };
1254 grpc_call_credentials* creds = grpc_sts_credentials_create(&options, nullptr);
1255 HttpRequest::SetOverride(httpcli_get_should_not_be_called,
1256 httpcli_post_should_not_be_called,
1257 httpcli_put_should_not_be_called);
1258 state->RunRequestMetadataTest(creds, kTestUrlScheme, kTestAuthority,
1259 kTestPath);
1260 CHECK_EQ(strcmp(creds->debug_string().c_str(), expected_creds_debug_string),
1261 0);
1262
1263 creds->Unref();
1264 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
1265 gpr_free(test_signed_jwt_path);
1266 }
1267
TEST_F(CredentialsTest,TestStsCredsHttpFailure)1268 TEST_F(CredentialsTest, TestStsCredsHttpFailure) {
1269 const char expected_creds_debug_string[] =
1270 "StsTokenFetcherCredentials{Path:/v1/"
1271 "token-exchange,Authority:foo.com:5555,OAuth2TokenFetcherCredentials}";
1272 ExecCtx exec_ctx;
1273 auto state = RequestMetadataState::NewInstance(
1274 // TODO(roth): This should return UNAUTHENTICATED.
1275 absl::UnavailableError("error parsing oauth2 token"), {});
1276 char* test_signed_jwt_path = write_tmp_jwt_file(test_signed_jwt);
1277 grpc_sts_credentials_options valid_options = {
1278 test_sts_endpoint_url, // sts_endpoint_url
1279 "resource", // resource
1280 "audience", // audience
1281 "scope", // scope
1282 "requested_token_type", // requested_token_type
1283 test_signed_jwt_path, // subject_token_path
1284 test_signed_jwt_token_type, // subject_token_type
1285 nullptr, // actor_token_path
1286 nullptr // actor_token_type
1287 };
1288 grpc_call_credentials* creds =
1289 grpc_sts_credentials_create(&valid_options, nullptr);
1290 HttpRequest::SetOverride(httpcli_get_should_not_be_called,
1291 token_httpcli_post_failure,
1292 httpcli_put_should_not_be_called);
1293 state->RunRequestMetadataTest(creds, kTestUrlScheme, kTestAuthority,
1294 kTestPath);
1295 CHECK_EQ(strcmp(creds->debug_string().c_str(), expected_creds_debug_string),
1296 0);
1297 creds->Unref();
1298 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
1299 gpr_free(test_signed_jwt_path);
1300 }
1301
validate_jwt_encode_and_sign_params(const grpc_auth_json_key * json_key,const char * scope,gpr_timespec token_lifetime)1302 void validate_jwt_encode_and_sign_params(const grpc_auth_json_key* json_key,
1303 const char* scope,
1304 gpr_timespec token_lifetime) {
1305 CHECK(grpc_auth_json_key_is_valid(json_key));
1306 CHECK_NE(json_key->private_key, nullptr);
1307 #if OPENSSL_VERSION_NUMBER < 0x30000000L
1308 CHECK(RSA_check_key(json_key->private_key));
1309 #else
1310 EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new(json_key->private_key, NULL);
1311 CHECK(EVP_PKEY_private_check(ctx));
1312 EVP_PKEY_CTX_free(ctx);
1313 #endif
1314 CHECK(json_key->type != nullptr &&
1315 strcmp(json_key->type, "service_account") == 0);
1316 CHECK(json_key->private_key_id != nullptr &&
1317 strcmp(json_key->private_key_id,
1318 "e6b5137873db8d2ef81e06a47289e6434ec8a165") == 0);
1319 CHECK(json_key->client_id != nullptr &&
1320 strcmp(json_key->client_id,
1321 "777-abaslkan11hlb6nmim3bpspl31ud.apps."
1322 "googleusercontent.com") == 0);
1323 CHECK(json_key->client_email != nullptr &&
1324 strcmp(json_key->client_email,
1325 "777-abaslkan11hlb6nmim3bpspl31ud@developer."
1326 "gserviceaccount.com") == 0);
1327 if (scope != nullptr) CHECK_EQ(strcmp(scope, test_scope), 0);
1328 CHECK_EQ(gpr_time_cmp(token_lifetime, grpc_max_auth_token_lifetime()), 0);
1329 }
1330
encode_and_sign_jwt_success(const grpc_auth_json_key * json_key,const char * audience,gpr_timespec token_lifetime,const char * scope)1331 char* encode_and_sign_jwt_success(const grpc_auth_json_key* json_key,
1332 const char* audience,
1333 gpr_timespec token_lifetime,
1334 const char* scope) {
1335 if (strcmp(audience, test_service_url_no_service_name) != 0 &&
1336 strcmp(audience, other_test_service_url_no_service_name) != 0) {
1337 return nullptr;
1338 }
1339 validate_jwt_encode_and_sign_params(json_key, scope, token_lifetime);
1340 return gpr_strdup(test_signed_jwt);
1341 }
1342
encode_and_sign_jwt_failure(const grpc_auth_json_key * json_key,const char *,gpr_timespec token_lifetime,const char * scope)1343 char* encode_and_sign_jwt_failure(const grpc_auth_json_key* json_key,
1344 const char* /*audience*/,
1345 gpr_timespec token_lifetime,
1346 const char* scope) {
1347 validate_jwt_encode_and_sign_params(json_key, scope, token_lifetime);
1348 return nullptr;
1349 }
1350
encode_and_sign_jwt_should_not_be_called(const grpc_auth_json_key *,const char *,gpr_timespec,const char *)1351 char* encode_and_sign_jwt_should_not_be_called(
1352 const grpc_auth_json_key* /*json_key*/, const char* /*audience*/,
1353 gpr_timespec /*token_lifetime*/, const char* /*scope*/) {
1354 CHECK_EQ("grpc_jwt_encode_and_sign should not be called", nullptr);
1355 return nullptr;
1356 }
1357
creds_as_jwt(grpc_call_credentials * creds)1358 grpc_service_account_jwt_access_credentials* creds_as_jwt(
1359 grpc_call_credentials* creds) {
1360 CHECK(creds != nullptr);
1361 CHECK(creds->type() == grpc_service_account_jwt_access_credentials::Type());
1362 return reinterpret_cast<grpc_service_account_jwt_access_credentials*>(creds);
1363 }
1364
TEST_F(CredentialsTest,TestJwtCredsLifetime)1365 TEST_F(CredentialsTest, TestJwtCredsLifetime) {
1366 char* json_key_string = test_json_key_str();
1367 const char expected_creds_debug_string_prefix[] =
1368 "JWTAccessCredentials{ExpirationTime:";
1369 // Max lifetime.
1370 grpc_call_credentials* jwt_creds =
1371 grpc_service_account_jwt_access_credentials_create(
1372 json_key_string, grpc_max_auth_token_lifetime(), nullptr);
1373 CHECK_EQ(gpr_time_cmp(creds_as_jwt(jwt_creds)->jwt_lifetime(),
1374 grpc_max_auth_token_lifetime()),
1375 0);
1376 // Check security level.
1377 CHECK_EQ(jwt_creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
1378 CHECK_EQ(strncmp(expected_creds_debug_string_prefix,
1379 jwt_creds->debug_string().c_str(),
1380 strlen(expected_creds_debug_string_prefix)),
1381 0);
1382 grpc_call_credentials_release(jwt_creds);
1383
1384 // Shorter lifetime.
1385 gpr_timespec token_lifetime = {10, 0, GPR_TIMESPAN};
1386 CHECK_GT(gpr_time_cmp(grpc_max_auth_token_lifetime(), token_lifetime), 0);
1387 jwt_creds = grpc_service_account_jwt_access_credentials_create(
1388 json_key_string, token_lifetime, nullptr);
1389 CHECK_EQ(
1390 gpr_time_cmp(creds_as_jwt(jwt_creds)->jwt_lifetime(), token_lifetime), 0);
1391 CHECK_EQ(strncmp(expected_creds_debug_string_prefix,
1392 jwt_creds->debug_string().c_str(),
1393 strlen(expected_creds_debug_string_prefix)),
1394 0);
1395 grpc_call_credentials_release(jwt_creds);
1396
1397 // Cropped lifetime.
1398 gpr_timespec add_to_max = {10, 0, GPR_TIMESPAN};
1399 token_lifetime = gpr_time_add(grpc_max_auth_token_lifetime(), add_to_max);
1400 jwt_creds = grpc_service_account_jwt_access_credentials_create(
1401 json_key_string, token_lifetime, nullptr);
1402 CHECK(gpr_time_cmp(creds_as_jwt(jwt_creds)->jwt_lifetime(),
1403 grpc_max_auth_token_lifetime()) == 0);
1404 CHECK(strncmp(expected_creds_debug_string_prefix,
1405 jwt_creds->debug_string().c_str(),
1406 strlen(expected_creds_debug_string_prefix)) == 0);
1407 grpc_call_credentials_release(jwt_creds);
1408
1409 gpr_free(json_key_string);
1410 }
1411
TEST_F(CredentialsTest,TestRemoveServiceFromJwtUri)1412 TEST_F(CredentialsTest, TestRemoveServiceFromJwtUri) {
1413 const char wrong_uri[] = "hello world";
1414 CHECK(!RemoveServiceNameFromJwtUri(wrong_uri).ok());
1415 const char valid_uri[] = "https://foo.com/get/";
1416 const char expected_uri[] = "https://foo.com/";
1417 auto output = RemoveServiceNameFromJwtUri(valid_uri);
1418 CHECK_OK(output);
1419 CHECK_EQ(strcmp(output->c_str(), expected_uri), 0);
1420 }
1421
TEST_F(CredentialsTest,TestJwtCredsSuccess)1422 TEST_F(CredentialsTest, TestJwtCredsSuccess) {
1423 const char expected_creds_debug_string_prefix[] =
1424 "JWTAccessCredentials{ExpirationTime:";
1425
1426 char* json_key_string = test_json_key_str();
1427 ExecCtx exec_ctx;
1428 std::string expected_md_value = absl::StrCat("Bearer ", test_signed_jwt);
1429 std::string emd = absl::StrCat("authorization: ", expected_md_value);
1430 grpc_call_credentials* creds =
1431 grpc_service_account_jwt_access_credentials_create(
1432 json_key_string, grpc_max_auth_token_lifetime(), nullptr);
1433
1434 // First request: jwt_encode_and_sign should be called.
1435 auto state = RequestMetadataState::NewInstance(absl::OkStatus(), emd);
1436 grpc_jwt_encode_and_sign_set_override(encode_and_sign_jwt_success);
1437 state->RunRequestMetadataTest(creds, kTestUrlScheme, kTestAuthority,
1438 kTestPath);
1439 ExecCtx::Get()->Flush();
1440
1441 // Second request: the cached token should be served directly.
1442 state = RequestMetadataState::NewInstance(absl::OkStatus(), emd);
1443 grpc_jwt_encode_and_sign_set_override(
1444 encode_and_sign_jwt_should_not_be_called);
1445 state->RunRequestMetadataTest(creds, kTestUrlScheme, kTestAuthority,
1446 kTestPath);
1447 ExecCtx::Get()->Flush();
1448
1449 // Third request: Different service url so jwt_encode_and_sign should be
1450 // called again (no caching).
1451 state = RequestMetadataState::NewInstance(absl::OkStatus(), emd);
1452 grpc_jwt_encode_and_sign_set_override(encode_and_sign_jwt_success);
1453 state->RunRequestMetadataTest(creds, kTestUrlScheme, kTestOtherAuthority,
1454 kTestOtherPath);
1455 ExecCtx::Get()->Flush();
1456 CHECK_EQ(
1457 strncmp(expected_creds_debug_string_prefix, creds->debug_string().c_str(),
1458 strlen(expected_creds_debug_string_prefix)),
1459 0);
1460
1461 creds->Unref();
1462 gpr_free(json_key_string);
1463 grpc_jwt_encode_and_sign_set_override(nullptr);
1464 }
1465
TEST_F(CredentialsTest,TestJwtCredsSigningFailure)1466 TEST_F(CredentialsTest, TestJwtCredsSigningFailure) {
1467 const char expected_creds_debug_string_prefix[] =
1468 "JWTAccessCredentials{ExpirationTime:";
1469 char* json_key_string = test_json_key_str();
1470 ExecCtx exec_ctx;
1471 auto state = RequestMetadataState::NewInstance(
1472 absl::UnauthenticatedError("Could not generate JWT."), {});
1473 grpc_call_credentials* creds =
1474 grpc_service_account_jwt_access_credentials_create(
1475 json_key_string, grpc_max_auth_token_lifetime(), nullptr);
1476
1477 grpc_jwt_encode_and_sign_set_override(encode_and_sign_jwt_failure);
1478 state->RunRequestMetadataTest(creds, kTestUrlScheme, kTestAuthority,
1479 kTestPath);
1480
1481 gpr_free(json_key_string);
1482 CHECK_EQ(
1483 strncmp(expected_creds_debug_string_prefix, creds->debug_string().c_str(),
1484 strlen(expected_creds_debug_string_prefix)),
1485 0);
1486
1487 creds->Unref();
1488 grpc_jwt_encode_and_sign_set_override(nullptr);
1489 }
1490
set_google_default_creds_env_var_with_file_contents(const char * file_prefix,const char * contents)1491 void set_google_default_creds_env_var_with_file_contents(
1492 const char* file_prefix, const char* contents) {
1493 size_t contents_len = strlen(contents);
1494 char* creds_file_name;
1495 FILE* creds_file = gpr_tmpfile(file_prefix, &creds_file_name);
1496 CHECK_NE(creds_file_name, nullptr);
1497 CHECK_NE(creds_file, nullptr);
1498 CHECK_EQ(fwrite(contents, 1, contents_len, creds_file), contents_len);
1499 fclose(creds_file);
1500 SetEnv(GRPC_GOOGLE_CREDENTIALS_ENV_VAR, creds_file_name);
1501 gpr_free(creds_file_name);
1502 }
1503
test_gce_tenancy_checker(void)1504 bool test_gce_tenancy_checker(void) {
1505 g_test_gce_tenancy_checker_called = true;
1506 return g_test_is_on_gce;
1507 }
1508
null_well_known_creds_path_getter(void)1509 std::string null_well_known_creds_path_getter(void) { return ""; }
1510
TEST_F(CredentialsTest,TestGoogleDefaultCredsAuthKey)1511 TEST_F(CredentialsTest, TestGoogleDefaultCredsAuthKey) {
1512 ExecCtx exec_ctx;
1513 grpc_composite_channel_credentials* creds;
1514 char* json_key = test_json_key_str();
1515 grpc_flush_cached_google_default_credentials();
1516 set_gce_tenancy_checker_for_testing(test_gce_tenancy_checker);
1517 g_test_gce_tenancy_checker_called = false;
1518 g_test_is_on_gce = true;
1519 set_google_default_creds_env_var_with_file_contents(
1520 "json_key_google_default_creds", json_key);
1521 grpc_override_well_known_credentials_path_getter(
1522 null_well_known_creds_path_getter);
1523 gpr_free(json_key);
1524 creds = reinterpret_cast<grpc_composite_channel_credentials*>(
1525 grpc_google_default_credentials_create(nullptr));
1526 auto* default_creds =
1527 reinterpret_cast<const grpc_google_default_channel_credentials*>(
1528 creds->inner_creds());
1529 CHECK_NE(default_creds->ssl_creds(), nullptr);
1530 auto* jwt =
1531 reinterpret_cast<const grpc_service_account_jwt_access_credentials*>(
1532 creds->call_creds());
1533 CHECK_EQ(
1534 strcmp(jwt->key().client_id,
1535 "777-abaslkan11hlb6nmim3bpspl31ud.apps.googleusercontent.com"),
1536 0);
1537 CHECK_EQ(g_test_gce_tenancy_checker_called, false);
1538 creds->Unref();
1539 SetEnv(GRPC_GOOGLE_CREDENTIALS_ENV_VAR, ""); // Reset.
1540 grpc_override_well_known_credentials_path_getter(nullptr);
1541 }
1542
TEST_F(CredentialsTest,TestGoogleDefaultCredsRefreshToken)1543 TEST_F(CredentialsTest, TestGoogleDefaultCredsRefreshToken) {
1544 ExecCtx exec_ctx;
1545 grpc_composite_channel_credentials* creds;
1546 grpc_flush_cached_google_default_credentials();
1547 set_google_default_creds_env_var_with_file_contents(
1548 "refresh_token_google_default_creds", test_refresh_token_str);
1549 grpc_override_well_known_credentials_path_getter(
1550 null_well_known_creds_path_getter);
1551 creds = reinterpret_cast<grpc_composite_channel_credentials*>(
1552 grpc_google_default_credentials_create(nullptr));
1553 auto* default_creds =
1554 reinterpret_cast<const grpc_google_default_channel_credentials*>(
1555 creds->inner_creds());
1556 CHECK_NE(default_creds->ssl_creds(), nullptr);
1557 auto* refresh =
1558 reinterpret_cast<const grpc_google_refresh_token_credentials*>(
1559 creds->call_creds());
1560 CHECK_EQ(strcmp(refresh->refresh_token().client_id,
1561 "32555999999.apps.googleusercontent.com"),
1562 0);
1563 creds->Unref();
1564 SetEnv(GRPC_GOOGLE_CREDENTIALS_ENV_VAR, ""); // Reset.
1565 grpc_override_well_known_credentials_path_getter(nullptr);
1566 }
1567
TEST_F(CredentialsTest,TestGoogleDefaultCredsExternalAccountCredentialsPscSts)1568 TEST_F(CredentialsTest,
1569 TestGoogleDefaultCredsExternalAccountCredentialsPscSts) {
1570 ExecCtx exec_ctx;
1571 grpc_composite_channel_credentials* creds;
1572 grpc_flush_cached_google_default_credentials();
1573 set_google_default_creds_env_var_with_file_contents(
1574 "google_default_creds_external_account_credentials_psc_sts",
1575 test_external_account_credentials_psc_sts_str);
1576 grpc_override_well_known_credentials_path_getter(
1577 null_well_known_creds_path_getter);
1578 creds = reinterpret_cast<grpc_composite_channel_credentials*>(
1579 grpc_google_default_credentials_create(nullptr));
1580 auto* default_creds =
1581 reinterpret_cast<const grpc_google_default_channel_credentials*>(
1582 creds->inner_creds());
1583 CHECK_NE(default_creds->ssl_creds(), nullptr);
1584 auto* external =
1585 reinterpret_cast<const ExternalAccountCredentials*>(creds->call_creds());
1586 CHECK_NE(external, nullptr);
1587 creds->Unref();
1588 SetEnv(GRPC_GOOGLE_CREDENTIALS_ENV_VAR, ""); // Reset.
1589 grpc_override_well_known_credentials_path_getter(nullptr);
1590 }
1591
TEST_F(CredentialsTest,TestGoogleDefaultCredsExternalAccountCredentialsPscIam)1592 TEST_F(CredentialsTest,
1593 TestGoogleDefaultCredsExternalAccountCredentialsPscIam) {
1594 ExecCtx exec_ctx;
1595 grpc_composite_channel_credentials* creds;
1596 grpc_flush_cached_google_default_credentials();
1597 set_google_default_creds_env_var_with_file_contents(
1598 "google_default_creds_external_account_credentials_psc_iam",
1599 test_external_account_credentials_psc_iam_str);
1600 grpc_override_well_known_credentials_path_getter(
1601 null_well_known_creds_path_getter);
1602 creds = reinterpret_cast<grpc_composite_channel_credentials*>(
1603 grpc_google_default_credentials_create(nullptr));
1604 auto* default_creds =
1605 reinterpret_cast<const grpc_google_default_channel_credentials*>(
1606 creds->inner_creds());
1607 CHECK_NE(default_creds->ssl_creds(), nullptr);
1608 auto* external =
1609 reinterpret_cast<const ExternalAccountCredentials*>(creds->call_creds());
1610 CHECK_NE(external, nullptr);
1611 creds->Unref();
1612 SetEnv(GRPC_GOOGLE_CREDENTIALS_ENV_VAR, ""); // Reset.
1613 grpc_override_well_known_credentials_path_getter(nullptr);
1614 }
1615
default_creds_metadata_server_detection_httpcli_get_success_override(const grpc_http_request *,const URI & uri,Timestamp,grpc_closure * on_done,grpc_http_response * response)1616 int default_creds_metadata_server_detection_httpcli_get_success_override(
1617 const grpc_http_request* /*request*/, const URI& uri,
1618 Timestamp /*deadline*/, grpc_closure* on_done,
1619 grpc_http_response* response) {
1620 *response = http_response(200, "");
1621 grpc_http_header* headers =
1622 static_cast<grpc_http_header*>(gpr_malloc(sizeof(*headers) * 1));
1623 headers[0].key = gpr_strdup("Metadata-Flavor");
1624 headers[0].value = gpr_strdup("Google");
1625 response->hdr_count = 1;
1626 response->hdrs = headers;
1627 EXPECT_EQ(uri.path(), "/");
1628 EXPECT_EQ(uri.authority(), "metadata.google.internal.");
1629 ExecCtx::Run(DEBUG_LOCATION, on_done, absl::OkStatus());
1630 return 1;
1631 }
1632
TEST_F(CredentialsTest,TestGoogleDefaultCredsGce)1633 TEST_F(CredentialsTest, TestGoogleDefaultCredsGce) {
1634 ExecCtx exec_ctx;
1635 auto state = RequestMetadataState::NewInstance(
1636 absl::OkStatus(),
1637 "authorization: Bearer ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_");
1638 grpc_flush_cached_google_default_credentials();
1639 SetEnv(GRPC_GOOGLE_CREDENTIALS_ENV_VAR, ""); // Reset.
1640 grpc_override_well_known_credentials_path_getter(
1641 null_well_known_creds_path_getter);
1642 set_gce_tenancy_checker_for_testing(test_gce_tenancy_checker);
1643 g_test_gce_tenancy_checker_called = false;
1644 g_test_is_on_gce = true;
1645
1646 // Simulate a successful detection of GCE.
1647 grpc_composite_channel_credentials* creds =
1648 reinterpret_cast<grpc_composite_channel_credentials*>(
1649 grpc_google_default_credentials_create(nullptr));
1650
1651 // Verify that the default creds actually embeds a GCE creds.
1652 CHECK(creds != nullptr);
1653 CHECK_NE(creds->call_creds(), nullptr);
1654 HttpRequest::SetOverride(compute_engine_httpcli_get_success_override,
1655 httpcli_post_should_not_be_called,
1656 httpcli_put_should_not_be_called);
1657 state->RunRequestMetadataTest(creds->mutable_call_creds(), kTestUrlScheme,
1658 kTestAuthority, kTestPath);
1659 ExecCtx::Get()->Flush();
1660
1661 CHECK_EQ(g_test_gce_tenancy_checker_called, true);
1662
1663 // Cleanup.
1664 creds->Unref();
1665 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
1666 grpc_override_well_known_credentials_path_getter(nullptr);
1667 }
1668
TEST_F(CredentialsTest,TestGoogleDefaultCredsNonGce)1669 TEST_F(CredentialsTest, TestGoogleDefaultCredsNonGce) {
1670 ExecCtx exec_ctx;
1671 auto state = RequestMetadataState::NewInstance(
1672 absl::OkStatus(),
1673 "authorization: Bearer ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_");
1674 grpc_flush_cached_google_default_credentials();
1675 SetEnv(GRPC_GOOGLE_CREDENTIALS_ENV_VAR, ""); // Reset.
1676 grpc_override_well_known_credentials_path_getter(
1677 null_well_known_creds_path_getter);
1678 set_gce_tenancy_checker_for_testing(test_gce_tenancy_checker);
1679 g_test_gce_tenancy_checker_called = false;
1680 g_test_is_on_gce = false;
1681 // Simulate a successful detection of metadata server.
1682 HttpRequest::SetOverride(
1683 default_creds_metadata_server_detection_httpcli_get_success_override,
1684 httpcli_post_should_not_be_called, httpcli_put_should_not_be_called);
1685 grpc_composite_channel_credentials* creds =
1686 reinterpret_cast<grpc_composite_channel_credentials*>(
1687 grpc_google_default_credentials_create(nullptr));
1688 // Verify that the default creds actually embeds a GCE creds.
1689 CHECK(creds != nullptr);
1690 CHECK_NE(creds->call_creds(), nullptr);
1691 HttpRequest::SetOverride(compute_engine_httpcli_get_success_override,
1692 httpcli_post_should_not_be_called,
1693 httpcli_put_should_not_be_called);
1694 state->RunRequestMetadataTest(creds->mutable_call_creds(), kTestUrlScheme,
1695 kTestAuthority, kTestPath);
1696 ExecCtx::Get()->Flush();
1697 CHECK_EQ(g_test_gce_tenancy_checker_called, true);
1698 // Cleanup.
1699 creds->Unref();
1700 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
1701 grpc_override_well_known_credentials_path_getter(nullptr);
1702 }
1703
default_creds_gce_detection_httpcli_get_failure_override(const grpc_http_request *,const URI & uri,Timestamp,grpc_closure * on_done,grpc_http_response * response)1704 int default_creds_gce_detection_httpcli_get_failure_override(
1705 const grpc_http_request* /*request*/, const URI& uri,
1706 Timestamp /*deadline*/, grpc_closure* on_done,
1707 grpc_http_response* response) {
1708 // No magic header.
1709 EXPECT_EQ(uri.path(), "/");
1710 EXPECT_EQ(uri.authority(), "metadata.google.internal.");
1711 *response = http_response(200, "");
1712 ExecCtx::Run(DEBUG_LOCATION, on_done, absl::OkStatus());
1713 return 1;
1714 }
1715
TEST_F(CredentialsTest,TestNoGoogleDefaultCreds)1716 TEST_F(CredentialsTest, TestNoGoogleDefaultCreds) {
1717 grpc_flush_cached_google_default_credentials();
1718 SetEnv(GRPC_GOOGLE_CREDENTIALS_ENV_VAR, ""); // Reset.
1719 grpc_override_well_known_credentials_path_getter(
1720 null_well_known_creds_path_getter);
1721 set_gce_tenancy_checker_for_testing(test_gce_tenancy_checker);
1722 g_test_gce_tenancy_checker_called = false;
1723 g_test_is_on_gce = false;
1724 HttpRequest::SetOverride(
1725 default_creds_gce_detection_httpcli_get_failure_override,
1726 httpcli_post_should_not_be_called, httpcli_put_should_not_be_called);
1727 // Simulate a successful detection of GCE.
1728 CHECK_EQ(grpc_google_default_credentials_create(nullptr), nullptr);
1729 // Try a second one. GCE detection should occur again.
1730 g_test_gce_tenancy_checker_called = false;
1731 CHECK_EQ(grpc_google_default_credentials_create(nullptr), nullptr);
1732 CHECK_EQ(g_test_gce_tenancy_checker_called, true);
1733 // Cleanup.
1734 grpc_override_well_known_credentials_path_getter(nullptr);
1735 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
1736 }
1737
TEST_F(CredentialsTest,TestGoogleDefaultCredsCallCredsSpecified)1738 TEST_F(CredentialsTest, TestGoogleDefaultCredsCallCredsSpecified) {
1739 auto state = RequestMetadataState::NewInstance(
1740 absl::OkStatus(),
1741 "authorization: Bearer ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_");
1742 ExecCtx exec_ctx;
1743 grpc_flush_cached_google_default_credentials();
1744 grpc_call_credentials* call_creds =
1745 grpc_google_compute_engine_credentials_create(nullptr);
1746 set_gce_tenancy_checker_for_testing(test_gce_tenancy_checker);
1747 g_test_gce_tenancy_checker_called = false;
1748 g_test_is_on_gce = true;
1749 HttpRequest::SetOverride(
1750 default_creds_metadata_server_detection_httpcli_get_success_override,
1751 httpcli_post_should_not_be_called, httpcli_put_should_not_be_called);
1752 grpc_composite_channel_credentials* channel_creds =
1753 reinterpret_cast<grpc_composite_channel_credentials*>(
1754 grpc_google_default_credentials_create(call_creds));
1755 CHECK_EQ(g_test_gce_tenancy_checker_called, false);
1756 CHECK_NE(channel_creds, nullptr);
1757 CHECK_NE(channel_creds->call_creds(), nullptr);
1758 HttpRequest::SetOverride(compute_engine_httpcli_get_success_override,
1759 httpcli_post_should_not_be_called,
1760 httpcli_put_should_not_be_called);
1761 state->RunRequestMetadataTest(channel_creds->mutable_call_creds(),
1762 kTestUrlScheme, kTestAuthority, kTestPath);
1763 ExecCtx::Get()->Flush();
1764 channel_creds->Unref();
1765 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
1766 }
1767
1768 struct fake_call_creds : public grpc_call_credentials {
1769 public:
Orphanedgrpc_core::__anon62eef70e0111::fake_call_creds1770 void Orphaned() override {}
1771
GetRequestMetadatagrpc_core::__anon62eef70e0111::fake_call_creds1772 ArenaPromise<absl::StatusOr<ClientMetadataHandle>> GetRequestMetadata(
1773 ClientMetadataHandle initial_metadata,
1774 const grpc_call_credentials::GetRequestMetadataArgs*) override {
1775 initial_metadata->Append("foo", Slice::FromStaticString("oof"),
1776 [](absl::string_view, const Slice&) { abort(); });
1777 return Immediate(std::move(initial_metadata));
1778 }
1779
typegrpc_core::__anon62eef70e0111::fake_call_creds1780 UniqueTypeName type() const override {
1781 static UniqueTypeName::Factory kFactory("fake");
1782 return kFactory.Create();
1783 }
1784
1785 private:
cmp_implgrpc_core::__anon62eef70e0111::fake_call_creds1786 int cmp_impl(const grpc_call_credentials* other) const override {
1787 // TODO(yashykt): Check if we can do something better here
1788 return QsortCompare(static_cast<const grpc_call_credentials*>(this), other);
1789 }
1790 };
1791
TEST_F(CredentialsTest,TestGoogleDefaultCredsNotDefault)1792 TEST_F(CredentialsTest, TestGoogleDefaultCredsNotDefault) {
1793 auto state = RequestMetadataState::NewInstance(absl::OkStatus(), "foo: oof");
1794 ExecCtx exec_ctx;
1795 grpc_flush_cached_google_default_credentials();
1796 RefCountedPtr<grpc_call_credentials> call_creds =
1797 MakeRefCounted<fake_call_creds>();
1798 set_gce_tenancy_checker_for_testing(test_gce_tenancy_checker);
1799 g_test_gce_tenancy_checker_called = false;
1800 g_test_is_on_gce = true;
1801 HttpRequest::SetOverride(
1802 default_creds_metadata_server_detection_httpcli_get_success_override,
1803 httpcli_post_should_not_be_called, httpcli_put_should_not_be_called);
1804 grpc_composite_channel_credentials* channel_creds =
1805 reinterpret_cast<grpc_composite_channel_credentials*>(
1806 grpc_google_default_credentials_create(call_creds.release()));
1807 CHECK_EQ(g_test_gce_tenancy_checker_called, false);
1808 CHECK_NE(channel_creds, nullptr);
1809 CHECK_NE(channel_creds->call_creds(), nullptr);
1810 state->RunRequestMetadataTest(channel_creds->mutable_call_creds(),
1811 kTestUrlScheme, kTestAuthority, kTestPath);
1812 ExecCtx::Get()->Flush();
1813 channel_creds->Unref();
1814 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
1815 }
1816
1817 typedef enum {
1818 PLUGIN_INITIAL_STATE,
1819 PLUGIN_GET_METADATA_CALLED_STATE,
1820 PLUGIN_DESTROY_CALLED_STATE
1821 } plugin_state;
1822
1823 const std::map<std::string, std::string> plugin_md = {{"foo", "bar"},
1824 {"hi", "there"}};
1825
plugin_get_metadata_success(void * state,grpc_auth_metadata_context context,grpc_credentials_plugin_metadata_cb,void *,grpc_metadata creds_md[GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX],size_t * num_creds_md,grpc_status_code *,const char **)1826 int plugin_get_metadata_success(
1827 void* state, grpc_auth_metadata_context context,
1828 grpc_credentials_plugin_metadata_cb /*cb*/, void* /*user_data*/,
1829 grpc_metadata creds_md[GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX],
1830 size_t* num_creds_md, grpc_status_code* /*status*/,
1831 const char** /*error_details*/) {
1832 CHECK_EQ(strcmp(context.service_url, test_service_url), 0);
1833 CHECK_EQ(strcmp(context.method_name, test_method), 0);
1834 CHECK_EQ(context.channel_auth_context, nullptr);
1835 CHECK_EQ(context.reserved, nullptr);
1836 CHECK_LT(plugin_md.size(), GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX);
1837 plugin_state* s = static_cast<plugin_state*>(state);
1838 *s = PLUGIN_GET_METADATA_CALLED_STATE;
1839 size_t i = 0;
1840 for (auto const& md : plugin_md) {
1841 memset(&creds_md[i], 0, sizeof(grpc_metadata));
1842 creds_md[i].key = grpc_slice_from_copied_string(md.first.c_str());
1843 creds_md[i].value = grpc_slice_from_copied_string(md.second.c_str());
1844 i += 1;
1845 }
1846 *num_creds_md = plugin_md.size();
1847 return true; // Synchronous return.
1848 }
1849
1850 const char* plugin_error_details = "Could not get metadata for plugin.";
1851
plugin_get_metadata_failure(void * state,grpc_auth_metadata_context context,grpc_credentials_plugin_metadata_cb,void *,grpc_metadata[GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX],size_t *,grpc_status_code * status,const char ** error_details)1852 int plugin_get_metadata_failure(
1853 void* state, grpc_auth_metadata_context context,
1854 grpc_credentials_plugin_metadata_cb /*cb*/, void* /*user_data*/,
1855 grpc_metadata /*creds_md*/[GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX],
1856 size_t* /*num_creds_md*/, grpc_status_code* status,
1857 const char** error_details) {
1858 CHECK_EQ(strcmp(context.service_url, test_service_url), 0);
1859 CHECK_EQ(strcmp(context.method_name, test_method), 0);
1860 CHECK_EQ(context.channel_auth_context, nullptr);
1861 CHECK_EQ(context.reserved, nullptr);
1862 plugin_state* s = static_cast<plugin_state*>(state);
1863 *s = PLUGIN_GET_METADATA_CALLED_STATE;
1864 *status = GRPC_STATUS_UNAUTHENTICATED;
1865 *error_details = gpr_strdup(plugin_error_details);
1866 return true; // Synchronous return.
1867 }
1868
plugin_destroy(void * state)1869 void plugin_destroy(void* state) {
1870 plugin_state* s = static_cast<plugin_state*>(state);
1871 *s = PLUGIN_DESTROY_CALLED_STATE;
1872 }
1873
plugin_debug_string(void * state)1874 char* plugin_debug_string(void* state) {
1875 plugin_state* s = static_cast<plugin_state*>(state);
1876 char* ret = nullptr;
1877 switch (*s) {
1878 case PLUGIN_INITIAL_STATE:
1879 gpr_asprintf(&ret, "TestPluginCredentials{state:INITIAL}");
1880 break;
1881 case PLUGIN_GET_METADATA_CALLED_STATE:
1882 gpr_asprintf(&ret, "TestPluginCredentials{state:GET_METADATA_CALLED}");
1883 break;
1884 case PLUGIN_DESTROY_CALLED_STATE:
1885 gpr_asprintf(&ret, "TestPluginCredentials{state:DESTROY}");
1886 break;
1887 default:
1888 gpr_asprintf(&ret, "TestPluginCredentials{state:UNKNOWN}");
1889 break;
1890 }
1891 return ret;
1892 }
1893
TEST_F(CredentialsTest,TestMetadataPluginSuccess)1894 TEST_F(CredentialsTest, TestMetadataPluginSuccess) {
1895 const char expected_creds_debug_string[] =
1896 "TestPluginCredentials{state:GET_METADATA_CALLED}";
1897 plugin_state state = PLUGIN_INITIAL_STATE;
1898 grpc_metadata_credentials_plugin plugin;
1899 ExecCtx exec_ctx;
1900 auto md_state = RequestMetadataState::NewInstance(absl::OkStatus(),
1901 "foo: bar, hi: there");
1902
1903 plugin.state = &state;
1904 plugin.get_metadata = plugin_get_metadata_success;
1905 plugin.destroy = plugin_destroy;
1906 plugin.debug_string = plugin_debug_string;
1907
1908 grpc_call_credentials* creds = grpc_metadata_credentials_create_from_plugin(
1909 plugin, GRPC_PRIVACY_AND_INTEGRITY, nullptr);
1910 // Check security level.
1911 CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
1912 CHECK_EQ(state, PLUGIN_INITIAL_STATE);
1913 md_state->RunRequestMetadataTest(creds, kTestUrlScheme, kTestAuthority,
1914 kTestPath);
1915 CHECK_EQ(state, PLUGIN_GET_METADATA_CALLED_STATE);
1916 CHECK_EQ(strcmp(creds->debug_string().c_str(), expected_creds_debug_string),
1917 0);
1918 creds->Unref();
1919
1920 CHECK_EQ(state, PLUGIN_DESTROY_CALLED_STATE);
1921 }
1922
TEST_F(CredentialsTest,TestMetadataPluginFailure)1923 TEST_F(CredentialsTest, TestMetadataPluginFailure) {
1924 const char expected_creds_debug_string[] =
1925 "TestPluginCredentials{state:GET_METADATA_CALLED}";
1926
1927 plugin_state state = PLUGIN_INITIAL_STATE;
1928 grpc_metadata_credentials_plugin plugin;
1929 ExecCtx exec_ctx;
1930 auto md_state = RequestMetadataState::NewInstance(
1931 // TODO(roth): Is this the right status to use here?
1932 absl::UnavailableError(
1933 absl::StrCat("Getting metadata from plugin failed with error: ",
1934 plugin_error_details)),
1935 {});
1936
1937 plugin.state = &state;
1938 plugin.get_metadata = plugin_get_metadata_failure;
1939 plugin.destroy = plugin_destroy;
1940 plugin.debug_string = plugin_debug_string;
1941
1942 grpc_call_credentials* creds = grpc_metadata_credentials_create_from_plugin(
1943 plugin, GRPC_PRIVACY_AND_INTEGRITY, nullptr);
1944 CHECK_EQ(state, PLUGIN_INITIAL_STATE);
1945 md_state->RunRequestMetadataTest(creds, kTestUrlScheme, kTestAuthority,
1946 kTestPath);
1947 CHECK_EQ(state, PLUGIN_GET_METADATA_CALLED_STATE);
1948 CHECK_EQ(strcmp(creds->debug_string().c_str(), expected_creds_debug_string),
1949 0);
1950 creds->Unref();
1951
1952 CHECK_EQ(state, PLUGIN_DESTROY_CALLED_STATE);
1953 }
1954
TEST_F(CredentialsTest,TestGetWellKnownGoogleCredentialsFilePath)1955 TEST_F(CredentialsTest, TestGetWellKnownGoogleCredentialsFilePath) {
1956 auto home = GetEnv("HOME");
1957 bool restore_home_env = false;
1958 #if defined(GRPC_BAZEL_BUILD) && \
1959 (defined(GPR_POSIX_ENV) || defined(GPR_LINUX_ENV))
1960 // when running under bazel locally, the HOME variable is not set
1961 // so we set it to some fake value
1962 restore_home_env = true;
1963 SetEnv("HOME", "/fake/home/for/bazel");
1964 #endif // defined(GRPC_BAZEL_BUILD) && (defined(GPR_POSIX_ENV) ||
1965 // defined(GPR_LINUX_ENV))
1966 std::string path = grpc_get_well_known_google_credentials_file_path();
1967 CHECK(!path.empty());
1968 #if defined(GPR_POSIX_ENV) || defined(GPR_LINUX_ENV)
1969 restore_home_env = true;
1970 UnsetEnv("HOME");
1971 path = grpc_get_well_known_google_credentials_file_path();
1972 CHECK(path.empty());
1973 #endif // GPR_POSIX_ENV || GPR_LINUX_ENV
1974 if (restore_home_env) {
1975 SetOrUnsetEnv("HOME", home);
1976 }
1977 }
1978
TEST_F(CredentialsTest,TestChannelCredsDuplicateWithoutCallCreds)1979 TEST_F(CredentialsTest, TestChannelCredsDuplicateWithoutCallCreds) {
1980 const char expected_creds_debug_string[] =
1981 "AccessTokenCredentials{Token:present}";
1982 ExecCtx exec_ctx;
1983
1984 grpc_channel_credentials* channel_creds =
1985 grpc_fake_transport_security_credentials_create();
1986
1987 RefCountedPtr<grpc_channel_credentials> dup =
1988 channel_creds->duplicate_without_call_credentials();
1989 CHECK(dup == channel_creds);
1990 dup.reset();
1991
1992 grpc_call_credentials* call_creds =
1993 grpc_access_token_credentials_create("blah", nullptr);
1994 grpc_channel_credentials* composite_creds =
1995 grpc_composite_channel_credentials_create(channel_creds, call_creds,
1996 nullptr);
1997 CHECK_EQ(
1998 strcmp(call_creds->debug_string().c_str(), expected_creds_debug_string),
1999 0);
2000
2001 call_creds->Unref();
2002 dup = composite_creds->duplicate_without_call_credentials();
2003 CHECK(dup == channel_creds);
2004 dup.reset();
2005
2006 channel_creds->Unref();
2007 composite_creds->Unref();
2008 }
2009
2010 typedef struct {
2011 const char* url_scheme;
2012 const char* call_host;
2013 const char* call_method;
2014 const char* desired_service_url;
2015 const char* desired_method_name;
2016 } auth_metadata_context_test_case;
2017
auth_metadata_context_build(const char * url_scheme,const grpc_slice & call_host,const grpc_slice & call_method,grpc_auth_context * auth_context,grpc_auth_metadata_context * auth_md_context)2018 void auth_metadata_context_build(const char* url_scheme,
2019 const grpc_slice& call_host,
2020 const grpc_slice& call_method,
2021 grpc_auth_context* auth_context,
2022 grpc_auth_metadata_context* auth_md_context) {
2023 char* service = grpc_slice_to_c_string(call_method);
2024 char* last_slash = strrchr(service, '/');
2025 char* method_name = nullptr;
2026 char* service_url = nullptr;
2027 grpc_auth_metadata_context_reset(auth_md_context);
2028 if (last_slash == nullptr) {
2029 LOG(ERROR) << "No '/' found in fully qualified method name";
2030 service[0] = '\0';
2031 method_name = gpr_strdup("");
2032 } else if (last_slash == service) {
2033 method_name = gpr_strdup("");
2034 } else {
2035 *last_slash = '\0';
2036 method_name = gpr_strdup(last_slash + 1);
2037 }
2038 char* host_and_port = grpc_slice_to_c_string(call_host);
2039 if (url_scheme != nullptr && strcmp(url_scheme, GRPC_SSL_URL_SCHEME) == 0) {
2040 // Remove the port if it is 443.
2041 char* port_delimiter = strrchr(host_and_port, ':');
2042 if (port_delimiter != nullptr && strcmp(port_delimiter + 1, "443") == 0) {
2043 *port_delimiter = '\0';
2044 }
2045 }
2046 gpr_asprintf(&service_url, "%s://%s%s",
2047 url_scheme == nullptr ? "" : url_scheme, host_and_port, service);
2048 auth_md_context->service_url = service_url;
2049 auth_md_context->method_name = method_name;
2050 auth_md_context->channel_auth_context =
2051 auth_context == nullptr
2052 ? nullptr
2053 : auth_context->Ref(DEBUG_LOCATION, "grpc_auth_metadata_context")
2054 .release();
2055 gpr_free(service);
2056 gpr_free(host_and_port);
2057 }
2058
TEST_F(CredentialsTest,TestAuthMetadataContext)2059 TEST_F(CredentialsTest, TestAuthMetadataContext) {
2060 auth_metadata_context_test_case test_cases[] = {
2061 // No service nor method.
2062 {"https", "www.foo.com", "", "https://www.foo.com", ""},
2063 // No method.
2064 {"https", "www.foo.com", "/Service", "https://www.foo.com/Service", ""},
2065 // Empty service and method.
2066 {"https", "www.foo.com", "//", "https://www.foo.com/", ""},
2067 // Empty method.
2068 {"https", "www.foo.com", "/Service/", "https://www.foo.com/Service", ""},
2069 // Malformed url.
2070 {"https", "www.foo.com:", "/Service/", "https://www.foo.com:/Service",
2071 ""},
2072 // https, default explicit port.
2073 {"https", "www.foo.com:443", "/Service/FooMethod",
2074 "https://www.foo.com/Service", "FooMethod"},
2075 // https, default implicit port.
2076 {"https", "www.foo.com", "/Service/FooMethod",
2077 "https://www.foo.com/Service", "FooMethod"},
2078 // https with ipv6 literal, default explicit port.
2079 {"https", "[1080:0:0:0:8:800:200C:417A]:443", "/Service/FooMethod",
2080 "https://[1080:0:0:0:8:800:200C:417A]/Service", "FooMethod"},
2081 // https with ipv6 literal, default implicit port.
2082 {"https", "[1080:0:0:0:8:800:200C:443]", "/Service/FooMethod",
2083 "https://[1080:0:0:0:8:800:200C:443]/Service", "FooMethod"},
2084 // https, custom port.
2085 {"https", "www.foo.com:8888", "/Service/FooMethod",
2086 "https://www.foo.com:8888/Service", "FooMethod"},
2087 // https with ipv6 literal, custom port.
2088 {"https", "[1080:0:0:0:8:800:200C:417A]:8888", "/Service/FooMethod",
2089 "https://[1080:0:0:0:8:800:200C:417A]:8888/Service", "FooMethod"},
2090 // custom url scheme, https default port.
2091 {"blah", "www.foo.com:443", "/Service/FooMethod",
2092 "blah://www.foo.com:443/Service", "FooMethod"}};
2093 for (uint32_t i = 0; i < GPR_ARRAY_SIZE(test_cases); i++) {
2094 const char* url_scheme = test_cases[i].url_scheme;
2095 grpc_slice call_host =
2096 grpc_slice_from_copied_string(test_cases[i].call_host);
2097 grpc_slice call_method =
2098 grpc_slice_from_copied_string(test_cases[i].call_method);
2099 grpc_auth_metadata_context auth_md_context;
2100 memset(&auth_md_context, 0, sizeof(auth_md_context));
2101 auth_metadata_context_build(url_scheme, call_host, call_method, nullptr,
2102 &auth_md_context);
2103 if (strcmp(auth_md_context.service_url,
2104 test_cases[i].desired_service_url) != 0) {
2105 Crash(absl::StrFormat("Invalid service url, want: %s, got %s.",
2106 test_cases[i].desired_service_url,
2107 auth_md_context.service_url));
2108 }
2109 if (strcmp(auth_md_context.method_name,
2110 test_cases[i].desired_method_name) != 0) {
2111 Crash(absl::StrFormat("Invalid method name, want: %s, got %s.",
2112 test_cases[i].desired_method_name,
2113 auth_md_context.method_name));
2114 }
2115 CHECK_EQ(auth_md_context.channel_auth_context, nullptr);
2116 grpc_slice_unref(call_host);
2117 grpc_slice_unref(call_method);
2118 grpc_auth_metadata_context_reset(&auth_md_context);
2119 }
2120 }
2121
validate_external_account_creds_token_exchange_request(const grpc_http_request * request,const URI & request_uri,absl::string_view body)2122 void validate_external_account_creds_token_exchange_request(
2123 const grpc_http_request* request, const URI& request_uri,
2124 absl::string_view body) {
2125 // Check that the body is constructed properly.
2126 std::string get_url_equivalent =
2127 absl::StrFormat("%s?%s", "https://foo.com:5555/token", body);
2128 absl::StatusOr<URI> uri = URI::Parse(get_url_equivalent);
2129 if (!uri.ok()) {
2130 LOG(ERROR) << uri.status().ToString();
2131 CHECK_OK(uri);
2132 }
2133 assert_query_parameters(*uri, "audience", "audience");
2134 assert_query_parameters(*uri, "grant_type",
2135 "urn:ietf:params:oauth:grant-type:token-exchange");
2136 assert_query_parameters(*uri, "requested_token_type",
2137 "urn:ietf:params:oauth:token-type:access_token");
2138 assert_query_parameters(*uri, "subject_token", "test_subject_token");
2139 assert_query_parameters(*uri, "subject_token_type", "subject_token_type");
2140 assert_query_parameters(*uri, "scope",
2141 "https://www.googleapis.com/auth/cloud-platform");
2142 // Check the rest of the request.
2143 EXPECT_EQ(request_uri.authority(), "foo.com:5555");
2144 EXPECT_EQ(request_uri.path(), "/token");
2145 ASSERT_EQ(request->hdr_count, 3);
2146 EXPECT_EQ(absl::string_view(request->hdrs[0].key), "Content-Type");
2147 EXPECT_EQ(absl::string_view(request->hdrs[0].value),
2148 "application/x-www-form-urlencoded");
2149 EXPECT_EQ(absl::string_view(request->hdrs[2].key), "Authorization");
2150 EXPECT_EQ(absl::string_view(request->hdrs[2].value),
2151 "Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=");
2152 }
2153
validate_external_account_creds_token_exchange_request_with_url_encode(const grpc_http_request * request,const URI & uri,absl::string_view body)2154 void validate_external_account_creds_token_exchange_request_with_url_encode(
2155 const grpc_http_request* request, const URI& uri, absl::string_view body) {
2156 // Check that the body is constructed properly.
2157 EXPECT_EQ(
2158 body,
2159 "audience=audience_!%40%23%24&grant_type=urn%3Aietf%3Aparams%3Aoauth%"
2160 "3Agrant-type%3Atoken-exchange&requested_token_type=urn%3Aietf%"
2161 "3Aparams%3Aoauth%3Atoken-type%3Aaccess_token&subject_token_type="
2162 "subject_token_type_!%40%23%24&subject_token=test_subject_token&"
2163 "scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcloud-platform&"
2164 "options=%7B%7D");
2165 // Check the rest of the request.
2166 EXPECT_EQ(uri.authority(), "foo.com:5555");
2167 EXPECT_EQ(uri.path(), "/token_url_encode");
2168 ASSERT_EQ(request->hdr_count, 3);
2169 EXPECT_EQ(absl::string_view(request->hdrs[0].key), "Content-Type");
2170 EXPECT_EQ(absl::string_view(request->hdrs[0].value),
2171 "application/x-www-form-urlencoded");
2172 EXPECT_EQ(absl::string_view(request->hdrs[2].key), "Authorization");
2173 EXPECT_EQ(absl::string_view(request->hdrs[2].value),
2174 "Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=");
2175 }
2176
validate_external_account_creds_service_account_impersonation_request(const grpc_http_request * request,const URI & uri,absl::string_view body)2177 void validate_external_account_creds_service_account_impersonation_request(
2178 const grpc_http_request* request, const URI& uri, absl::string_view body) {
2179 // Check that the body is constructed properly.
2180 EXPECT_EQ(body, "scope=scope_1%20scope_2&lifetime=3600s");
2181 // Check the rest of the request.
2182 EXPECT_EQ(uri.authority(), "foo.com:5555");
2183 EXPECT_EQ(uri.path(), "/service_account_impersonation");
2184 ASSERT_EQ(request->hdr_count, 2);
2185 EXPECT_EQ(absl::string_view(request->hdrs[0].key), "Content-Type");
2186 EXPECT_EQ(absl::string_view(request->hdrs[0].value),
2187 "application/x-www-form-urlencoded");
2188 EXPECT_EQ(absl::string_view(request->hdrs[1].key), "Authorization");
2189 EXPECT_EQ(absl::string_view(request->hdrs[1].value),
2190 "Bearer token_exchange_access_token");
2191 }
2192
validate_external_account_creds_serv_acc_imp_custom_lifetime_request(const grpc_http_request * request,const URI & uri,absl::string_view body)2193 void validate_external_account_creds_serv_acc_imp_custom_lifetime_request(
2194 const grpc_http_request* request, const URI& uri, absl::string_view body) {
2195 // Check that the body is constructed properly.
2196 EXPECT_EQ(body, "scope=scope_1%20scope_2&lifetime=1800s");
2197 // Check the rest of the request.
2198 EXPECT_EQ(uri.authority(), "foo.com:5555");
2199 EXPECT_EQ(uri.path(), "/service_account_impersonation");
2200 ASSERT_EQ(request->hdr_count, 2);
2201 EXPECT_EQ(absl::string_view(request->hdrs[0].key), "Content-Type");
2202 EXPECT_EQ(absl::string_view(request->hdrs[0].value),
2203 "application/x-www-form-urlencoded");
2204 EXPECT_EQ(absl::string_view(request->hdrs[1].key), "Authorization");
2205 EXPECT_EQ(absl::string_view(request->hdrs[1].value),
2206 "Bearer token_exchange_access_token");
2207 }
2208
external_acc_creds_serv_acc_imp_custom_lifetime_httpcli_post_success(const grpc_http_request * request,const URI & uri,absl::string_view body,Timestamp,grpc_closure * on_done,grpc_http_response * response)2209 int external_acc_creds_serv_acc_imp_custom_lifetime_httpcli_post_success(
2210 const grpc_http_request* request, const URI& uri, absl::string_view body,
2211 Timestamp /*deadline*/, grpc_closure* on_done,
2212 grpc_http_response* response) {
2213 if (uri.path() == "/token") {
2214 validate_external_account_creds_token_exchange_request(request, uri, body);
2215 *response = http_response(
2216 200, valid_external_account_creds_token_exchange_response);
2217 } else if (uri.path() == "/service_account_impersonation") {
2218 validate_external_account_creds_serv_acc_imp_custom_lifetime_request(
2219 request, uri, body);
2220 *response = http_response(
2221 200,
2222 valid_external_account_creds_service_account_impersonation_response);
2223 }
2224 ExecCtx::Run(DEBUG_LOCATION, on_done, absl::OkStatus());
2225 return 1;
2226 }
2227
external_account_creds_httpcli_post_success(const grpc_http_request * request,const URI & uri,absl::string_view body,Timestamp,grpc_closure * on_done,grpc_http_response * response)2228 int external_account_creds_httpcli_post_success(
2229 const grpc_http_request* request, const URI& uri, absl::string_view body,
2230 Timestamp /*deadline*/, grpc_closure* on_done,
2231 grpc_http_response* response) {
2232 if (uri.path() == "/token") {
2233 validate_external_account_creds_token_exchange_request(request, uri, body);
2234 *response = http_response(
2235 200, valid_external_account_creds_token_exchange_response);
2236 } else if (uri.path() == "/service_account_impersonation") {
2237 validate_external_account_creds_service_account_impersonation_request(
2238 request, uri, body);
2239 *response = http_response(
2240 200,
2241 valid_external_account_creds_service_account_impersonation_response);
2242 } else if (uri.path() == "/token_url_encode") {
2243 validate_external_account_creds_token_exchange_request_with_url_encode(
2244 request, uri, body);
2245 *response = http_response(
2246 200, valid_external_account_creds_token_exchange_response);
2247 }
2248 ExecCtx::Run(DEBUG_LOCATION, on_done, absl::OkStatus());
2249 return 1;
2250 }
2251
external_account_creds_httpcli_post_failure_token_exchange_response_missing_access_token(const grpc_http_request *,const URI & uri,absl::string_view,Timestamp,grpc_closure * on_done,grpc_http_response * response)2252 int external_account_creds_httpcli_post_failure_token_exchange_response_missing_access_token(
2253 const grpc_http_request* /*request*/, const URI& uri,
2254 absl::string_view /*body*/, Timestamp /*deadline*/, grpc_closure* on_done,
2255 grpc_http_response* response) {
2256 if (uri.path() == "/token") {
2257 *response = http_response(200,
2258 "{\"not_access_token\":\"not_access_token\","
2259 "\"expires_in\":3599,"
2260 " \"token_type\":\"Bearer\"}");
2261 } else if (uri.path() == "/service_account_impersonation") {
2262 *response = http_response(
2263 200,
2264 valid_external_account_creds_service_account_impersonation_response);
2265 }
2266 ExecCtx::Run(DEBUG_LOCATION, on_done, absl::OkStatus());
2267 return 1;
2268 }
2269
url_external_account_creds_httpcli_get_success(const grpc_http_request *,const URI & uri,Timestamp,grpc_closure * on_done,grpc_http_response * response)2270 int url_external_account_creds_httpcli_get_success(
2271 const grpc_http_request* /*request*/, const URI& uri,
2272 Timestamp /*deadline*/, grpc_closure* on_done,
2273 grpc_http_response* response) {
2274 if (uri.path() == "/generate_subject_token_format_text") {
2275 *response = http_response(
2276 200,
2277 valid_url_external_account_creds_retrieve_subject_token_response_format_text);
2278 } else if (uri.path() == "/path/to/url/creds?p1=v1&p2=v2") {
2279 *response = http_response(
2280 200,
2281 valid_url_external_account_creds_retrieve_subject_token_response_format_text);
2282 } else if (uri.path() == "/generate_subject_token_format_json") {
2283 *response = http_response(
2284 200,
2285 valid_url_external_account_creds_retrieve_subject_token_response_format_json);
2286 }
2287 ExecCtx::Run(DEBUG_LOCATION, on_done, absl::OkStatus());
2288 return 1;
2289 }
2290
validate_aws_external_account_creds_token_exchange_request(const grpc_http_request * request,const URI & request_uri,absl::string_view body)2291 void validate_aws_external_account_creds_token_exchange_request(
2292 const grpc_http_request* request, const URI& request_uri,
2293 absl::string_view body) {
2294 // Check that the regional_cred_verification_url got constructed
2295 // with the correct AWS Region ("test_regionz" or "test_region").
2296 EXPECT_NE(body.find("regional_cred_verification_url_test_region"), body.npos);
2297 std::string get_url_equivalent =
2298 absl::StrFormat("%s?%s", "https://foo.com:5555/token", body);
2299 absl::StatusOr<URI> uri = URI::Parse(get_url_equivalent);
2300 CHECK_OK(uri);
2301 assert_query_parameters(*uri, "audience", "audience");
2302 assert_query_parameters(*uri, "grant_type",
2303 "urn:ietf:params:oauth:grant-type:token-exchange");
2304 assert_query_parameters(*uri, "requested_token_type",
2305 "urn:ietf:params:oauth:token-type:access_token");
2306 assert_query_parameters(*uri, "subject_token_type", "subject_token_type");
2307 assert_query_parameters(*uri, "scope",
2308 "https://www.googleapis.com/auth/cloud-platform");
2309 // Check the rest of the request.
2310 EXPECT_EQ(request_uri.authority(), "foo.com:5555");
2311 EXPECT_EQ(request_uri.path(), "/token");
2312 ASSERT_EQ(request->hdr_count, 3);
2313 EXPECT_EQ(absl::string_view(request->hdrs[0].key), "Content-Type");
2314 EXPECT_EQ(absl::string_view(request->hdrs[0].value),
2315 "application/x-www-form-urlencoded");
2316 EXPECT_EQ(absl::string_view(request->hdrs[1].key), "x-goog-api-client");
2317 EXPECT_EQ(
2318 absl::string_view(request->hdrs[1].value),
2319 absl::StrFormat("gl-cpp/unknown auth/%s google-byoid-sdk source/aws "
2320 "sa-impersonation/false config-lifetime/false",
2321 grpc_version_string()));
2322 EXPECT_EQ(absl::string_view(request->hdrs[2].key), "Authorization");
2323 EXPECT_EQ(absl::string_view(request->hdrs[2].value),
2324 "Basic Y2xpZW50X2lkOmNsaWVudF9zZWNyZXQ=");
2325 }
2326
aws_external_account_creds_httpcli_get_success(const grpc_http_request *,const URI & uri,Timestamp,grpc_closure * on_done,grpc_http_response * response)2327 int aws_external_account_creds_httpcli_get_success(
2328 const grpc_http_request* /*request*/, const URI& uri,
2329 Timestamp /*deadline*/, grpc_closure* on_done,
2330 grpc_http_response* response) {
2331 if (uri.path() == "/region_url") {
2332 *response = http_response(200, "test_regionz");
2333 } else if (uri.path() == "/url") {
2334 *response = http_response(200, "test_role_name");
2335 } else if (uri.path() == "/url_no_role_name") {
2336 *response = http_response(200, "");
2337 } else if (uri.path() == "/url/test_role_name") {
2338 *response = http_response(
2339 200, valid_aws_external_account_creds_retrieve_signing_keys_response);
2340 }
2341 ExecCtx::Run(DEBUG_LOCATION, on_done, absl::OkStatus());
2342 return 1;
2343 }
2344
aws_imdsv2_external_account_creds_httpcli_get_success(const grpc_http_request * request,const URI & uri,Timestamp deadline,grpc_closure * on_done,grpc_http_response * response)2345 int aws_imdsv2_external_account_creds_httpcli_get_success(
2346 const grpc_http_request* request, const URI& uri, Timestamp deadline,
2347 grpc_closure* on_done, grpc_http_response* response) {
2348 EXPECT_EQ(request->hdr_count, 1);
2349 if (request->hdr_count == 1) {
2350 EXPECT_EQ(absl::string_view(request->hdrs[0].key),
2351 "x-aws-ec2-metadata-token");
2352 EXPECT_EQ(absl::string_view(request->hdrs[0].value),
2353 aws_imdsv2_session_token);
2354 }
2355 return aws_external_account_creds_httpcli_get_success(request, uri, deadline,
2356 on_done, response);
2357 }
2358
aws_imdsv2_external_account_creds_httpcli_put_success(const grpc_http_request * request,const URI & uri,absl::string_view,Timestamp,grpc_closure * on_done,grpc_http_response * response)2359 int aws_imdsv2_external_account_creds_httpcli_put_success(
2360 const grpc_http_request* request, const URI& uri,
2361 absl::string_view /*body*/, Timestamp /*deadline*/, grpc_closure* on_done,
2362 grpc_http_response* response) {
2363 EXPECT_EQ(request->hdr_count, 1);
2364 if (request->hdr_count == 1) {
2365 EXPECT_EQ(absl::string_view(request->hdrs[0].key),
2366 "x-aws-ec2-metadata-token-ttl-seconds");
2367 EXPECT_EQ(absl::string_view(request->hdrs[0].value), "300");
2368 }
2369 EXPECT_EQ(uri.path(), "/imdsv2_session_token_url");
2370 *response = http_response(200, aws_imdsv2_session_token);
2371 ExecCtx::Run(DEBUG_LOCATION, on_done, absl::OkStatus());
2372 return 1;
2373 }
2374
aws_external_account_creds_httpcli_post_success(const grpc_http_request * request,const URI & uri,absl::string_view body,Timestamp,grpc_closure * on_done,grpc_http_response * response)2375 int aws_external_account_creds_httpcli_post_success(
2376 const grpc_http_request* request, const URI& uri, absl::string_view body,
2377 Timestamp /*deadline*/, grpc_closure* on_done,
2378 grpc_http_response* response) {
2379 if (uri.path() == "/token") {
2380 validate_aws_external_account_creds_token_exchange_request(request, uri,
2381 body);
2382 *response = http_response(
2383 200, valid_external_account_creds_token_exchange_response);
2384 }
2385 ExecCtx::Run(DEBUG_LOCATION, on_done, absl::OkStatus());
2386 return 1;
2387 }
2388
2389 class TokenFetcherCredentialsTest : public ::testing::Test {
2390 protected:
2391 class TestTokenFetcherCredentials final : public TokenFetcherCredentials {
2392 public:
TestTokenFetcherCredentials(std::shared_ptr<grpc_event_engine::experimental::EventEngine> event_engine=nullptr)2393 explicit TestTokenFetcherCredentials(
2394 std::shared_ptr<grpc_event_engine::experimental::EventEngine>
2395 event_engine = nullptr)
2396 : TokenFetcherCredentials(std::move(event_engine),
2397 /*test_only_use_backoff_jitter=*/false) {}
2398
~TestTokenFetcherCredentials()2399 ~TestTokenFetcherCredentials() override { CHECK_EQ(queue_.size(), 0); }
2400
AddResult(absl::StatusOr<RefCountedPtr<Token>> result)2401 void AddResult(absl::StatusOr<RefCountedPtr<Token>> result) {
2402 MutexLock lock(&mu_);
2403 queue_.push_front(std::move(result));
2404 }
2405
num_fetches() const2406 size_t num_fetches() const { return num_fetches_; }
2407
2408 private:
2409 class TestFetchRequest final : public FetchRequest {
2410 public:
TestFetchRequest(grpc_event_engine::experimental::EventEngine & event_engine,absl::AnyInvocable<void (absl::StatusOr<RefCountedPtr<Token>>)> on_done,absl::StatusOr<RefCountedPtr<Token>> result)2411 TestFetchRequest(
2412 grpc_event_engine::experimental::EventEngine& event_engine,
2413 absl::AnyInvocable<void(absl::StatusOr<RefCountedPtr<Token>>)>
2414 on_done,
2415 absl::StatusOr<RefCountedPtr<Token>> result) {
2416 event_engine.Run([on_done = std::move(on_done),
2417 result = std::move(result)]() mutable {
2418 ApplicationCallbackExecCtx application_exec_ctx;
2419 ExecCtx exec_ctx;
2420 std::exchange(on_done, nullptr)(std::move(result));
2421 });
2422 }
2423
Orphan()2424 void Orphan() override { Unref(); }
2425 };
2426
FetchToken(Timestamp deadline,absl::AnyInvocable<void (absl::StatusOr<RefCountedPtr<Token>>)> on_done)2427 OrphanablePtr<FetchRequest> FetchToken(
2428 Timestamp deadline,
2429 absl::AnyInvocable<void(absl::StatusOr<RefCountedPtr<Token>>)> on_done)
2430 override {
2431 absl::StatusOr<RefCountedPtr<Token>> result;
2432 {
2433 MutexLock lock(&mu_);
2434 CHECK(!queue_.empty());
2435 result = std::move(queue_.back());
2436 queue_.pop_back();
2437 }
2438 num_fetches_.fetch_add(1);
2439 return MakeOrphanable<TestFetchRequest>(
2440 event_engine(), std::move(on_done), std::move(result));
2441 }
2442
debug_string()2443 std::string debug_string() override {
2444 return "TestTokenFetcherCredentials";
2445 }
2446
type() const2447 UniqueTypeName type() const override {
2448 static UniqueTypeName::Factory kFactory("TestTokenFetcherCredentials");
2449 return kFactory.Create();
2450 }
2451
2452 Mutex mu_;
2453 std::deque<absl::StatusOr<RefCountedPtr<Token>>> queue_
2454 ABSL_GUARDED_BY(&mu_);
2455
2456 std::atomic<size_t> num_fetches_{0};
2457 };
2458
SetUp()2459 void SetUp() override {
2460 event_engine_ = std::make_shared<FuzzingEventEngine>(
2461 FuzzingEventEngine::Options(), fuzzing_event_engine::Actions());
2462 grpc_timer_manager_set_start_threaded(false);
2463 grpc_init();
2464 creds_ = MakeRefCounted<TestTokenFetcherCredentials>(event_engine_);
2465 }
2466
TearDown()2467 void TearDown() override {
2468 event_engine_->FuzzingDone();
2469 event_engine_->TickUntilIdle();
2470 event_engine_->UnsetGlobalHooks();
2471 creds_.reset();
2472 grpc_event_engine::experimental::WaitForSingleOwner(
2473 std::move(event_engine_));
2474 grpc_shutdown_blocking();
2475 }
2476
MakeToken(absl::string_view token,Timestamp expiration=Timestamp::InfFuture ())2477 static RefCountedPtr<TokenFetcherCredentials::Token> MakeToken(
2478 absl::string_view token, Timestamp expiration = Timestamp::InfFuture()) {
2479 return MakeRefCounted<TokenFetcherCredentials::Token>(
2480 Slice::FromCopiedString(token), expiration);
2481 }
2482
2483 std::shared_ptr<FuzzingEventEngine> event_engine_;
2484 RefCountedPtr<TestTokenFetcherCredentials> creds_;
2485 };
2486
TEST_F(TokenFetcherCredentialsTest,Basic)2487 TEST_F(TokenFetcherCredentialsTest, Basic) {
2488 const auto kExpirationTime = Timestamp::Now() + Duration::Hours(1);
2489 ExecCtx exec_ctx;
2490 creds_->AddResult(MakeToken("foo", kExpirationTime));
2491 // First request will trigger a fetch.
2492 LOG(INFO) << "First request";
2493 auto state = RequestMetadataState::NewInstance(
2494 absl::OkStatus(), "authorization: foo", /*expect_delay=*/true);
2495 state->RunRequestMetadataTest(creds_.get(), kTestUrlScheme, kTestAuthority,
2496 kTestPath);
2497 EXPECT_EQ(creds_->num_fetches(), 1);
2498 // Second request while fetch is still outstanding will be delayed but
2499 // will not trigger a new fetch.
2500 LOG(INFO) << "Second request";
2501 state = RequestMetadataState::NewInstance(
2502 absl::OkStatus(), "authorization: foo", /*expect_delay=*/true);
2503 state->RunRequestMetadataTest(creds_.get(), kTestUrlScheme, kTestAuthority,
2504 kTestPath);
2505 EXPECT_EQ(creds_->num_fetches(), 1);
2506 // Now tick to finish the fetch.
2507 event_engine_->TickUntilIdle();
2508 // Next request will be served from cache with no delay.
2509 LOG(INFO) << "Third request";
2510 state = RequestMetadataState::NewInstance(
2511 absl::OkStatus(), "authorization: foo", /*expect_delay=*/false);
2512 state->RunRequestMetadataTest(creds_.get(), kTestUrlScheme, kTestAuthority,
2513 kTestPath);
2514 EXPECT_EQ(creds_->num_fetches(), 1);
2515 // Advance time to expiration minus expiration adjustment and prefetch time.
2516 exec_ctx.TestOnlySetNow(kExpirationTime - Duration::Seconds(90));
2517 // No new fetch yet.
2518 EXPECT_EQ(creds_->num_fetches(), 1);
2519 // Next request will trigger a new fetch but will still use the
2520 // cached token.
2521 creds_->AddResult(MakeToken("bar"));
2522 LOG(INFO) << "Fourth request";
2523 state = RequestMetadataState::NewInstance(
2524 absl::OkStatus(), "authorization: foo", /*expect_delay=*/false);
2525 state->RunRequestMetadataTest(creds_.get(), kTestUrlScheme, kTestAuthority,
2526 kTestPath);
2527 EXPECT_EQ(creds_->num_fetches(), 2);
2528 event_engine_->TickUntilIdle();
2529 // Next request will use the new data.
2530 LOG(INFO) << "Fifth request";
2531 state = RequestMetadataState::NewInstance(
2532 absl::OkStatus(), "authorization: bar", /*expect_delay=*/false);
2533 state->RunRequestMetadataTest(creds_.get(), kTestUrlScheme, kTestAuthority,
2534 kTestPath);
2535 EXPECT_EQ(creds_->num_fetches(), 2);
2536 }
2537
TEST_F(TokenFetcherCredentialsTest,Expires30SecondsEarly)2538 TEST_F(TokenFetcherCredentialsTest, Expires30SecondsEarly) {
2539 const auto kExpirationTime = Timestamp::Now() + Duration::Hours(1);
2540 ExecCtx exec_ctx;
2541 creds_->AddResult(MakeToken("foo", kExpirationTime));
2542 // First request will trigger a fetch.
2543 auto state = RequestMetadataState::NewInstance(
2544 absl::OkStatus(), "authorization: foo", /*expect_delay=*/true);
2545 state->RunRequestMetadataTest(creds_.get(), kTestUrlScheme, kTestAuthority,
2546 kTestPath);
2547 EXPECT_EQ(creds_->num_fetches(), 1);
2548 event_engine_->TickUntilIdle();
2549 // Advance time to expiration minus 30 seconds.
2550 exec_ctx.TestOnlySetNow(kExpirationTime - Duration::Seconds(30));
2551 // No new fetch yet.
2552 EXPECT_EQ(creds_->num_fetches(), 1);
2553 // Next request will trigger a new fetch and will delay the call until
2554 // the fetch completes.
2555 creds_->AddResult(MakeToken("bar"));
2556 state = RequestMetadataState::NewInstance(
2557 absl::OkStatus(), "authorization: bar", /*expect_delay=*/true);
2558 state->RunRequestMetadataTest(creds_.get(), kTestUrlScheme, kTestAuthority,
2559 kTestPath);
2560 EXPECT_EQ(creds_->num_fetches(), 2);
2561 event_engine_->TickUntilIdle();
2562 }
2563
TEST_F(TokenFetcherCredentialsTest,FetchFails)2564 TEST_F(TokenFetcherCredentialsTest, FetchFails) {
2565 const absl::Status kExpectedError = absl::UnavailableError("bummer, dude");
2566 absl::optional<FuzzingEventEngine::Duration> run_after_duration;
2567 event_engine_->SetRunAfterDurationCallback(
2568 [&](FuzzingEventEngine::Duration duration) {
2569 run_after_duration = duration;
2570 });
2571 ExecCtx exec_ctx;
2572 // First request will trigger a fetch, which will fail.
2573 LOG(INFO) << "Sending first RPC.";
2574 creds_->AddResult(kExpectedError);
2575 auto state = RequestMetadataState::NewInstance(kExpectedError, "",
2576 /*expect_delay=*/true);
2577 state->RunRequestMetadataTest(creds_.get(), kTestUrlScheme, kTestAuthority,
2578 kTestPath);
2579 EXPECT_EQ(creds_->num_fetches(), 1);
2580 while (!run_after_duration.has_value()) event_engine_->Tick();
2581 // Make sure backoff was set for the right period.
2582 EXPECT_EQ(run_after_duration, std::chrono::seconds(1));
2583 run_after_duration.reset();
2584 // Start a new call now, which will fail because we're in backoff.
2585 LOG(INFO) << "Sending second RPC.";
2586 state = RequestMetadataState::NewInstance(
2587 kExpectedError, "authorization: foo", /*expect_delay=*/false);
2588 state->RunRequestMetadataTest(creds_.get(), kTestUrlScheme, kTestAuthority,
2589 kTestPath);
2590 EXPECT_EQ(creds_->num_fetches(), 1);
2591 // Tick until backoff expires.
2592 LOG(INFO) << "Waiting for backoff.";
2593 event_engine_->TickUntilIdle();
2594 EXPECT_EQ(creds_->num_fetches(), 1);
2595 // Starting another call should trigger a new fetch, which will
2596 // succeed this time.
2597 LOG(INFO) << "Sending third RPC.";
2598 creds_->AddResult(MakeToken("foo"));
2599 state = RequestMetadataState::NewInstance(
2600 absl::OkStatus(), "authorization: foo", /*expect_delay=*/true);
2601 state->RunRequestMetadataTest(creds_.get(), kTestUrlScheme, kTestAuthority,
2602 kTestPath);
2603 EXPECT_EQ(creds_->num_fetches(), 2);
2604 }
2605
TEST_F(TokenFetcherCredentialsTest,Backoff)2606 TEST_F(TokenFetcherCredentialsTest, Backoff) {
2607 const absl::Status kExpectedError = absl::UnavailableError("bummer, dude");
2608 absl::optional<FuzzingEventEngine::Duration> run_after_duration;
2609 event_engine_->SetRunAfterDurationCallback(
2610 [&](FuzzingEventEngine::Duration duration) {
2611 run_after_duration = duration;
2612 });
2613 ExecCtx exec_ctx;
2614 // First request will trigger a fetch, which will fail.
2615 LOG(INFO) << "Sending first RPC.";
2616 creds_->AddResult(kExpectedError);
2617 auto state = RequestMetadataState::NewInstance(kExpectedError, "",
2618 /*expect_delay=*/true);
2619 state->RunRequestMetadataTest(creds_.get(), kTestUrlScheme, kTestAuthority,
2620 kTestPath);
2621 EXPECT_EQ(creds_->num_fetches(), 1);
2622 while (!run_after_duration.has_value()) event_engine_->Tick();
2623 // Make sure backoff was set for the right period.
2624 EXPECT_EQ(run_after_duration, std::chrono::seconds(1));
2625 run_after_duration.reset();
2626 // Start a new call now, which will fail because we're in backoff.
2627 LOG(INFO) << "Sending second RPC.";
2628 state = RequestMetadataState::NewInstance(kExpectedError, "",
2629 /*expect_delay=*/false);
2630 state->RunRequestMetadataTest(creds_.get(), kTestUrlScheme, kTestAuthority,
2631 kTestPath);
2632 EXPECT_EQ(creds_->num_fetches(), 1);
2633 // Tick until backoff expires.
2634 LOG(INFO) << "Waiting for backoff.";
2635 event_engine_->TickUntilIdle();
2636 EXPECT_EQ(creds_->num_fetches(), 1);
2637 // Starting another call should trigger a new fetch, which will again fail.
2638 LOG(INFO) << "Sending third RPC.";
2639 creds_->AddResult(kExpectedError);
2640 state = RequestMetadataState::NewInstance(kExpectedError, "",
2641 /*expect_delay=*/true);
2642 state->RunRequestMetadataTest(creds_.get(), kTestUrlScheme, kTestAuthority,
2643 kTestPath);
2644 EXPECT_EQ(creds_->num_fetches(), 2);
2645 while (!run_after_duration.has_value()) event_engine_->Tick();
2646 // The backoff time should be longer now.
2647 EXPECT_EQ(run_after_duration, std::chrono::milliseconds(1600))
2648 << "actual: " << run_after_duration->count();
2649 run_after_duration.reset();
2650 // Start a new call now, which will fail because we're in backoff.
2651 LOG(INFO) << "Sending fourth RPC.";
2652 state = RequestMetadataState::NewInstance(kExpectedError, "",
2653 /*expect_delay=*/false);
2654 state->RunRequestMetadataTest(creds_.get(), kTestUrlScheme, kTestAuthority,
2655 kTestPath);
2656 EXPECT_EQ(creds_->num_fetches(), 2);
2657 // Tick until backoff expires.
2658 LOG(INFO) << "Waiting for backoff.";
2659 event_engine_->TickUntilIdle();
2660 EXPECT_EQ(creds_->num_fetches(), 2);
2661 // Starting another call should trigger a new fetch, which will again fail.
2662 LOG(INFO) << "Sending fifth RPC.";
2663 creds_->AddResult(kExpectedError);
2664 state = RequestMetadataState::NewInstance(kExpectedError, "",
2665 /*expect_delay=*/true);
2666 state->RunRequestMetadataTest(creds_.get(), kTestUrlScheme, kTestAuthority,
2667 kTestPath);
2668 EXPECT_EQ(creds_->num_fetches(), 3);
2669 while (!run_after_duration.has_value()) event_engine_->Tick();
2670 // The backoff time should be longer now.
2671 EXPECT_EQ(run_after_duration, std::chrono::milliseconds(2560))
2672 << "actual: " << run_after_duration->count();
2673 }
2674
TEST_F(TokenFetcherCredentialsTest,ShutdownWhileBackoffTimerPending)2675 TEST_F(TokenFetcherCredentialsTest, ShutdownWhileBackoffTimerPending) {
2676 const absl::Status kExpectedError = absl::UnavailableError("bummer, dude");
2677 absl::optional<FuzzingEventEngine::Duration> run_after_duration;
2678 event_engine_->SetRunAfterDurationCallback(
2679 [&](FuzzingEventEngine::Duration duration) {
2680 run_after_duration = duration;
2681 });
2682 ExecCtx exec_ctx;
2683 creds_->AddResult(kExpectedError);
2684 // First request will trigger a fetch, which will fail.
2685 auto state = RequestMetadataState::NewInstance(kExpectedError, "",
2686 /*expect_delay=*/true);
2687 state->RunRequestMetadataTest(creds_.get(), kTestUrlScheme, kTestAuthority,
2688 kTestPath);
2689 EXPECT_EQ(creds_->num_fetches(), 1);
2690 while (!run_after_duration.has_value()) event_engine_->Tick();
2691 // Make sure backoff was set for the right period.
2692 EXPECT_EQ(run_after_duration, std::chrono::seconds(1));
2693 run_after_duration.reset();
2694 // Do nothing else. Make sure the creds shut down correctly.
2695 }
2696
2697 // The subclass of ExternalAccountCredentials for testing.
2698 // ExternalAccountCredentials is an abstract class so we can't directly test
2699 // against it.
2700 class TestExternalAccountCredentials final : public ExternalAccountCredentials {
2701 public:
TestExternalAccountCredentials(Options options,std::vector<std::string> scopes,std::shared_ptr<grpc_event_engine::experimental::EventEngine> event_engine=nullptr)2702 TestExternalAccountCredentials(
2703 Options options, std::vector<std::string> scopes,
2704 std::shared_ptr<grpc_event_engine::experimental::EventEngine>
2705 event_engine = nullptr)
2706 : ExternalAccountCredentials(std::move(options), std::move(scopes),
2707 std::move(event_engine)) {}
2708
GetMetricsValue()2709 std::string GetMetricsValue() { return MetricsHeaderValue(); }
2710
debug_string()2711 std::string debug_string() override {
2712 return "TestExternalAccountCredentials";
2713 }
2714
type() const2715 UniqueTypeName type() const override {
2716 static UniqueTypeName::Factory kFactory("TestExternalAccountCredentials");
2717 return kFactory.Create();
2718 }
2719
2720 private:
RetrieveSubjectToken(Timestamp,absl::AnyInvocable<void (absl::StatusOr<std::string>)> on_done)2721 OrphanablePtr<FetchBody> RetrieveSubjectToken(
2722 Timestamp /*deadline*/,
2723 absl::AnyInvocable<void(absl::StatusOr<std::string>)> on_done) override {
2724 return MakeOrphanable<NoOpFetchBody>(event_engine(), std::move(on_done),
2725 "test_subject_token");
2726 }
2727 };
2728
TEST_F(CredentialsTest,TestExternalAccountCredsMetricsHeader)2729 TEST_F(CredentialsTest, TestExternalAccountCredsMetricsHeader) {
2730 Json credential_source = Json::FromString("");
2731 TestExternalAccountCredentials::ServiceAccountImpersonation
2732 service_account_impersonation;
2733 service_account_impersonation.token_lifetime_seconds = 3600;
2734 TestExternalAccountCredentials::Options options = {
2735 "external_account", // type;
2736 "audience", // audience;
2737 "subject_token_type", // subject_token_type;
2738 "", // service_account_impersonation_url;
2739 service_account_impersonation, // service_account_impersonation;
2740 "https://foo.com:5555/token", // token_url;
2741 "https://foo.com:5555/token_info", // token_info_url;
2742 credential_source, // credential_source;
2743 "quota_project_id", // quota_project_id;
2744 "client_id", // client_id;
2745 "client_secret", // client_secret;
2746 "", // workforce_pool_user_project;
2747 };
2748 TestExternalAccountCredentials creds(options, {});
2749 EXPECT_EQ(
2750 creds.GetMetricsValue(),
2751 absl::StrFormat("gl-cpp/unknown auth/%s google-byoid-sdk source/unknown "
2752 "sa-impersonation/false config-lifetime/false",
2753 grpc_version_string()));
2754 }
2755
TEST_F(CredentialsTest,TestExternalAccountCredsMetricsHeaderWithServiceAccountImpersonation)2756 TEST_F(CredentialsTest,
2757 TestExternalAccountCredsMetricsHeaderWithServiceAccountImpersonation) {
2758 Json credential_source = Json::FromString("");
2759 TestExternalAccountCredentials::ServiceAccountImpersonation
2760 service_account_impersonation;
2761 service_account_impersonation.token_lifetime_seconds = 3600;
2762 TestExternalAccountCredentials::Options options = {
2763 "external_account", // type;
2764 "audience", // audience;
2765 "subject_token_type", // subject_token_type;
2766 "https://foo.com:5555/service_account_impersonation", // service_account_impersonation_url;
2767 service_account_impersonation, // service_account_impersonation;
2768 "https://foo.com:5555/token", // token_url;
2769 "https://foo.com:5555/token_info", // token_info_url;
2770 credential_source, // credential_source;
2771 "quota_project_id", // quota_project_id;
2772 "client_id", // client_id;
2773 "client_secret", // client_secret;
2774 "", // workforce_pool_user_project;
2775 };
2776 TestExternalAccountCredentials creds(options, {});
2777 EXPECT_EQ(
2778 creds.GetMetricsValue(),
2779 absl::StrFormat("gl-cpp/unknown auth/%s google-byoid-sdk source/unknown "
2780 "sa-impersonation/true config-lifetime/false",
2781 grpc_version_string()));
2782 }
2783
TEST_F(CredentialsTest,TestExternalAccountCredsMetricsHeaderWithConfigLifetime)2784 TEST_F(CredentialsTest,
2785 TestExternalAccountCredsMetricsHeaderWithConfigLifetime) {
2786 Json credential_source = Json::FromString("");
2787 TestExternalAccountCredentials::ServiceAccountImpersonation
2788 service_account_impersonation;
2789 service_account_impersonation.token_lifetime_seconds = 5000;
2790 TestExternalAccountCredentials::Options options = {
2791 "external_account", // type;
2792 "audience", // audience;
2793 "subject_token_type", // subject_token_type;
2794 "https://foo.com:5555/service_account_impersonation", // service_account_impersonation_url;
2795 service_account_impersonation, // service_account_impersonation;
2796 "https://foo.com:5555/token", // token_url;
2797 "https://foo.com:5555/token_info", // token_info_url;
2798 credential_source, // credential_source;
2799 "quota_project_id", // quota_project_id;
2800 "client_id", // client_id;
2801 "client_secret", // client_secret;
2802 "", // workforce_pool_user_project;
2803 };
2804 TestExternalAccountCredentials creds(options, {});
2805 EXPECT_EQ(
2806 creds.GetMetricsValue(),
2807 absl::StrFormat("gl-cpp/unknown auth/%s google-byoid-sdk source/unknown "
2808 "sa-impersonation/true config-lifetime/true",
2809 grpc_version_string()));
2810 }
2811
2812 class ExternalAccountCredentialsTest : public ::testing::Test {
2813 protected:
SetUp()2814 void SetUp() override {
2815 grpc_timer_manager_set_start_threaded(false);
2816 grpc_init();
2817 }
2818
TearDown()2819 void TearDown() override {
2820 event_engine_->FuzzingDone();
2821 event_engine_->TickUntilIdle();
2822 event_engine_->UnsetGlobalHooks();
2823 grpc_event_engine::experimental::WaitForSingleOwner(
2824 std::move(event_engine_));
2825 grpc_shutdown_blocking();
2826 }
2827
2828 std::shared_ptr<FuzzingEventEngine> event_engine_ =
2829 std::make_shared<FuzzingEventEngine>(FuzzingEventEngine::Options(),
2830 fuzzing_event_engine::Actions());
2831 };
2832
TEST_F(ExternalAccountCredentialsTest,Success)2833 TEST_F(ExternalAccountCredentialsTest, Success) {
2834 ExecCtx exec_ctx;
2835 Json credential_source = Json::FromString("");
2836 TestExternalAccountCredentials::ServiceAccountImpersonation
2837 service_account_impersonation;
2838 service_account_impersonation.token_lifetime_seconds = 3600;
2839 TestExternalAccountCredentials::Options options = {
2840 "external_account", // type;
2841 "audience", // audience;
2842 "subject_token_type", // subject_token_type;
2843 "", // service_account_impersonation_url;
2844 service_account_impersonation, // service_account_impersonation;
2845 "https://foo.com:5555/token", // token_url;
2846 "https://foo.com:5555/token_info", // token_info_url;
2847 credential_source, // credential_source;
2848 "quota_project_id", // quota_project_id;
2849 "client_id", // client_id;
2850 "client_secret", // client_secret;
2851 "", // workforce_pool_user_project;
2852 };
2853 auto creds = MakeRefCounted<TestExternalAccountCredentials>(
2854 options, std::vector<std::string>(), event_engine_);
2855 // Check security level.
2856 EXPECT_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
2857 // First request: http post should be called.
2858 auto state = RequestMetadataState::NewInstance(
2859 absl::OkStatus(), "authorization: Bearer token_exchange_access_token");
2860 HttpRequest::SetOverride(httpcli_get_should_not_be_called,
2861 external_account_creds_httpcli_post_success,
2862 httpcli_put_should_not_be_called);
2863 state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority,
2864 kTestPath);
2865 ExecCtx::Get()->Flush();
2866 event_engine_->TickUntilIdle();
2867 // Second request: the cached token should be served directly.
2868 state = RequestMetadataState::NewInstance(
2869 absl::OkStatus(), "authorization: Bearer token_exchange_access_token");
2870 HttpRequest::SetOverride(httpcli_get_should_not_be_called,
2871 httpcli_post_should_not_be_called,
2872 httpcli_put_should_not_be_called);
2873 state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority,
2874 kTestPath);
2875 ExecCtx::Get()->Flush();
2876 event_engine_->TickUntilIdle();
2877 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
2878 }
2879
TEST_F(ExternalAccountCredentialsTest,SuccessWithUrlEncode)2880 TEST_F(ExternalAccountCredentialsTest, SuccessWithUrlEncode) {
2881 std::map<std::string, std::string> emd = {
2882 {"authorization", "Bearer token_exchange_access_token"}};
2883 ExecCtx exec_ctx;
2884 Json credential_source = Json::FromString("");
2885 TestExternalAccountCredentials::ServiceAccountImpersonation
2886 service_account_impersonation;
2887 service_account_impersonation.token_lifetime_seconds = 3600;
2888 TestExternalAccountCredentials::Options options = {
2889 "external_account", // type;
2890 "audience_!@#$", // audience;
2891 "subject_token_type_!@#$", // subject_token_type;
2892 "", // service_account_impersonation_url;
2893 service_account_impersonation, // service_account_impersonation;
2894 "https://foo.com:5555/token_url_encode", // token_url;
2895 "https://foo.com:5555/token_info", // token_info_url;
2896 credential_source, // credential_source;
2897 "quota_project_id", // quota_project_id;
2898 "client_id", // client_id;
2899 "client_secret", // client_secret;
2900 "", // workforce_pool_user_project;
2901 };
2902 auto creds = MakeRefCounted<TestExternalAccountCredentials>(
2903 options, std::vector<std::string>(), event_engine_);
2904 auto state = RequestMetadataState::NewInstance(
2905 absl::OkStatus(), "authorization: Bearer token_exchange_access_token");
2906 HttpRequest::SetOverride(httpcli_get_should_not_be_called,
2907 external_account_creds_httpcli_post_success,
2908 httpcli_put_should_not_be_called);
2909 state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority,
2910 kTestPath);
2911 ExecCtx::Get()->Flush();
2912 event_engine_->TickUntilIdle();
2913 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
2914 }
2915
TEST_F(ExternalAccountCredentialsTest,SuccessWithServiceAccountImpersonation)2916 TEST_F(ExternalAccountCredentialsTest, SuccessWithServiceAccountImpersonation) {
2917 ExecCtx exec_ctx;
2918 Json credential_source = Json::FromString("");
2919 TestExternalAccountCredentials::ServiceAccountImpersonation
2920 service_account_impersonation;
2921 service_account_impersonation.token_lifetime_seconds = 3600;
2922 TestExternalAccountCredentials::Options options = {
2923 "external_account", // type;
2924 "audience", // audience;
2925 "subject_token_type", // subject_token_type;
2926 "https://foo.com:5555/service_account_impersonation", // service_account_impersonation_url;
2927 service_account_impersonation, // service_account_impersonation;
2928 "https://foo.com:5555/token", // token_url;
2929 "https://foo.com:5555/token_info", // token_info_url;
2930 credential_source, // credential_source;
2931 "quota_project_id", // quota_project_id;
2932 "client_id", // client_id;
2933 "client_secret", // client_secret;
2934 "", // workforce_pool_user_project;
2935 };
2936 auto creds = MakeRefCounted<TestExternalAccountCredentials>(
2937 options, std::vector<std::string>{"scope_1", "scope_2"}, event_engine_);
2938 // Check security level.
2939 EXPECT_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
2940 // First request: http put should be called.
2941 auto state = RequestMetadataState::NewInstance(
2942 absl::OkStatus(),
2943 "authorization: Bearer service_account_impersonation_access_token");
2944 HttpRequest::SetOverride(httpcli_get_should_not_be_called,
2945 external_account_creds_httpcli_post_success,
2946 httpcli_put_should_not_be_called);
2947 state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority,
2948 kTestPath);
2949 ExecCtx::Get()->Flush();
2950 event_engine_->TickUntilIdle();
2951 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
2952 }
2953
TEST_F(ExternalAccountCredentialsTest,SuccessWithServiceAccountImpersonationAndCustomTokenLifetime)2954 TEST_F(ExternalAccountCredentialsTest,
2955 SuccessWithServiceAccountImpersonationAndCustomTokenLifetime) {
2956 ExecCtx exec_ctx;
2957 Json credential_source = Json::FromString("");
2958 TestExternalAccountCredentials::ServiceAccountImpersonation
2959 service_account_impersonation;
2960 service_account_impersonation.token_lifetime_seconds = 1800;
2961 TestExternalAccountCredentials::Options options = {
2962 "external_account", // type;
2963 "audience", // audience;
2964 "subject_token_type", // subject_token_type;
2965 "https://foo.com:5555/service_account_impersonation", // service_account_impersonation_url;
2966 service_account_impersonation, // service_account_impersonation;
2967 "https://foo.com:5555/token", // token_url;
2968 "https://foo.com:5555/token_info", // token_info_url;
2969 credential_source, // credential_source;
2970 "quota_project_id", // quota_project_id;
2971 "client_id", // client_id;
2972 "client_secret", // client_secret;
2973 "", // workforce_pool_user_project;
2974 };
2975 auto creds = MakeRefCounted<TestExternalAccountCredentials>(
2976 options, std::vector<std::string>{"scope_1", "scope_2"}, event_engine_);
2977 // Check security level.
2978 EXPECT_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
2979 // First request: http put should be called.
2980 auto state = RequestMetadataState::NewInstance(
2981 absl::OkStatus(),
2982 "authorization: Bearer service_account_impersonation_access_token");
2983 HttpRequest::SetOverride(
2984 httpcli_get_should_not_be_called,
2985 external_acc_creds_serv_acc_imp_custom_lifetime_httpcli_post_success,
2986 httpcli_put_should_not_be_called);
2987 state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority,
2988 kTestPath);
2989 ExecCtx::Get()->Flush();
2990 event_engine_->TickUntilIdle();
2991 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
2992 }
2993
TEST_F(ExternalAccountCredentialsTest,FailureWithServiceAccountImpersonationAndInvalidCustomTokenLifetime)2994 TEST_F(ExternalAccountCredentialsTest,
2995 FailureWithServiceAccountImpersonationAndInvalidCustomTokenLifetime) {
2996 const char* options_string1 =
2997 "{\"type\":\"external_account\",\"audience\":\"audience\","
2998 "\"subject_token_type\":\"subject_token_type\","
2999 "\"service_account_impersonation_url\":\"service_account_impersonation_"
3000 "url\",\"service_account_impersonation\":"
3001 "{\"token_lifetime_seconds\":599},"
3002 "\"token_url\":\"https://foo.com:5555/token\","
3003 "\"token_info_url\":\"https://foo.com:5555/token_info\","
3004 "\"credential_source\":{\"url\":\"https://foo.com:5555/"
3005 "generate_subject_token_format_json\","
3006 "\"headers\":{\"Metadata-Flavor\":\"Google\"},"
3007 "\"format\":{\"type\":\"json\",\"subject_token_field_name\":\"access_"
3008 "token\"}},\"quota_project_id\":\"quota_project_id\","
3009 "\"client_id\":\"client_id\",\"client_secret\":\"client_secret\"}";
3010 auto json = JsonParse(options_string1);
3011 ASSERT_TRUE(json.ok()) << json.status();
3012 auto creds = ExternalAccountCredentials::Create(*json, {"scope1", "scope2"});
3013 std::string actual_error;
3014 grpc_error_get_str(creds.status(), StatusStrProperty::kDescription,
3015 &actual_error);
3016 EXPECT_EQ("token_lifetime_seconds must be more than 600s", actual_error);
3017
3018 const char* options_string2 =
3019 "{\"type\":\"external_account\",\"audience\":\"audience\","
3020 "\"subject_token_type\":\"subject_token_type\","
3021 "\"service_account_impersonation_url\":\"service_account_impersonation_"
3022 "url\",\"service_account_impersonation\":"
3023 "{\"token_lifetime_seconds\":43201},"
3024 "\"token_url\":\"https://foo.com:5555/token\","
3025 "\"token_info_url\":\"https://foo.com:5555/token_info\","
3026 "\"credential_source\":{\"url\":\"https://foo.com:5555/"
3027 "generate_subject_token_format_json\","
3028 "\"headers\":{\"Metadata-Flavor\":\"Google\"},"
3029 "\"format\":{\"type\":\"json\",\"subject_token_field_name\":\"access_"
3030 "token\"}},\"quota_project_id\":\"quota_project_id\","
3031 "\"client_id\":\"client_id\",\"client_secret\":\"client_secret\"}";
3032 json = JsonParse(options_string2);
3033 ASSERT_TRUE(json.ok()) << json.status();
3034 creds = ExternalAccountCredentials::Create(*json, {"scope1", "scope2"});
3035 grpc_error_get_str(creds.status(), StatusStrProperty::kDescription,
3036 &actual_error);
3037 EXPECT_EQ("token_lifetime_seconds must be less than 43200s", actual_error);
3038 }
3039
TEST_F(ExternalAccountCredentialsTest,FailureInvalidTokenUrl)3040 TEST_F(ExternalAccountCredentialsTest, FailureInvalidTokenUrl) {
3041 ExecCtx exec_ctx;
3042 Json credential_source = Json::FromString("");
3043 TestExternalAccountCredentials::ServiceAccountImpersonation
3044 service_account_impersonation;
3045 service_account_impersonation.token_lifetime_seconds = 3600;
3046 TestExternalAccountCredentials::Options options = {
3047 "external_account", // type;
3048 "audience", // audience;
3049 "subject_token_type", // subject_token_type;
3050 "https://foo.com:5555/service_account_impersonation", // service_account_impersonation_url;
3051 service_account_impersonation, // service_account_impersonation;
3052 "invalid_token_url", // token_url;
3053 "https://foo.com:5555/token_info", // token_info_url;
3054 credential_source, // credential_source;
3055 "quota_project_id", // quota_project_id;
3056 "client_id", // client_id;
3057 "client_secret", // client_secret;
3058 "", // workforce_pool_user_project;
3059 };
3060 auto creds = MakeRefCounted<TestExternalAccountCredentials>(
3061 options, std::vector<std::string>(), event_engine_);
3062 HttpRequest::SetOverride(httpcli_get_should_not_be_called,
3063 httpcli_post_should_not_be_called,
3064 httpcli_put_should_not_be_called);
3065 // TODO(roth): This should return UNAUTHENTICATED.
3066 grpc_error_handle expected_error = absl::UnknownError(
3067 "error fetching oauth2 token: Invalid token url: "
3068 "invalid_token_url. Error: INVALID_ARGUMENT: Could not parse "
3069 "'scheme' from uri 'invalid_token_url'. Scheme not found.");
3070 auto state = RequestMetadataState::NewInstance(expected_error, {});
3071 state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority,
3072 kTestPath);
3073 ExecCtx::Get()->Flush();
3074 event_engine_->TickUntilIdle();
3075 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
3076 }
3077
TEST_F(ExternalAccountCredentialsTest,FailureInvalidServiceAccountImpersonationUrl)3078 TEST_F(ExternalAccountCredentialsTest,
3079 FailureInvalidServiceAccountImpersonationUrl) {
3080 ExecCtx exec_ctx;
3081 Json credential_source = Json::FromString("");
3082 TestExternalAccountCredentials::ServiceAccountImpersonation
3083 service_account_impersonation;
3084 service_account_impersonation.token_lifetime_seconds = 3600;
3085 TestExternalAccountCredentials::Options options = {
3086 "external_account", // type;
3087 "audience", // audience;
3088 "subject_token_type", // subject_token_type;
3089 "invalid_service_account_impersonation_url", // service_account_impersonation_url;
3090 service_account_impersonation, // service_account_impersonation;
3091 "https://foo.com:5555/token", // token_url;
3092 "https://foo.com:5555/token_info", // token_info_url;
3093 credential_source, // credential_source;
3094 "quota_project_id", // quota_project_id;
3095 "client_id", // client_id;
3096 "client_secret", // client_secret;
3097 "", // workforce_pool_user_project;
3098 };
3099 auto creds = MakeRefCounted<TestExternalAccountCredentials>(
3100 options, std::vector<std::string>(), event_engine_);
3101 HttpRequest::SetOverride(httpcli_get_should_not_be_called,
3102 external_account_creds_httpcli_post_success,
3103 httpcli_put_should_not_be_called);
3104 // TODO(roth): This should return UNAUTHENTICATED.
3105 grpc_error_handle expected_error = absl::UnknownError(
3106 "error fetching oauth2 token: Invalid service account impersonation url: "
3107 "invalid_service_account_impersonation_url. Error: INVALID_ARGUMENT: "
3108 "Could not parse 'scheme' from uri "
3109 "'invalid_service_account_impersonation_url'. Scheme not found.");
3110 auto state = RequestMetadataState::NewInstance(expected_error, {});
3111 state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority,
3112 kTestPath);
3113 ExecCtx::Get()->Flush();
3114 event_engine_->TickUntilIdle();
3115 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
3116 }
3117
TEST_F(ExternalAccountCredentialsTest,FailureTokenExchangeResponseMissingAccessToken)3118 TEST_F(ExternalAccountCredentialsTest,
3119 FailureTokenExchangeResponseMissingAccessToken) {
3120 ExecCtx exec_ctx;
3121 Json credential_source = Json::FromString("");
3122 TestExternalAccountCredentials::ServiceAccountImpersonation
3123 service_account_impersonation;
3124 service_account_impersonation.token_lifetime_seconds = 3600;
3125 TestExternalAccountCredentials::Options options = {
3126 "external_account", // type;
3127 "audience", // audience;
3128 "subject_token_type", // subject_token_type;
3129 "https://foo.com:5555/service_account_impersonation", // service_account_impersonation_url;
3130 service_account_impersonation, // service_account_impersonation;
3131 "https://foo.com:5555/token", // token_url;
3132 "https://foo.com:5555/token_info", // token_info_url;
3133 credential_source, // credential_source
3134 "quota_project_id", // quota_project_id;
3135 "client_id", // client_id;
3136 "client_secret", // client_secret;
3137 "", // workforce_pool_user_project;
3138 };
3139 auto creds = MakeRefCounted<TestExternalAccountCredentials>(
3140 options, std::vector<std::string>(), event_engine_);
3141 HttpRequest::SetOverride(
3142 httpcli_get_should_not_be_called,
3143 external_account_creds_httpcli_post_failure_token_exchange_response_missing_access_token,
3144 httpcli_put_should_not_be_called);
3145 // TODO(roth): This should return UNAUTHENTICATED.
3146 grpc_error_handle expected_error = absl::UnknownError(
3147 "error fetching oauth2 token: Missing or invalid access_token in "
3148 "{\"not_access_token\":\"not_access_token\",\"expires_in\":3599, "
3149 "\"token_type\":\"Bearer\"}.");
3150 auto state = RequestMetadataState::NewInstance(expected_error, {});
3151 state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority,
3152 kTestPath);
3153 ExecCtx::Get()->Flush();
3154 event_engine_->TickUntilIdle();
3155 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
3156 }
3157
TEST_F(ExternalAccountCredentialsTest,UrlExternalAccountCredsSuccessFormatText)3158 TEST_F(ExternalAccountCredentialsTest,
3159 UrlExternalAccountCredsSuccessFormatText) {
3160 ExecCtx exec_ctx;
3161 auto credential_source = JsonParse(
3162 valid_url_external_account_creds_options_credential_source_format_text);
3163 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
3164 TestExternalAccountCredentials::ServiceAccountImpersonation
3165 service_account_impersonation;
3166 service_account_impersonation.token_lifetime_seconds = 3600;
3167 ExternalAccountCredentials::Options options = {
3168 "external_account", // type;
3169 "audience", // audience;
3170 "subject_token_type", // subject_token_type;
3171 "", // service_account_impersonation_url;
3172 service_account_impersonation, // service_account_impersonation;
3173 "https://foo.com:5555/token", // token_url;
3174 "https://foo.com:5555/token_info", // token_info_url;
3175 *credential_source, // credential_source;
3176 "quota_project_id", // quota_project_id;
3177 "client_id", // client_id;
3178 "client_secret", // client_secret;
3179 "", // workforce_pool_user_project;
3180 };
3181 auto creds =
3182 UrlExternalAccountCredentials::Create(options, {}, event_engine_);
3183 ASSERT_TRUE(creds.ok()) << creds.status();
3184 ASSERT_NE(*creds, nullptr);
3185 EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
3186 auto state = RequestMetadataState::NewInstance(
3187 absl::OkStatus(), "authorization: Bearer token_exchange_access_token");
3188 HttpRequest::SetOverride(url_external_account_creds_httpcli_get_success,
3189 external_account_creds_httpcli_post_success,
3190 httpcli_put_should_not_be_called);
3191 state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority,
3192 kTestPath);
3193 ExecCtx::Get()->Flush();
3194 event_engine_->TickUntilIdle();
3195 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
3196 }
3197
TEST_F(ExternalAccountCredentialsTest,UrlExternalAccountCredsSuccessWithQueryParamsFormatText)3198 TEST_F(ExternalAccountCredentialsTest,
3199 UrlExternalAccountCredsSuccessWithQueryParamsFormatText) {
3200 std::map<std::string, std::string> emd = {
3201 {"authorization", "Bearer token_exchange_access_token"}};
3202 ExecCtx exec_ctx;
3203 auto credential_source = JsonParse(
3204 valid_url_external_account_creds_options_credential_source_with_query_params_format_text);
3205 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
3206 TestExternalAccountCredentials::ServiceAccountImpersonation
3207 service_account_impersonation;
3208 service_account_impersonation.token_lifetime_seconds = 3600;
3209 ExternalAccountCredentials::Options options = {
3210 "external_account", // type;
3211 "audience", // audience;
3212 "subject_token_type", // subject_token_type;
3213 "", // service_account_impersonation_url;
3214 service_account_impersonation, // service_account_impersonation;
3215 "https://foo.com:5555/token", // token_url;
3216 "https://foo.com:5555/token_info", // token_info_url;
3217 *credential_source, // credential_source;
3218 "quota_project_id", // quota_project_id;
3219 "client_id", // client_id;
3220 "client_secret", // client_secret;
3221 "", // workforce_pool_user_project;
3222 };
3223 auto creds =
3224 UrlExternalAccountCredentials::Create(options, {}, event_engine_);
3225 ASSERT_TRUE(creds.ok()) << creds.status();
3226 ASSERT_NE(*creds, nullptr);
3227 EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
3228 auto state = RequestMetadataState::NewInstance(
3229 absl::OkStatus(), "authorization: Bearer token_exchange_access_token");
3230 HttpRequest::SetOverride(url_external_account_creds_httpcli_get_success,
3231 external_account_creds_httpcli_post_success,
3232 httpcli_put_should_not_be_called);
3233 state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority,
3234 kTestPath);
3235 ExecCtx::Get()->Flush();
3236 event_engine_->TickUntilIdle();
3237 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
3238 }
3239
TEST_F(ExternalAccountCredentialsTest,UrlExternalAccountCredsSuccessFormatJson)3240 TEST_F(ExternalAccountCredentialsTest,
3241 UrlExternalAccountCredsSuccessFormatJson) {
3242 ExecCtx exec_ctx;
3243 auto credential_source = JsonParse(
3244 valid_url_external_account_creds_options_credential_source_format_json);
3245 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
3246 TestExternalAccountCredentials::ServiceAccountImpersonation
3247 service_account_impersonation;
3248 service_account_impersonation.token_lifetime_seconds = 3600;
3249 ExternalAccountCredentials::Options options = {
3250 "external_account", // type;
3251 "audience", // audience;
3252 "subject_token_type", // subject_token_type;
3253 "", // service_account_impersonation_url;
3254 service_account_impersonation, // service_account_impersonation;
3255 "https://foo.com:5555/token", // token_url;
3256 "https://foo.com:5555/token_info", // token_info_url;
3257 *credential_source, // credential_source;
3258 "quota_project_id", // quota_project_id;
3259 "client_id", // client_id;
3260 "client_secret", // client_secret;
3261 "", // workforce_pool_user_project;
3262 };
3263 auto creds =
3264 UrlExternalAccountCredentials::Create(options, {}, event_engine_);
3265 ASSERT_TRUE(creds.ok()) << creds.status();
3266 ASSERT_NE(*creds, nullptr);
3267 EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
3268 auto state = RequestMetadataState::NewInstance(
3269 absl::OkStatus(), "authorization: Bearer token_exchange_access_token");
3270 HttpRequest::SetOverride(url_external_account_creds_httpcli_get_success,
3271 external_account_creds_httpcli_post_success,
3272 httpcli_put_should_not_be_called);
3273 state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority,
3274 kTestPath);
3275 ExecCtx::Get()->Flush();
3276 event_engine_->TickUntilIdle();
3277 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
3278 }
3279
TEST_F(ExternalAccountCredentialsTest,UrlExternalAccountCredsFailureInvalidCredentialSourceUrl)3280 TEST_F(ExternalAccountCredentialsTest,
3281 UrlExternalAccountCredsFailureInvalidCredentialSourceUrl) {
3282 auto credential_source =
3283 JsonParse(invalid_url_external_account_creds_options_credential_source);
3284 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
3285 TestExternalAccountCredentials::ServiceAccountImpersonation
3286 service_account_impersonation;
3287 service_account_impersonation.token_lifetime_seconds = 3600;
3288 ExternalAccountCredentials::Options options = {
3289 "external_account", // type;
3290 "audience", // audience;
3291 "subject_token_type", // subject_token_type;
3292 "", // service_account_impersonation_url;
3293 service_account_impersonation, // service_account_impersonation;
3294 "https://foo.com:5555/token", // token_url;
3295 "https://foo.com:5555/token_info", // token_info_url;
3296 *credential_source, // credential_source;
3297 "quota_project_id", // quota_project_id;
3298 "client_id", // client_id;
3299 "client_secret", // client_secret;
3300 "", // workforce_pool_user_project;
3301 };
3302 auto creds = UrlExternalAccountCredentials::Create(options, {});
3303 ASSERT_FALSE(creds.ok());
3304 std::string actual_error;
3305 grpc_error_get_str(creds.status(), StatusStrProperty::kDescription,
3306 &actual_error);
3307 EXPECT_THAT(actual_error,
3308 ::testing::StartsWith("Invalid credential source url."));
3309 }
3310
TEST_F(ExternalAccountCredentialsTest,FileExternalAccountCredsSuccessFormatText)3311 TEST_F(ExternalAccountCredentialsTest,
3312 FileExternalAccountCredsSuccessFormatText) {
3313 ExecCtx exec_ctx;
3314 char* subject_token_path = write_tmp_jwt_file("test_subject_token");
3315 auto credential_source = JsonParse(absl::StrFormat(
3316 "{\"file\":\"%s\"}",
3317 absl::StrReplaceAll(subject_token_path, {{"\\", "\\\\"}})));
3318 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
3319 TestExternalAccountCredentials::ServiceAccountImpersonation
3320 service_account_impersonation;
3321 service_account_impersonation.token_lifetime_seconds = 3600;
3322 ExternalAccountCredentials::Options options = {
3323 "external_account", // type;
3324 "audience", // audience;
3325 "subject_token_type", // subject_token_type;
3326 "", // service_account_impersonation_url;
3327 service_account_impersonation, // service_account_impersonation;
3328 "https://foo.com:5555/token", // token_url;
3329 "https://foo.com:5555/token_info", // token_info_url;
3330 *credential_source, // credential_source;
3331 "quota_project_id", // quota_project_id;
3332 "client_id", // client_id;
3333 "client_secret", // client_secret;
3334 "", // workforce_pool_user_project;
3335 };
3336 auto creds =
3337 FileExternalAccountCredentials::Create(options, {}, event_engine_);
3338 ASSERT_TRUE(creds.ok()) << creds.status();
3339 ASSERT_NE(*creds, nullptr);
3340 EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
3341 auto state = RequestMetadataState::NewInstance(
3342 absl::OkStatus(), "authorization: Bearer token_exchange_access_token");
3343 HttpRequest::SetOverride(httpcli_get_should_not_be_called,
3344 external_account_creds_httpcli_post_success,
3345 httpcli_put_should_not_be_called);
3346 state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority,
3347 kTestPath);
3348 ExecCtx::Get()->Flush();
3349 event_engine_->TickUntilIdle();
3350 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
3351 gpr_free(subject_token_path);
3352 }
3353
TEST_F(ExternalAccountCredentialsTest,FileExternalAccountCredsSuccessFormatJson)3354 TEST_F(ExternalAccountCredentialsTest,
3355 FileExternalAccountCredsSuccessFormatJson) {
3356 ExecCtx exec_ctx;
3357 char* subject_token_path =
3358 write_tmp_jwt_file("{\"access_token\":\"test_subject_token\"}");
3359 auto credential_source = JsonParse(absl::StrFormat(
3360 "{\n"
3361 "\"file\":\"%s\",\n"
3362 "\"format\":\n"
3363 "{\n"
3364 "\"type\":\"json\",\n"
3365 "\"subject_token_field_name\":\"access_token\"\n"
3366 "}\n"
3367 "}",
3368 absl::StrReplaceAll(subject_token_path, {{"\\", "\\\\"}})));
3369 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
3370 TestExternalAccountCredentials::ServiceAccountImpersonation
3371 service_account_impersonation;
3372 service_account_impersonation.token_lifetime_seconds = 3600;
3373 ExternalAccountCredentials::Options options = {
3374 "external_account", // type;
3375 "audience", // audience;
3376 "subject_token_type", // subject_token_type;
3377 "", // service_account_impersonation_url;
3378 service_account_impersonation, // service_account_impersonation;
3379 "https://foo.com:5555/token", // token_url;
3380 "https://foo.com:5555/token_info", // token_info_url;
3381 *credential_source, // credential_source;
3382 "quota_project_id", // quota_project_id;
3383 "client_id", // client_id;
3384 "client_secret", // client_secret;
3385 "", // workforce_pool_user_project;
3386 };
3387 auto creds =
3388 FileExternalAccountCredentials::Create(options, {}, event_engine_);
3389 ASSERT_TRUE(creds.ok()) << creds.status();
3390 ASSERT_NE(*creds, nullptr);
3391 EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
3392 auto state = RequestMetadataState::NewInstance(
3393 absl::OkStatus(), "authorization: Bearer token_exchange_access_token");
3394 HttpRequest::SetOverride(httpcli_get_should_not_be_called,
3395 external_account_creds_httpcli_post_success,
3396 httpcli_put_should_not_be_called);
3397 state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority,
3398 kTestPath);
3399 ExecCtx::Get()->Flush();
3400 event_engine_->TickUntilIdle();
3401 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
3402 gpr_free(subject_token_path);
3403 }
3404
TEST_F(ExternalAccountCredentialsTest,FileExternalAccountCredsFailureFileNotFound)3405 TEST_F(ExternalAccountCredentialsTest,
3406 FileExternalAccountCredsFailureFileNotFound) {
3407 ExecCtx exec_ctx;
3408 auto credential_source = JsonParse("{\"file\":\"non_exisiting_file\"}");
3409 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
3410 TestExternalAccountCredentials::ServiceAccountImpersonation
3411 service_account_impersonation;
3412 service_account_impersonation.token_lifetime_seconds = 3600;
3413 ExternalAccountCredentials::Options options = {
3414 "external_account", // type;
3415 "audience", // audience;
3416 "subject_token_type", // subject_token_type;
3417 "", // service_account_impersonation_url;
3418 service_account_impersonation, // service_account_impersonation;
3419 "https://foo.com:5555/token", // token_url;
3420 "https://foo.com:5555/token_info", // token_info_url;
3421 *credential_source, // credential_source;
3422 "quota_project_id", // quota_project_id;
3423 "client_id", // client_id;
3424 "client_secret", // client_secret;
3425 "", // workforce_pool_user_project;
3426 };
3427 auto creds =
3428 FileExternalAccountCredentials::Create(options, {}, event_engine_);
3429 ASSERT_TRUE(creds.ok()) << creds.status();
3430 ASSERT_NE(*creds, nullptr);
3431 HttpRequest::SetOverride(httpcli_get_should_not_be_called,
3432 httpcli_post_should_not_be_called,
3433 httpcli_put_should_not_be_called);
3434 // TODO(roth): This should return UNAVAILABLE.
3435 grpc_error_handle expected_error = absl::InternalError(
3436 "error fetching oauth2 token: Failed to load file: "
3437 "non_exisiting_file due to error(fdopen): No such file or directory");
3438 auto state = RequestMetadataState::NewInstance(expected_error, {});
3439 state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority,
3440 kTestPath);
3441 ExecCtx::Get()->Flush();
3442 event_engine_->TickUntilIdle();
3443 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
3444 }
3445
TEST_F(ExternalAccountCredentialsTest,FileExternalAccountCredsFailureInvalidJsonContent)3446 TEST_F(ExternalAccountCredentialsTest,
3447 FileExternalAccountCredsFailureInvalidJsonContent) {
3448 ExecCtx exec_ctx;
3449 char* subject_token_path = write_tmp_jwt_file("not_a_valid_json_file");
3450 auto credential_source = JsonParse(absl::StrFormat(
3451 "{\n"
3452 "\"file\":\"%s\",\n"
3453 "\"format\":\n"
3454 "{\n"
3455 "\"type\":\"json\",\n"
3456 "\"subject_token_field_name\":\"access_token\"\n"
3457 "}\n"
3458 "}",
3459 absl::StrReplaceAll(subject_token_path, {{"\\", "\\\\"}})));
3460 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
3461 TestExternalAccountCredentials::ServiceAccountImpersonation
3462 service_account_impersonation;
3463 service_account_impersonation.token_lifetime_seconds = 3600;
3464 ExternalAccountCredentials::Options options = {
3465 "external_account", // type;
3466 "audience", // audience;
3467 "subject_token_type", // subject_token_type;
3468 "", // service_account_impersonation_url;
3469 service_account_impersonation, // service_account_impersonation;
3470 "https://foo.com:5555/token", // token_url;
3471 "https://foo.com:5555/token_info", // token_info_url;
3472 *credential_source, // credential_source;
3473 "quota_project_id", // quota_project_id;
3474 "client_id", // client_id;
3475 "client_secret", // client_secret;
3476 "", // workforce_pool_user_project;
3477 };
3478 auto creds =
3479 FileExternalAccountCredentials::Create(options, {}, event_engine_);
3480 ASSERT_TRUE(creds.ok()) << creds.status();
3481 ASSERT_NE(*creds, nullptr);
3482 HttpRequest::SetOverride(httpcli_get_should_not_be_called,
3483 httpcli_post_should_not_be_called,
3484 httpcli_put_should_not_be_called);
3485 // TODO(roth): This should return UNAUTHENTICATED.
3486 grpc_error_handle expected_error = absl::UnknownError(
3487 "error fetching oauth2 token: The content of the file is not a "
3488 "valid json object.");
3489 auto state = RequestMetadataState::NewInstance(expected_error, {});
3490 state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority,
3491 kTestPath);
3492 ExecCtx::Get()->Flush();
3493 event_engine_->TickUntilIdle();
3494 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
3495 gpr_free(subject_token_path);
3496 }
3497
TEST_F(ExternalAccountCredentialsTest,AwsExternalAccountCredsSuccess)3498 TEST_F(ExternalAccountCredentialsTest, AwsExternalAccountCredsSuccess) {
3499 ExecCtx exec_ctx;
3500 auto credential_source =
3501 JsonParse(valid_aws_external_account_creds_options_credential_source);
3502 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
3503 TestExternalAccountCredentials::ServiceAccountImpersonation
3504 service_account_impersonation;
3505 service_account_impersonation.token_lifetime_seconds = 3600;
3506 ExternalAccountCredentials::Options options = {
3507 "external_account", // type;
3508 "audience", // audience;
3509 "subject_token_type", // subject_token_type;
3510 "", // service_account_impersonation_url;
3511 service_account_impersonation, // service_account_impersonation;
3512 "https://foo.com:5555/token", // token_url;
3513 "https://foo.com:5555/token_info", // token_info_url;
3514 *credential_source, // credential_source;
3515 "quota_project_id", // quota_project_id;
3516 "client_id", // client_id;
3517 "client_secret", // client_secret;
3518 "", // workforce_pool_user_project;
3519 };
3520 auto creds =
3521 AwsExternalAccountCredentials::Create(options, {}, event_engine_);
3522 ASSERT_TRUE(creds.ok()) << creds.status();
3523 ASSERT_NE(*creds, nullptr);
3524 EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
3525 auto state = RequestMetadataState::NewInstance(
3526 absl::OkStatus(), "authorization: Bearer token_exchange_access_token");
3527 HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success,
3528 aws_external_account_creds_httpcli_post_success,
3529 httpcli_put_should_not_be_called);
3530 state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority,
3531 kTestPath);
3532 ExecCtx::Get()->Flush();
3533 event_engine_->TickUntilIdle();
3534 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
3535 }
3536
TEST_F(ExternalAccountCredentialsTest,AwsImdsv2ExternalAccountCredsSuccess)3537 TEST_F(ExternalAccountCredentialsTest, AwsImdsv2ExternalAccountCredsSuccess) {
3538 ExecCtx exec_ctx;
3539 auto credential_source = JsonParse(
3540 valid_aws_imdsv2_external_account_creds_options_credential_source);
3541 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
3542 TestExternalAccountCredentials::ServiceAccountImpersonation
3543 service_account_impersonation;
3544 service_account_impersonation.token_lifetime_seconds = 3600;
3545 ExternalAccountCredentials::Options options = {
3546 "external_account", // type;
3547 "audience", // audience;
3548 "subject_token_type", // subject_token_type;
3549 "", // service_account_impersonation_url;
3550 service_account_impersonation, // service_account_impersonation;
3551 "https://foo.com:5555/token", // token_url;
3552 "https://foo.com:5555/token_info", // token_info_url;
3553 *credential_source, // credential_source;
3554 "quota_project_id", // quota_project_id;
3555 "client_id", // client_id;
3556 "client_secret", // client_secret;
3557 "", // workforce_pool_user_project;
3558 };
3559 auto creds =
3560 AwsExternalAccountCredentials::Create(options, {}, event_engine_);
3561 ASSERT_TRUE(creds.ok()) << creds.status();
3562 ASSERT_NE(*creds, nullptr);
3563 EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
3564 auto state = RequestMetadataState::NewInstance(
3565 absl::OkStatus(), "authorization: Bearer token_exchange_access_token");
3566 HttpRequest::SetOverride(
3567 aws_imdsv2_external_account_creds_httpcli_get_success,
3568 aws_external_account_creds_httpcli_post_success,
3569 aws_imdsv2_external_account_creds_httpcli_put_success);
3570 state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority,
3571 kTestPath);
3572 ExecCtx::Get()->Flush();
3573 event_engine_->TickUntilIdle();
3574 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
3575 }
3576
TEST_F(ExternalAccountCredentialsTest,AwsImdsv2ExternalAccountCredShouldNotUseMetadataServer)3577 TEST_F(ExternalAccountCredentialsTest,
3578 AwsImdsv2ExternalAccountCredShouldNotUseMetadataServer) {
3579 ExecCtx exec_ctx;
3580 SetEnv("AWS_REGION", "test_regionz");
3581 SetEnv("AWS_ACCESS_KEY_ID", "test_access_key_id");
3582 SetEnv("AWS_SECRET_ACCESS_KEY", "test_secret_access_key");
3583 SetEnv("AWS_SESSION_TOKEN", "test_token");
3584 auto credential_source = JsonParse(
3585 valid_aws_imdsv2_external_account_creds_options_credential_source);
3586 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
3587 TestExternalAccountCredentials::ServiceAccountImpersonation
3588 service_account_impersonation;
3589 service_account_impersonation.token_lifetime_seconds = 3600;
3590 ExternalAccountCredentials::Options options = {
3591 "external_account", // type;
3592 "audience", // audience;
3593 "subject_token_type", // subject_token_type;
3594 "", // service_account_impersonation_url;
3595 service_account_impersonation, // service_account_impersonation;
3596 "https://foo.com:5555/token", // token_url;
3597 "https://foo.com:5555/token_info", // token_info_url;
3598 *credential_source, // credential_source;
3599 "quota_project_id", // quota_project_id;
3600 "client_id", // client_id;
3601 "client_secret", // client_secret;
3602 "", // workforce_pool_user_project;
3603 };
3604 auto creds =
3605 AwsExternalAccountCredentials::Create(options, {}, event_engine_);
3606 ASSERT_TRUE(creds.ok()) << creds.status();
3607 ASSERT_NE(*creds, nullptr);
3608 EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
3609 auto state = RequestMetadataState::NewInstance(
3610 absl::OkStatus(), "authorization: Bearer token_exchange_access_token");
3611 HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success,
3612 aws_external_account_creds_httpcli_post_success,
3613 httpcli_put_should_not_be_called);
3614 state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority,
3615 kTestPath);
3616 ExecCtx::Get()->Flush();
3617 event_engine_->TickUntilIdle();
3618 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
3619 UnsetEnv("AWS_REGION");
3620 UnsetEnv("AWS_ACCESS_KEY_ID");
3621 UnsetEnv("AWS_SECRET_ACCESS_KEY");
3622 UnsetEnv("AWS_SESSION_TOKEN");
3623 }
3624
TEST_F(ExternalAccountCredentialsTest,AwsImdsv2ExternalAccountCredShouldNotUseMetadataServerOptionalTokenMissing)3625 TEST_F(
3626 ExternalAccountCredentialsTest,
3627 AwsImdsv2ExternalAccountCredShouldNotUseMetadataServerOptionalTokenMissing) {
3628 ExecCtx exec_ctx;
3629 SetEnv("AWS_REGION", "test_regionz");
3630 SetEnv("AWS_ACCESS_KEY_ID", "test_access_key_id");
3631 SetEnv("AWS_SECRET_ACCESS_KEY", "test_secret_access_key");
3632 auto credential_source = JsonParse(
3633 valid_aws_imdsv2_external_account_creds_options_credential_source);
3634 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
3635 TestExternalAccountCredentials::ServiceAccountImpersonation
3636 service_account_impersonation;
3637 service_account_impersonation.token_lifetime_seconds = 3600;
3638 ExternalAccountCredentials::Options options = {
3639 "external_account", // type;
3640 "audience", // audience;
3641 "subject_token_type", // subject_token_type;
3642 "", // service_account_impersonation_url;
3643 service_account_impersonation, // service_account_impersonation;
3644 "https://foo.com:5555/token", // token_url;
3645 "https://foo.com:5555/token_info", // token_info_url;
3646 *credential_source, // credential_source;
3647 "quota_project_id", // quota_project_id;
3648 "client_id", // client_id;
3649 "client_secret", // client_secret;
3650 "", // workforce_pool_user_project;
3651 };
3652 auto creds =
3653 AwsExternalAccountCredentials::Create(options, {}, event_engine_);
3654 ASSERT_TRUE(creds.ok()) << creds.status();
3655 ASSERT_NE(*creds, nullptr);
3656 EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
3657 auto state = RequestMetadataState::NewInstance(
3658 absl::OkStatus(), "authorization: Bearer token_exchange_access_token");
3659 HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success,
3660 aws_external_account_creds_httpcli_post_success,
3661 httpcli_put_should_not_be_called);
3662 state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority,
3663 kTestPath);
3664 ExecCtx::Get()->Flush();
3665 event_engine_->TickUntilIdle();
3666 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
3667 UnsetEnv("AWS_REGION");
3668 UnsetEnv("AWS_ACCESS_KEY_ID");
3669 UnsetEnv("AWS_SECRET_ACCESS_KEY");
3670 }
3671
TEST_F(ExternalAccountCredentialsTest,AwsExternalAccountCredsSuccessIpv6)3672 TEST_F(ExternalAccountCredentialsTest, AwsExternalAccountCredsSuccessIpv6) {
3673 ExecCtx exec_ctx;
3674 auto credential_source = JsonParse(
3675 valid_aws_external_account_creds_options_credential_source_ipv6);
3676 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
3677 TestExternalAccountCredentials::ServiceAccountImpersonation
3678 service_account_impersonation;
3679 service_account_impersonation.token_lifetime_seconds = 3600;
3680 ExternalAccountCredentials::Options options = {
3681 "external_account", // type;
3682 "audience", // audience;
3683 "subject_token_type", // subject_token_type;
3684 "", // service_account_impersonation_url;
3685 service_account_impersonation, // service_account_impersonation;
3686 "https://foo.com:5555/token", // token_url;
3687 "https://foo.com:5555/token_info", // token_info_url;
3688 *credential_source, // credential_source;
3689 "quota_project_id", // quota_project_id;
3690 "client_id", // client_id;
3691 "client_secret", // client_secret;
3692 "", // workforce_pool_user_project;
3693 };
3694 auto creds =
3695 AwsExternalAccountCredentials::Create(options, {}, event_engine_);
3696 ASSERT_TRUE(creds.ok()) << creds.status();
3697 ASSERT_NE(*creds, nullptr);
3698 EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
3699 auto state = RequestMetadataState::NewInstance(
3700 absl::OkStatus(), "authorization: Bearer token_exchange_access_token");
3701 HttpRequest::SetOverride(
3702 aws_imdsv2_external_account_creds_httpcli_get_success,
3703 aws_external_account_creds_httpcli_post_success,
3704 aws_imdsv2_external_account_creds_httpcli_put_success);
3705 state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority,
3706 kTestPath);
3707 ExecCtx::Get()->Flush();
3708 event_engine_->TickUntilIdle();
3709 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
3710 }
3711
TEST_F(ExternalAccountCredentialsTest,AwsExternalAccountCredsSuccessPathRegionEnvKeysUrl)3712 TEST_F(ExternalAccountCredentialsTest,
3713 AwsExternalAccountCredsSuccessPathRegionEnvKeysUrl) {
3714 ExecCtx exec_ctx;
3715 SetEnv("AWS_REGION", "test_regionz");
3716 auto credential_source =
3717 JsonParse(valid_aws_external_account_creds_options_credential_source);
3718 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
3719 TestExternalAccountCredentials::ServiceAccountImpersonation
3720 service_account_impersonation;
3721 service_account_impersonation.token_lifetime_seconds = 3600;
3722 ExternalAccountCredentials::Options options = {
3723 "external_account", // type;
3724 "audience", // audience;
3725 "subject_token_type", // subject_token_type;
3726 "", // service_account_impersonation_url;
3727 service_account_impersonation, // service_account_impersonation;
3728 "https://foo.com:5555/token", // token_url;
3729 "https://foo.com:5555/token_info", // token_info_url;
3730 *credential_source, // credential_source;
3731 "quota_project_id", // quota_project_id;
3732 "client_id", // client_id;
3733 "client_secret", // client_secret;
3734 "", // workforce_pool_user_project;
3735 };
3736 auto creds =
3737 AwsExternalAccountCredentials::Create(options, {}, event_engine_);
3738 ASSERT_TRUE(creds.ok()) << creds.status();
3739 ASSERT_NE(*creds, nullptr);
3740 EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
3741 auto state = RequestMetadataState::NewInstance(
3742 absl::OkStatus(), "authorization: Bearer token_exchange_access_token");
3743 HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success,
3744 aws_external_account_creds_httpcli_post_success,
3745 httpcli_put_should_not_be_called);
3746 state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority,
3747 kTestPath);
3748 ExecCtx::Get()->Flush();
3749 event_engine_->TickUntilIdle();
3750 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
3751 UnsetEnv("AWS_REGION");
3752 }
3753
TEST_F(ExternalAccountCredentialsTest,AwsExternalAccountCredsSuccessPathDefaultRegionEnvKeysUrl)3754 TEST_F(ExternalAccountCredentialsTest,
3755 AwsExternalAccountCredsSuccessPathDefaultRegionEnvKeysUrl) {
3756 ExecCtx exec_ctx;
3757 SetEnv("AWS_DEFAULT_REGION", "test_regionz");
3758 auto credential_source =
3759 JsonParse(valid_aws_external_account_creds_options_credential_source);
3760 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
3761 TestExternalAccountCredentials::ServiceAccountImpersonation
3762 service_account_impersonation;
3763 service_account_impersonation.token_lifetime_seconds = 3600;
3764 ExternalAccountCredentials::Options options = {
3765 "external_account", // type;
3766 "audience", // audience;
3767 "subject_token_type", // subject_token_type;
3768 "", // service_account_impersonation_url;
3769 service_account_impersonation, // service_account_impersonation;
3770 "https://foo.com:5555/token", // token_url;
3771 "https://foo.com:5555/token_info", // token_info_url;
3772 *credential_source, // credential_source;
3773 "quota_project_id", // quota_project_id;
3774 "client_id", // client_id;
3775 "client_secret", // client_secret;
3776 "", // workforce_pool_user_project;
3777 };
3778 auto creds =
3779 AwsExternalAccountCredentials::Create(options, {}, event_engine_);
3780 ASSERT_TRUE(creds.ok()) << creds.status();
3781 ASSERT_NE(*creds, nullptr);
3782 EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
3783 auto state = RequestMetadataState::NewInstance(
3784 absl::OkStatus(), "authorization: Bearer token_exchange_access_token");
3785 HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success,
3786 aws_external_account_creds_httpcli_post_success,
3787 httpcli_put_should_not_be_called);
3788 state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority,
3789 kTestPath);
3790 ExecCtx::Get()->Flush();
3791 event_engine_->TickUntilIdle();
3792 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
3793 UnsetEnv("AWS_DEFAULT_REGION");
3794 }
3795
TEST_F(ExternalAccountCredentialsTest,AwsExternalAccountCredsSuccessPathDuplicateRegionEnvKeysUrl)3796 TEST_F(ExternalAccountCredentialsTest,
3797 AwsExternalAccountCredsSuccessPathDuplicateRegionEnvKeysUrl) {
3798 ExecCtx exec_ctx;
3799 // Make sure that AWS_REGION gets used over AWS_DEFAULT_REGION
3800 SetEnv("AWS_REGION", "test_regionz");
3801 SetEnv("AWS_DEFAULT_REGION", "ERROR_REGION");
3802 auto credential_source =
3803 JsonParse(valid_aws_external_account_creds_options_credential_source);
3804 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
3805 TestExternalAccountCredentials::ServiceAccountImpersonation
3806 service_account_impersonation;
3807 service_account_impersonation.token_lifetime_seconds = 3600;
3808 ExternalAccountCredentials::Options options = {
3809 "external_account", // type;
3810 "audience", // audience;
3811 "subject_token_type", // subject_token_type;
3812 "", // service_account_impersonation_url;
3813 service_account_impersonation, // service_account_impersonation;
3814 "https://foo.com:5555/token", // token_url;
3815 "https://foo.com:5555/token_info", // token_info_url;
3816 *credential_source, // credential_source;
3817 "quota_project_id", // quota_project_id;
3818 "client_id", // client_id;
3819 "client_secret", // client_secret;
3820 "", // workforce_pool_user_project;
3821 };
3822 auto creds =
3823 AwsExternalAccountCredentials::Create(options, {}, event_engine_);
3824 ASSERT_TRUE(creds.ok()) << creds.status();
3825 ASSERT_NE(*creds, nullptr);
3826 EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
3827 auto state = RequestMetadataState::NewInstance(
3828 absl::OkStatus(), "authorization: Bearer token_exchange_access_token");
3829 HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success,
3830 aws_external_account_creds_httpcli_post_success,
3831 httpcli_put_should_not_be_called);
3832 state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority,
3833 kTestPath);
3834 ExecCtx::Get()->Flush();
3835 event_engine_->TickUntilIdle();
3836 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
3837 UnsetEnv("AWS_REGION");
3838 UnsetEnv("AWS_DEFAULT_REGION");
3839 }
3840
TEST_F(ExternalAccountCredentialsTest,AwsExternalAccountCredsSuccessPathRegionUrlKeysEnv)3841 TEST_F(ExternalAccountCredentialsTest,
3842 AwsExternalAccountCredsSuccessPathRegionUrlKeysEnv) {
3843 ExecCtx exec_ctx;
3844 SetEnv("AWS_ACCESS_KEY_ID", "test_access_key_id");
3845 SetEnv("AWS_SECRET_ACCESS_KEY", "test_secret_access_key");
3846 SetEnv("AWS_SESSION_TOKEN", "test_token");
3847 auto credential_source =
3848 JsonParse(valid_aws_external_account_creds_options_credential_source);
3849 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
3850 TestExternalAccountCredentials::ServiceAccountImpersonation
3851 service_account_impersonation;
3852 service_account_impersonation.token_lifetime_seconds = 3600;
3853 ExternalAccountCredentials::Options options = {
3854 "external_account", // type;
3855 "audience", // audience;
3856 "subject_token_type", // subject_token_type;
3857 "", // service_account_impersonation_url;
3858 service_account_impersonation, // service_account_impersonation;
3859 "https://foo.com:5555/token", // token_url;
3860 "https://foo.com:5555/token_info", // token_info_url;
3861 *credential_source, // credential_source;
3862 "quota_project_id", // quota_project_id;
3863 "client_id", // client_id;
3864 "client_secret", // client_secret;
3865 "", // workforce_pool_user_project;
3866 };
3867 auto creds =
3868 AwsExternalAccountCredentials::Create(options, {}, event_engine_);
3869 ASSERT_TRUE(creds.ok()) << creds.status();
3870 ASSERT_NE(*creds, nullptr);
3871 EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
3872 auto state = RequestMetadataState::NewInstance(
3873 absl::OkStatus(), "authorization: Bearer token_exchange_access_token");
3874 HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success,
3875 aws_external_account_creds_httpcli_post_success,
3876 httpcli_put_should_not_be_called);
3877 state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority,
3878 kTestPath);
3879 ExecCtx::Get()->Flush();
3880 event_engine_->TickUntilIdle();
3881 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
3882 UnsetEnv("AWS_ACCESS_KEY_ID");
3883 UnsetEnv("AWS_SECRET_ACCESS_KEY");
3884 UnsetEnv("AWS_SESSION_TOKEN");
3885 }
3886
TEST_F(ExternalAccountCredentialsTest,AwsExternalAccountCredsSuccessPathRegionEnvKeysEnv)3887 TEST_F(ExternalAccountCredentialsTest,
3888 AwsExternalAccountCredsSuccessPathRegionEnvKeysEnv) {
3889 ExecCtx exec_ctx;
3890 SetEnv("AWS_REGION", "test_regionz");
3891 SetEnv("AWS_ACCESS_KEY_ID", "test_access_key_id");
3892 SetEnv("AWS_SECRET_ACCESS_KEY", "test_secret_access_key");
3893 SetEnv("AWS_SESSION_TOKEN", "test_token");
3894 auto credential_source =
3895 JsonParse(valid_aws_external_account_creds_options_credential_source);
3896 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
3897 TestExternalAccountCredentials::ServiceAccountImpersonation
3898 service_account_impersonation;
3899 service_account_impersonation.token_lifetime_seconds = 3600;
3900 ExternalAccountCredentials::Options options = {
3901 "external_account", // type;
3902 "audience", // audience;
3903 "subject_token_type", // subject_token_type;
3904 "", // service_account_impersonation_url;
3905 service_account_impersonation, // service_account_impersonation;
3906 "https://foo.com:5555/token", // token_url;
3907 "https://foo.com:5555/token_info", // token_info_url;
3908 *credential_source, // credential_source;
3909 "quota_project_id", // quota_project_id;
3910 "client_id", // client_id;
3911 "client_secret", // client_secret;
3912 "", // workforce_pool_user_project;
3913 };
3914 auto creds =
3915 AwsExternalAccountCredentials::Create(options, {}, event_engine_);
3916 ASSERT_TRUE(creds.ok()) << creds.status();
3917 ASSERT_NE(*creds, nullptr);
3918 EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
3919 auto state = RequestMetadataState::NewInstance(
3920 absl::OkStatus(), "authorization: Bearer token_exchange_access_token");
3921 HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success,
3922 aws_external_account_creds_httpcli_post_success,
3923 httpcli_put_should_not_be_called);
3924 state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority,
3925 kTestPath);
3926 ExecCtx::Get()->Flush();
3927 event_engine_->TickUntilIdle();
3928 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
3929 UnsetEnv("AWS_REGION");
3930 UnsetEnv("AWS_ACCESS_KEY_ID");
3931 UnsetEnv("AWS_SECRET_ACCESS_KEY");
3932 UnsetEnv("AWS_SESSION_TOKEN");
3933 }
3934
TEST_F(ExternalAccountCredentialsTest,AwsExternalAccountCredsSuccessPathDefaultRegionEnvKeysEnv)3935 TEST_F(ExternalAccountCredentialsTest,
3936 AwsExternalAccountCredsSuccessPathDefaultRegionEnvKeysEnv) {
3937 std::map<std::string, std::string> emd = {
3938 {"authorization", "Bearer token_exchange_access_token"}};
3939 ExecCtx exec_ctx;
3940 SetEnv("AWS_DEFAULT_REGION", "test_regionz");
3941 SetEnv("AWS_ACCESS_KEY_ID", "test_access_key_id");
3942 SetEnv("AWS_SECRET_ACCESS_KEY", "test_secret_access_key");
3943 SetEnv("AWS_SESSION_TOKEN", "test_token");
3944 auto credential_source =
3945 JsonParse(valid_aws_external_account_creds_options_credential_source);
3946 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
3947 TestExternalAccountCredentials::ServiceAccountImpersonation
3948 service_account_impersonation;
3949 service_account_impersonation.token_lifetime_seconds = 3600;
3950 ExternalAccountCredentials::Options options = {
3951 "external_account", // type;
3952 "audience", // audience;
3953 "subject_token_type", // subject_token_type;
3954 "", // service_account_impersonation_url;
3955 service_account_impersonation, // service_account_impersonation;
3956 "https://foo.com:5555/token", // token_url;
3957 "https://foo.com:5555/token_info", // token_info_url;
3958 *credential_source, // credential_source;
3959 "quota_project_id", // quota_project_id;
3960 "client_id", // client_id;
3961 "client_secret", // client_secret;
3962 "", // workforce_pool_user_project;
3963 };
3964 auto creds =
3965 AwsExternalAccountCredentials::Create(options, {}, event_engine_);
3966 ASSERT_TRUE(creds.ok()) << creds.status();
3967 ASSERT_NE(*creds, nullptr);
3968 EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
3969 auto state = RequestMetadataState::NewInstance(
3970 absl::OkStatus(), "authorization: Bearer token_exchange_access_token");
3971 HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success,
3972 aws_external_account_creds_httpcli_post_success,
3973 httpcli_put_should_not_be_called);
3974 state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority,
3975 kTestPath);
3976 ExecCtx::Get()->Flush();
3977 event_engine_->TickUntilIdle();
3978 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
3979 UnsetEnv("AWS_DEFAULT_REGION");
3980 UnsetEnv("AWS_ACCESS_KEY_ID");
3981 UnsetEnv("AWS_SECRET_ACCESS_KEY");
3982 UnsetEnv("AWS_SESSION_TOKEN");
3983 }
3984
TEST_F(ExternalAccountCredentialsTest,AwsExternalAccountCredsSuccessPathDuplicateRegionEnvKeysEnv)3985 TEST_F(ExternalAccountCredentialsTest,
3986 AwsExternalAccountCredsSuccessPathDuplicateRegionEnvKeysEnv) {
3987 ExecCtx exec_ctx;
3988 // Make sure that AWS_REGION gets used over AWS_DEFAULT_REGION
3989 SetEnv("AWS_REGION", "test_regionz");
3990 SetEnv("AWS_DEFAULT_REGION", "ERROR_REGION");
3991 SetEnv("AWS_ACCESS_KEY_ID", "test_access_key_id");
3992 SetEnv("AWS_SECRET_ACCESS_KEY", "test_secret_access_key");
3993 SetEnv("AWS_SESSION_TOKEN", "test_token");
3994 auto credential_source =
3995 JsonParse(valid_aws_external_account_creds_options_credential_source);
3996 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
3997 TestExternalAccountCredentials::ServiceAccountImpersonation
3998 service_account_impersonation;
3999 service_account_impersonation.token_lifetime_seconds = 3600;
4000 ExternalAccountCredentials::Options options = {
4001 "external_account", // type;
4002 "audience", // audience;
4003 "subject_token_type", // subject_token_type;
4004 "", // service_account_impersonation_url;
4005 service_account_impersonation, // service_account_impersonation;
4006 "https://foo.com:5555/token", // token_url;
4007 "https://foo.com:5555/token_info", // token_info_url;
4008 *credential_source, // credential_source;
4009 "quota_project_id", // quota_project_id;
4010 "client_id", // client_id;
4011 "client_secret", // client_secret;
4012 "", // workforce_pool_user_project;
4013 };
4014 auto creds =
4015 AwsExternalAccountCredentials::Create(options, {}, event_engine_);
4016 ASSERT_TRUE(creds.ok()) << creds.status();
4017 ASSERT_NE(*creds, nullptr);
4018 EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
4019 auto state = RequestMetadataState::NewInstance(
4020 absl::OkStatus(), "authorization: Bearer token_exchange_access_token");
4021 HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success,
4022 aws_external_account_creds_httpcli_post_success,
4023 httpcli_put_should_not_be_called);
4024 state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority,
4025 kTestPath);
4026 ExecCtx::Get()->Flush();
4027 event_engine_->TickUntilIdle();
4028 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
4029 UnsetEnv("AWS_REGION");
4030 UnsetEnv("AWS_DEFAULT_REGION");
4031 UnsetEnv("AWS_ACCESS_KEY_ID");
4032 UnsetEnv("AWS_SECRET_ACCESS_KEY");
4033 UnsetEnv("AWS_SESSION_TOKEN");
4034 }
4035
TEST_F(ExternalAccountCredentialsTest,CreateSuccess)4036 TEST_F(ExternalAccountCredentialsTest, CreateSuccess) {
4037 // url credentials
4038 const char* url_options_string =
4039 "{\"type\":\"external_account\",\"audience\":\"audience\",\"subject_"
4040 "token_type\":\"subject_token_type\",\"service_account_impersonation_"
4041 "url\":\"service_account_impersonation_url\","
4042 "\"token_url\":\"https://foo.com:5555/"
4043 "token\",\"token_info_url\":\"https://foo.com:5555/"
4044 "token_info\",\"credential_source\":{\"url\":\"https://foo.com:5555/"
4045 "generate_subject_token_format_json\",\"headers\":{\"Metadata-Flavor\":"
4046 "\"Google\"},\"format\":{\"type\":\"json\",\"subject_token_field_name\":"
4047 "\"access_token\"}},\"quota_project_id\":\"quota_"
4048 "project_id\",\"client_id\":\"client_id\",\"client_secret\":\"client_"
4049 "secret\"}";
4050 const char* url_scopes_string = "scope1,scope2";
4051 grpc_call_credentials* url_creds = grpc_external_account_credentials_create(
4052 url_options_string, url_scopes_string);
4053 ASSERT_NE(url_creds, nullptr);
4054 url_creds->Unref();
4055 // file credentials
4056 const char* file_options_string =
4057 "{\"type\":\"external_account\",\"audience\":\"audience\",\"subject_"
4058 "token_type\":\"subject_token_type\",\"service_account_impersonation_"
4059 "url\":\"service_account_impersonation_url\","
4060 "\"token_url\":\"https://foo.com:5555/"
4061 "token\",\"token_info_url\":\"https://foo.com:5555/"
4062 "token_info\",\"credential_source\":{\"file\":\"credentials_file_path\"},"
4063 "\"quota_project_id\":\"quota_"
4064 "project_id\",\"client_id\":\"client_id\",\"client_secret\":\"client_"
4065 "secret\"}";
4066 const char* file_scopes_string = "scope1,scope2";
4067 grpc_call_credentials* file_creds = grpc_external_account_credentials_create(
4068 file_options_string, file_scopes_string);
4069 ASSERT_NE(file_creds, nullptr);
4070 file_creds->Unref();
4071 // aws credentials
4072 const char* aws_options_string =
4073 "{\"type\":\"external_account\",\"audience\":\"audience\",\"subject_"
4074 "token_type\":\"subject_token_type\",\"service_account_impersonation_"
4075 "url\":\"service_account_impersonation_url\","
4076 "\"token_url\":\"https://"
4077 "foo.com:5555/token\",\"token_info_url\":\"https://foo.com:5555/"
4078 "token_info\",\"credential_source\":{\"environment_id\":\"aws1\","
4079 "\"region_url\":\"https://169.254.169.254:5555/"
4080 "region_url\",\"url\":\"https://"
4081 "169.254.169.254:5555/url\",\"regional_cred_verification_url\":\"https://"
4082 "foo.com:5555/regional_cred_verification_url_{region}\"},"
4083 "\"quota_project_id\":\"quota_"
4084 "project_id\",\"client_id\":\"client_id\",\"client_secret\":\"client_"
4085 "secret\"}";
4086 const char* aws_scopes_string = "scope1,scope2";
4087 grpc_call_credentials* aws_creds = grpc_external_account_credentials_create(
4088 aws_options_string, aws_scopes_string);
4089 ASSERT_NE(aws_creds, nullptr);
4090 aws_creds->Unref();
4091 }
4092
TEST_F(ExternalAccountCredentialsTest,AwsExternalAccountCredsFailureUnmatchedEnvironmentId)4093 TEST_F(ExternalAccountCredentialsTest,
4094 AwsExternalAccountCredsFailureUnmatchedEnvironmentId) {
4095 auto credential_source = JsonParse(
4096 invalid_aws_external_account_creds_options_credential_source_unmatched_environment_id);
4097 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
4098 TestExternalAccountCredentials::ServiceAccountImpersonation
4099 service_account_impersonation;
4100 service_account_impersonation.token_lifetime_seconds = 3600;
4101 ExternalAccountCredentials::Options options = {
4102 "external_account", // type;
4103 "audience", // audience;
4104 "subject_token_type", // subject_token_type;
4105 "", // service_account_impersonation_url;
4106 service_account_impersonation, // service_account_impersonation;
4107 "https://foo.com:5555/token", // token_url;
4108 "https://foo.com:5555/token_info", // token_info_url;
4109 *credential_source, // credential_source;
4110 "quota_project_id", // quota_project_id;
4111 "client_id", // client_id;
4112 "client_secret", // client_secret;
4113 "", // workforce_pool_user_project;
4114 };
4115 auto creds =
4116 AwsExternalAccountCredentials::Create(options, {}, event_engine_);
4117 ASSERT_FALSE(creds.ok());
4118 std::string actual_error;
4119 grpc_error_get_str(creds.status(), StatusStrProperty::kDescription,
4120 &actual_error);
4121 EXPECT_EQ("environment_id does not match.", actual_error);
4122 }
4123
TEST_F(ExternalAccountCredentialsTest,AwsExternalAccountCredsFailureInvalidRegionalCredVerificationUrl)4124 TEST_F(ExternalAccountCredentialsTest,
4125 AwsExternalAccountCredsFailureInvalidRegionalCredVerificationUrl) {
4126 ExecCtx exec_ctx;
4127 auto credential_source = JsonParse(
4128 invalid_aws_external_account_creds_options_credential_source_invalid_regional_cred_verification_url);
4129 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
4130 TestExternalAccountCredentials::ServiceAccountImpersonation
4131 service_account_impersonation;
4132 service_account_impersonation.token_lifetime_seconds = 3600;
4133 ExternalAccountCredentials::Options options = {
4134 "external_account", // type;
4135 "audience", // audience;
4136 "subject_token_type", // subject_token_type;
4137 "", // service_account_impersonation_url;
4138 service_account_impersonation, // service_account_impersonation;
4139 "https://foo.com:5555/token", // token_url;
4140 "https://foo.com:5555/token_info", // token_info_url;
4141 *credential_source, // credential_source;
4142 "quota_project_id", // quota_project_id;
4143 "client_id", // client_id;
4144 "client_secret", // client_secret;
4145 "", // workforce_pool_user_project;
4146 };
4147 auto creds =
4148 AwsExternalAccountCredentials::Create(options, {}, event_engine_);
4149 ASSERT_TRUE(creds.ok()) << creds.status();
4150 ASSERT_NE(*creds, nullptr);
4151 EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
4152 // TODO(roth): This should return UNAUTHENTICATED.
4153 grpc_error_handle expected_error = absl::UnknownError(
4154 "error fetching oauth2 token: Creating aws request signer failed.");
4155 auto state = RequestMetadataState::NewInstance(expected_error, {});
4156 HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success,
4157 aws_external_account_creds_httpcli_post_success,
4158 httpcli_put_should_not_be_called);
4159 state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority,
4160 kTestPath);
4161 ExecCtx::Get()->Flush();
4162 event_engine_->TickUntilIdle();
4163 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
4164 }
4165
TEST_F(ExternalAccountCredentialsTest,AwsExternalAccountCredsFailureMissingRoleName)4166 TEST_F(ExternalAccountCredentialsTest,
4167 AwsExternalAccountCredsFailureMissingRoleName) {
4168 ExecCtx exec_ctx;
4169 auto credential_source = JsonParse(
4170 invalid_aws_external_account_creds_options_credential_source_missing_role_name);
4171 ASSERT_TRUE(credential_source.ok()) << credential_source.status();
4172 TestExternalAccountCredentials::ServiceAccountImpersonation
4173 service_account_impersonation;
4174 service_account_impersonation.token_lifetime_seconds = 3600;
4175 ExternalAccountCredentials::Options options = {
4176 "external_account", // type;
4177 "audience", // audience;
4178 "subject_token_type", // subject_token_type;
4179 "", // service_account_impersonation_url;
4180 service_account_impersonation, // service_account_impersonation;
4181 "https://foo.com:5555/token", // token_url;
4182 "https://foo.com:5555/token_info", // token_info_url;
4183 *credential_source, // credential_source;
4184 "quota_project_id", // quota_project_id;
4185 "client_id", // client_id;
4186 "client_secret", // client_secret;
4187 "", // workforce_pool_user_project;
4188 };
4189 auto creds =
4190 AwsExternalAccountCredentials::Create(options, {}, event_engine_);
4191 ASSERT_TRUE(creds.ok()) << creds.status();
4192 ASSERT_NE(*creds, nullptr);
4193 EXPECT_EQ((*creds)->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
4194 // TODO(roth): This should return UNAUTHENTICATED.
4195 grpc_error_handle expected_error = absl::UnknownError(
4196 "error fetching oauth2 token: "
4197 "Missing role name when retrieving signing keys.");
4198 auto state = RequestMetadataState::NewInstance(expected_error, {});
4199 HttpRequest::SetOverride(aws_external_account_creds_httpcli_get_success,
4200 aws_external_account_creds_httpcli_post_success,
4201 httpcli_put_should_not_be_called);
4202 state->RunRequestMetadataTest(creds->get(), kTestUrlScheme, kTestAuthority,
4203 kTestPath);
4204 ExecCtx::Get()->Flush();
4205 event_engine_->TickUntilIdle();
4206 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
4207 }
4208
TEST_F(ExternalAccountCredentialsTest,CreateFailureInvalidJsonFormat)4209 TEST_F(ExternalAccountCredentialsTest, CreateFailureInvalidJsonFormat) {
4210 const char* options_string = "invalid_json";
4211 grpc_call_credentials* creds =
4212 grpc_external_account_credentials_create(options_string, "");
4213 EXPECT_EQ(creds, nullptr);
4214 }
4215
TEST_F(ExternalAccountCredentialsTest,CreateFailureInvalidOptionsFormat)4216 TEST_F(ExternalAccountCredentialsTest, CreateFailureInvalidOptionsFormat) {
4217 const char* options_string = "{\"random_key\":\"random_value\"}";
4218 grpc_call_credentials* creds =
4219 grpc_external_account_credentials_create(options_string, "");
4220 EXPECT_EQ(creds, nullptr);
4221 }
4222
TEST_F(ExternalAccountCredentialsTest,CreateFailureInvalidOptionsCredentialSource)4223 TEST_F(ExternalAccountCredentialsTest,
4224 CreateFailureInvalidOptionsCredentialSource) {
4225 const char* options_string =
4226 "{\"type\":\"external_account\",\"audience\":\"audience\",\"subject_"
4227 "token_type\":\"subject_token_type\",\"service_account_impersonation_"
4228 "url\":\"service_account_impersonation_url\","
4229 "\"token_url\":\"https://foo.com:5555/"
4230 "token\",\"token_info_url\":\"https://foo.com:5555/"
4231 "token_info\",\"credential_source\":{\"random_key\":\"random_value\"},"
4232 "\"quota_project_id\":\"quota_"
4233 "project_id\",\"client_id\":\"client_id\",\"client_secret\":\"client_"
4234 "secret\"}";
4235 grpc_call_credentials* creds =
4236 grpc_external_account_credentials_create(options_string, "");
4237 EXPECT_EQ(creds, nullptr);
4238 }
4239
TEST_F(ExternalAccountCredentialsTest,CreateSuccessWorkforcePool)4240 TEST_F(ExternalAccountCredentialsTest, CreateSuccessWorkforcePool) {
4241 const char* url_options_string =
4242 "{\"type\":\"external_account\",\"audience\":\"//iam.googleapis.com/"
4243 "locations/location/workforcePools/pool/providers/provider\",\"subject_"
4244 "token_type\":\"subject_token_type\",\"service_account_impersonation_"
4245 "url\":\"service_account_impersonation_url\","
4246 "\"token_url\":\"https://foo.com:5555/"
4247 "token\",\"token_info_url\":\"https://foo.com:5555/"
4248 "token_info\",\"credential_source\":{\"url\":\"https://foo.com:5555/"
4249 "generate_subject_token_format_json\",\"headers\":{\"Metadata-Flavor\":"
4250 "\"Google\"},\"format\":{\"type\":\"json\",\"subject_token_field_name\":"
4251 "\"access_token\"}},\"quota_project_id\":\"quota_"
4252 "project_id\",\"client_id\":\"client_id\",\"client_secret\":\"client_"
4253 "secret\",\"workforce_pool_user_project\":\"workforce_pool_user_"
4254 "project\"}";
4255 const char* url_scopes_string = "scope1,scope2";
4256 grpc_call_credentials* url_creds = grpc_external_account_credentials_create(
4257 url_options_string, url_scopes_string);
4258 ASSERT_NE(url_creds, nullptr);
4259 url_creds->Unref();
4260 }
4261
TEST_F(ExternalAccountCredentialsTest,CreateFailureInvalidWorkforcePoolAudience)4262 TEST_F(ExternalAccountCredentialsTest,
4263 CreateFailureInvalidWorkforcePoolAudience) {
4264 const char* url_options_string =
4265 "{\"type\":\"external_account\",\"audience\":\"invalid_workforce_pool_"
4266 "audience\",\"subject_"
4267 "token_type\":\"subject_token_type\",\"service_account_impersonation_"
4268 "url\":\"service_account_impersonation_url\","
4269 "\"token_url\":\"https://foo.com:5555/"
4270 "token\",\"token_info_url\":\"https://foo.com:5555/"
4271 "token_info\",\"credential_source\":{\"url\":\"https://foo.com:5555/"
4272 "generate_subject_token_format_json\",\"headers\":{\"Metadata-Flavor\":"
4273 "\"Google\"},\"format\":{\"type\":\"json\",\"subject_token_field_name\":"
4274 "\"access_token\"}},\"quota_project_id\":\"quota_"
4275 "project_id\",\"client_id\":\"client_id\",\"client_secret\":\"client_"
4276 "secret\",\"workforce_pool_user_project\":\"workforce_pool_user_"
4277 "project\"}";
4278 const char* url_scopes_string = "scope1,scope2";
4279 grpc_call_credentials* url_creds = grpc_external_account_credentials_create(
4280 url_options_string, url_scopes_string);
4281 ASSERT_EQ(url_creds, nullptr);
4282 }
4283
TEST_F(CredentialsTest,TestInsecureCredentialsCompareSuccess)4284 TEST_F(CredentialsTest, TestInsecureCredentialsCompareSuccess) {
4285 auto insecure_creds_1 = grpc_insecure_credentials_create();
4286 auto insecure_creds_2 = grpc_insecure_credentials_create();
4287 ASSERT_EQ(insecure_creds_1->cmp(insecure_creds_2), 0);
4288 grpc_arg arg_1 = grpc_channel_credentials_to_arg(insecure_creds_1);
4289 grpc_channel_args args_1 = {1, &arg_1};
4290 grpc_arg arg_2 = grpc_channel_credentials_to_arg(insecure_creds_2);
4291 grpc_channel_args args_2 = {1, &arg_2};
4292 EXPECT_EQ(grpc_channel_args_compare(&args_1, &args_2), 0);
4293 grpc_channel_credentials_release(insecure_creds_1);
4294 grpc_channel_credentials_release(insecure_creds_2);
4295 }
4296
TEST_F(CredentialsTest,TestInsecureCredentialsCompareFailure)4297 TEST_F(CredentialsTest, TestInsecureCredentialsCompareFailure) {
4298 auto* insecure_creds = grpc_insecure_credentials_create();
4299 auto* fake_creds = grpc_fake_transport_security_credentials_create();
4300 ASSERT_NE(insecure_creds->cmp(fake_creds), 0);
4301 ASSERT_NE(fake_creds->cmp(insecure_creds), 0);
4302 grpc_arg arg_1 = grpc_channel_credentials_to_arg(insecure_creds);
4303 grpc_channel_args args_1 = {1, &arg_1};
4304 grpc_arg arg_2 = grpc_channel_credentials_to_arg(fake_creds);
4305 grpc_channel_args args_2 = {1, &arg_2};
4306 EXPECT_NE(grpc_channel_args_compare(&args_1, &args_2), 0);
4307 grpc_channel_credentials_release(fake_creds);
4308 grpc_channel_credentials_release(insecure_creds);
4309 }
4310
TEST_F(CredentialsTest,TestInsecureCredentialsSingletonCreate)4311 TEST_F(CredentialsTest, TestInsecureCredentialsSingletonCreate) {
4312 auto* insecure_creds_1 = grpc_insecure_credentials_create();
4313 auto* insecure_creds_2 = grpc_insecure_credentials_create();
4314 EXPECT_EQ(insecure_creds_1, insecure_creds_2);
4315 }
4316
TEST_F(CredentialsTest,TestFakeCallCredentialsCompareSuccess)4317 TEST_F(CredentialsTest, TestFakeCallCredentialsCompareSuccess) {
4318 auto call_creds = MakeRefCounted<fake_call_creds>();
4319 CHECK_EQ(call_creds->cmp(call_creds.get()), 0);
4320 }
4321
TEST_F(CredentialsTest,TestFakeCallCredentialsCompareFailure)4322 TEST_F(CredentialsTest, TestFakeCallCredentialsCompareFailure) {
4323 auto fake_creds = MakeRefCounted<fake_call_creds>();
4324 auto* md_creds = grpc_md_only_test_credentials_create("key", "value");
4325 CHECK_NE(fake_creds->cmp(md_creds), 0);
4326 CHECK_NE(md_creds->cmp(fake_creds.get()), 0);
4327 grpc_call_credentials_release(md_creds);
4328 }
4329
TEST_F(CredentialsTest,TestHttpRequestSSLCredentialsCompare)4330 TEST_F(CredentialsTest, TestHttpRequestSSLCredentialsCompare) {
4331 auto creds_1 = CreateHttpRequestSSLCredentials();
4332 auto creds_2 = CreateHttpRequestSSLCredentials();
4333 EXPECT_EQ(creds_1->cmp(creds_2.get()), 0);
4334 EXPECT_EQ(creds_2->cmp(creds_1.get()), 0);
4335 }
4336
TEST_F(CredentialsTest,TestHttpRequestSSLCredentialsSingleton)4337 TEST_F(CredentialsTest, TestHttpRequestSSLCredentialsSingleton) {
4338 auto creds_1 = CreateHttpRequestSSLCredentials();
4339 auto creds_2 = CreateHttpRequestSSLCredentials();
4340 EXPECT_EQ(creds_1, creds_2);
4341 }
4342
TEST_F(CredentialsTest,TestCompositeChannelCredsCompareSuccess)4343 TEST_F(CredentialsTest, TestCompositeChannelCredsCompareSuccess) {
4344 auto* insecure_creds = grpc_insecure_credentials_create();
4345 auto fake_creds = MakeRefCounted<fake_call_creds>();
4346 auto* composite_creds_1 = grpc_composite_channel_credentials_create(
4347 insecure_creds, fake_creds.get(), nullptr);
4348 auto* composite_creds_2 = grpc_composite_channel_credentials_create(
4349 insecure_creds, fake_creds.get(), nullptr);
4350 EXPECT_EQ(composite_creds_1->cmp(composite_creds_2), 0);
4351 EXPECT_EQ(composite_creds_2->cmp(composite_creds_1), 0);
4352 grpc_channel_credentials_release(insecure_creds);
4353 grpc_channel_credentials_release(composite_creds_1);
4354 grpc_channel_credentials_release(composite_creds_2);
4355 }
4356
TEST_F(CredentialsTest,RecursiveCompositeCredsDuplicateWithoutCallCreds)4357 TEST_F(CredentialsTest, RecursiveCompositeCredsDuplicateWithoutCallCreds) {
4358 auto* insecure_creds = grpc_insecure_credentials_create();
4359 auto inner_fake_creds = MakeRefCounted<fake_call_creds>();
4360 auto outer_fake_creds = MakeRefCounted<fake_call_creds>();
4361 auto* inner_composite_creds = grpc_composite_channel_credentials_create(
4362 insecure_creds, inner_fake_creds.get(), nullptr);
4363 auto* outer_composite_creds = grpc_composite_channel_credentials_create(
4364 inner_composite_creds, outer_fake_creds.get(), nullptr);
4365 auto duplicate_without_call_creds =
4366 outer_composite_creds->duplicate_without_call_credentials();
4367 EXPECT_EQ(duplicate_without_call_creds.get(), insecure_creds);
4368 grpc_channel_credentials_release(insecure_creds);
4369 grpc_channel_credentials_release(inner_composite_creds);
4370 grpc_channel_credentials_release(outer_composite_creds);
4371 }
4372
TEST_F(CredentialsTest,TestCompositeChannelCredsCompareFailureDifferentChannelCreds)4373 TEST_F(CredentialsTest,
4374 TestCompositeChannelCredsCompareFailureDifferentChannelCreds) {
4375 auto* insecure_creds = grpc_insecure_credentials_create();
4376 auto* fake_channel_creds = grpc_fake_transport_security_credentials_create();
4377 auto fake_creds = MakeRefCounted<fake_call_creds>();
4378 auto* composite_creds_1 = grpc_composite_channel_credentials_create(
4379 insecure_creds, fake_creds.get(), nullptr);
4380 auto* composite_creds_2 = grpc_composite_channel_credentials_create(
4381 fake_channel_creds, fake_creds.get(), nullptr);
4382 EXPECT_NE(composite_creds_1->cmp(composite_creds_2), 0);
4383 EXPECT_NE(composite_creds_2->cmp(composite_creds_1), 0);
4384 grpc_channel_credentials_release(insecure_creds);
4385 grpc_channel_credentials_release(fake_channel_creds);
4386 grpc_channel_credentials_release(composite_creds_1);
4387 grpc_channel_credentials_release(composite_creds_2);
4388 }
4389
TEST_F(CredentialsTest,TestCompositeChannelCredsCompareFailureDifferentCallCreds)4390 TEST_F(CredentialsTest,
4391 TestCompositeChannelCredsCompareFailureDifferentCallCreds) {
4392 auto* insecure_creds = grpc_insecure_credentials_create();
4393 auto fake_creds = MakeRefCounted<fake_call_creds>();
4394 auto* md_creds = grpc_md_only_test_credentials_create("key", "value");
4395 auto* composite_creds_1 = grpc_composite_channel_credentials_create(
4396 insecure_creds, fake_creds.get(), nullptr);
4397 auto* composite_creds_2 = grpc_composite_channel_credentials_create(
4398 insecure_creds, md_creds, nullptr);
4399 EXPECT_NE(composite_creds_1->cmp(composite_creds_2), 0);
4400 EXPECT_NE(composite_creds_2->cmp(composite_creds_1), 0);
4401 grpc_channel_credentials_release(insecure_creds);
4402 grpc_call_credentials_release(md_creds);
4403 grpc_channel_credentials_release(composite_creds_1);
4404 grpc_channel_credentials_release(composite_creds_2);
4405 }
4406
TEST_F(CredentialsTest,TestTlsCredentialsCompareSuccess)4407 TEST_F(CredentialsTest, TestTlsCredentialsCompareSuccess) {
4408 auto* tls_creds_1 =
4409 grpc_tls_credentials_create(grpc_tls_credentials_options_create());
4410 auto* tls_creds_2 =
4411 grpc_tls_credentials_create(grpc_tls_credentials_options_create());
4412 EXPECT_EQ(tls_creds_1->cmp(tls_creds_2), 0);
4413 EXPECT_EQ(tls_creds_2->cmp(tls_creds_1), 0);
4414 grpc_channel_credentials_release(tls_creds_1);
4415 grpc_channel_credentials_release(tls_creds_2);
4416 }
4417
TEST_F(CredentialsTest,TestTlsCredentialsWithVerifierCompareSuccess)4418 TEST_F(CredentialsTest, TestTlsCredentialsWithVerifierCompareSuccess) {
4419 auto* options_1 = grpc_tls_credentials_options_create();
4420 options_1->set_certificate_verifier(
4421 MakeRefCounted<HostNameCertificateVerifier>());
4422 auto* tls_creds_1 = grpc_tls_credentials_create(options_1);
4423 auto* options_2 = grpc_tls_credentials_options_create();
4424 options_2->set_certificate_verifier(
4425 MakeRefCounted<HostNameCertificateVerifier>());
4426 auto* tls_creds_2 = grpc_tls_credentials_create(options_2);
4427 EXPECT_EQ(tls_creds_1->cmp(tls_creds_2), 0);
4428 EXPECT_EQ(tls_creds_2->cmp(tls_creds_1), 0);
4429 grpc_channel_credentials_release(tls_creds_1);
4430 grpc_channel_credentials_release(tls_creds_2);
4431 }
4432
TEST_F(CredentialsTest,TestTlsCredentialsCompareFailure)4433 TEST_F(CredentialsTest, TestTlsCredentialsCompareFailure) {
4434 auto* options_1 = grpc_tls_credentials_options_create();
4435 options_1->set_check_call_host(true);
4436 auto* tls_creds_1 = grpc_tls_credentials_create(options_1);
4437 auto* options_2 = grpc_tls_credentials_options_create();
4438 options_2->set_check_call_host(false);
4439 auto* tls_creds_2 = grpc_tls_credentials_create(options_2);
4440 EXPECT_NE(tls_creds_1->cmp(tls_creds_2), 0);
4441 EXPECT_NE(tls_creds_2->cmp(tls_creds_1), 0);
4442 grpc_channel_credentials_release(tls_creds_1);
4443 grpc_channel_credentials_release(tls_creds_2);
4444 }
4445
TEST_F(CredentialsTest,TestTlsCredentialsWithVerifierCompareFailure)4446 TEST_F(CredentialsTest, TestTlsCredentialsWithVerifierCompareFailure) {
4447 auto* options_1 = grpc_tls_credentials_options_create();
4448 options_1->set_certificate_verifier(
4449 MakeRefCounted<HostNameCertificateVerifier>());
4450 auto* tls_creds_1 = grpc_tls_credentials_create(options_1);
4451 auto* options_2 = grpc_tls_credentials_options_create();
4452 grpc_tls_certificate_verifier_external verifier = {nullptr, nullptr, nullptr,
4453 nullptr};
4454 options_2->set_certificate_verifier(
4455 MakeRefCounted<ExternalCertificateVerifier>(&verifier));
4456 auto* tls_creds_2 = grpc_tls_credentials_create(options_2);
4457 EXPECT_NE(tls_creds_1->cmp(tls_creds_2), 0);
4458 EXPECT_NE(tls_creds_2->cmp(tls_creds_1), 0);
4459 grpc_channel_credentials_release(tls_creds_1);
4460 grpc_channel_credentials_release(tls_creds_2);
4461 }
4462
TEST_F(CredentialsTest,TestXdsCredentialsCompareSuccess)4463 TEST_F(CredentialsTest, TestXdsCredentialsCompareSuccess) {
4464 auto* insecure_creds = grpc_insecure_credentials_create();
4465 auto* xds_creds_1 = grpc_xds_credentials_create(insecure_creds);
4466 auto* xds_creds_2 = grpc_xds_credentials_create(insecure_creds);
4467 EXPECT_EQ(xds_creds_1->cmp(xds_creds_2), 0);
4468 EXPECT_EQ(xds_creds_2->cmp(xds_creds_1), 0);
4469 grpc_channel_credentials_release(insecure_creds);
4470 grpc_channel_credentials_release(xds_creds_1);
4471 grpc_channel_credentials_release(xds_creds_2);
4472 }
4473
TEST_F(CredentialsTest,TestXdsCredentialsCompareFailure)4474 TEST_F(CredentialsTest, TestXdsCredentialsCompareFailure) {
4475 auto* insecure_creds = grpc_insecure_credentials_create();
4476 auto* fake_creds = grpc_fake_transport_security_credentials_create();
4477 auto* xds_creds_1 = grpc_xds_credentials_create(insecure_creds);
4478 auto* xds_creds_2 = grpc_xds_credentials_create(fake_creds);
4479 EXPECT_NE(xds_creds_1->cmp(xds_creds_2), 0);
4480 EXPECT_NE(xds_creds_2->cmp(xds_creds_1), 0);
4481 grpc_channel_credentials_release(insecure_creds);
4482 grpc_channel_credentials_release(fake_creds);
4483 grpc_channel_credentials_release(xds_creds_1);
4484 grpc_channel_credentials_release(xds_creds_2);
4485 }
4486
4487 class GcpServiceAccountIdentityCredentialsTest : public ::testing::Test {
4488 protected:
SetUp()4489 void SetUp() override {
4490 grpc_init();
4491 g_http_status = 200;
4492 g_audience = "";
4493 g_token = nullptr;
4494 g_on_http_request_error = nullptr;
4495 HttpRequest::SetOverride(HttpGetOverride, httpcli_post_should_not_be_called,
4496 httpcli_put_should_not_be_called);
4497 }
4498
TearDown()4499 void TearDown() override {
4500 HttpRequest::SetOverride(nullptr, nullptr, nullptr);
4501 grpc_shutdown_blocking();
4502 }
4503
ValidateHttpRequest(const grpc_http_request * request,const URI & uri)4504 static void ValidateHttpRequest(const grpc_http_request* request,
4505 const URI& uri) {
4506 EXPECT_EQ(uri.authority(), "metadata.google.internal.");
4507 EXPECT_EQ(uri.path(),
4508 "/computeMetadata/v1/instance/service-accounts/default/identity");
4509 EXPECT_THAT(
4510 uri.query_parameter_map(),
4511 ::testing::ElementsAre(::testing::Pair("audience", g_audience)));
4512 ASSERT_EQ(request->hdr_count, 1);
4513 EXPECT_EQ(absl::string_view(request->hdrs[0].key), "Metadata-Flavor");
4514 EXPECT_EQ(absl::string_view(request->hdrs[0].value), "Google");
4515 }
4516
HttpGetOverride(const grpc_http_request * request,const URI & uri,Timestamp,grpc_closure * on_done,grpc_http_response * response)4517 static int HttpGetOverride(const grpc_http_request* request, const URI& uri,
4518 Timestamp /*deadline*/, grpc_closure* on_done,
4519 grpc_http_response* response) {
4520 // Validate request.
4521 ValidateHttpRequest(request, uri);
4522 // Generate response.
4523 *response = http_response(g_http_status, g_token == nullptr ? "" : g_token);
4524 ExecCtx::Run(DEBUG_LOCATION, on_done,
4525 g_on_http_request_error == nullptr ? absl::OkStatus()
4526 : *g_on_http_request_error);
4527 return 1;
4528 }
4529
4530 // Constructs a synthetic JWT token that's just valid enough for the
4531 // call creds to extract the expiration date.
MakeToken(Timestamp expiration)4532 static std::string MakeToken(Timestamp expiration) {
4533 gpr_timespec ts = expiration.as_timespec(GPR_CLOCK_REALTIME);
4534 std::string json = absl::StrCat("{\"exp\":", ts.tv_sec, "}");
4535 return absl::StrCat("foo.", absl::WebSafeBase64Escape(json), ".bar");
4536 }
4537
4538 static int g_http_status;
4539 static absl::string_view g_audience;
4540 static const char* g_token;
4541 static absl::Status* g_on_http_request_error;
4542 };
4543
4544 int GcpServiceAccountIdentityCredentialsTest::g_http_status;
4545 absl::string_view GcpServiceAccountIdentityCredentialsTest::g_audience;
4546 const char* GcpServiceAccountIdentityCredentialsTest::g_token;
4547 absl::Status* GcpServiceAccountIdentityCredentialsTest::g_on_http_request_error;
4548
TEST_F(GcpServiceAccountIdentityCredentialsTest,Basic)4549 TEST_F(GcpServiceAccountIdentityCredentialsTest, Basic) {
4550 g_audience = "CV-6";
4551 auto token = MakeToken(Timestamp::Now() + Duration::Hours(1));
4552 g_token = token.c_str();
4553 ExecCtx exec_ctx;
4554 auto creds =
4555 MakeRefCounted<GcpServiceAccountIdentityCallCredentials>(g_audience);
4556 CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
4557 auto state = RequestMetadataState::NewInstance(absl::OkStatus(), g_token);
4558 state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority,
4559 kTestPath);
4560 ExecCtx::Get()->Flush();
4561 }
4562
4563 // HTTP status 429 is mapped to UNAVAILABLE as per
4564 // https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md.
TEST_F(GcpServiceAccountIdentityCredentialsTest,FailsWithHttpStatus429)4565 TEST_F(GcpServiceAccountIdentityCredentialsTest, FailsWithHttpStatus429) {
4566 g_audience = "CV-5_Midway";
4567 g_http_status = 429;
4568 ExecCtx exec_ctx;
4569 auto creds =
4570 MakeRefCounted<GcpServiceAccountIdentityCallCredentials>(g_audience);
4571 CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
4572 auto state = RequestMetadataState::NewInstance(
4573 absl::UnavailableError("JWT fetch failed with status 429"), "");
4574 state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority,
4575 kTestPath);
4576 ExecCtx::Get()->Flush();
4577 }
4578
4579 // HTTP status 400 is mapped to INTERNAL as per
4580 // https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md,
4581 // so it should be rewritten as UNAUTHENTICATED.
TEST_F(GcpServiceAccountIdentityCredentialsTest,FailsWithHttpStatus400)4582 TEST_F(GcpServiceAccountIdentityCredentialsTest, FailsWithHttpStatus400) {
4583 g_audience = "CV-8_SantaCruzIslands";
4584 g_http_status = 400;
4585 ExecCtx exec_ctx;
4586 auto creds =
4587 MakeRefCounted<GcpServiceAccountIdentityCallCredentials>(g_audience);
4588 CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
4589 auto state = RequestMetadataState::NewInstance(
4590 absl::UnauthenticatedError("JWT fetch failed with status 400"), "");
4591 state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority,
4592 kTestPath);
4593 ExecCtx::Get()->Flush();
4594 }
4595
TEST_F(GcpServiceAccountIdentityCredentialsTest,FailsWithHttpIOError)4596 TEST_F(GcpServiceAccountIdentityCredentialsTest, FailsWithHttpIOError) {
4597 g_audience = "CV-2_CoralSea";
4598 absl::Status status = absl::InternalError("uh oh");
4599 g_on_http_request_error = &status;
4600 ExecCtx exec_ctx;
4601 auto creds =
4602 MakeRefCounted<GcpServiceAccountIdentityCallCredentials>(g_audience);
4603 CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
4604 auto state = RequestMetadataState::NewInstance(
4605 absl::UnavailableError("INTERNAL:uh oh"), "");
4606 state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority,
4607 kTestPath);
4608 ExecCtx::Get()->Flush();
4609 }
4610
TEST_F(GcpServiceAccountIdentityCredentialsTest,TokenHasWrongNumberOfDots)4611 TEST_F(GcpServiceAccountIdentityCredentialsTest, TokenHasWrongNumberOfDots) {
4612 g_audience = "CV-7_Guadalcanal";
4613 std::string bad_token = "foo.bar";
4614 g_token = bad_token.c_str();
4615 ExecCtx exec_ctx;
4616 auto creds =
4617 MakeRefCounted<GcpServiceAccountIdentityCallCredentials>(g_audience);
4618 CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
4619 auto state = RequestMetadataState::NewInstance(
4620 absl::UnauthenticatedError("error parsing JWT token"), "");
4621 state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority,
4622 kTestPath);
4623 ExecCtx::Get()->Flush();
4624 }
4625
TEST_F(GcpServiceAccountIdentityCredentialsTest,TokenPayloadNotBase64)4626 TEST_F(GcpServiceAccountIdentityCredentialsTest, TokenPayloadNotBase64) {
4627 g_audience = "CVE-56_Makin";
4628 std::string bad_token = "foo.&.bar";
4629 g_token = bad_token.c_str();
4630 ExecCtx exec_ctx;
4631 auto creds =
4632 MakeRefCounted<GcpServiceAccountIdentityCallCredentials>(g_audience);
4633 CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
4634 auto state = RequestMetadataState::NewInstance(
4635 absl::UnauthenticatedError("error parsing JWT token"), "");
4636 state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority,
4637 kTestPath);
4638 ExecCtx::Get()->Flush();
4639 }
4640
TEST_F(GcpServiceAccountIdentityCredentialsTest,TokenPayloadNotJson)4641 TEST_F(GcpServiceAccountIdentityCredentialsTest, TokenPayloadNotJson) {
4642 g_audience = "CVE-73_Samar";
4643 std::string bad_token =
4644 absl::StrCat("foo.", absl::WebSafeBase64Escape("xxx"), ".bar");
4645 g_token = bad_token.c_str();
4646 ExecCtx exec_ctx;
4647 auto creds =
4648 MakeRefCounted<GcpServiceAccountIdentityCallCredentials>(g_audience);
4649 CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
4650 auto state = RequestMetadataState::NewInstance(
4651 absl::UnauthenticatedError("error parsing JWT token"), "");
4652 state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority,
4653 kTestPath);
4654 ExecCtx::Get()->Flush();
4655 }
4656
TEST_F(GcpServiceAccountIdentityCredentialsTest,TokenInvalidExpiration)4657 TEST_F(GcpServiceAccountIdentityCredentialsTest, TokenInvalidExpiration) {
4658 g_audience = "CVL-23_Leyte";
4659 std::string bad_token = absl::StrCat(
4660 "foo.", absl::WebSafeBase64Escape("{\"exp\":\"foo\"}"), ".bar");
4661 g_token = bad_token.c_str();
4662 ExecCtx exec_ctx;
4663 auto creds =
4664 MakeRefCounted<GcpServiceAccountIdentityCallCredentials>(g_audience);
4665 CHECK_EQ(creds->min_security_level(), GRPC_PRIVACY_AND_INTEGRITY);
4666 auto state = RequestMetadataState::NewInstance(
4667 absl::UnauthenticatedError("error parsing JWT token"), "");
4668 state->RunRequestMetadataTest(creds.get(), kTestUrlScheme, kTestAuthority,
4669 kTestPath);
4670 ExecCtx::Get()->Flush();
4671 }
4672
4673 } // namespace
4674 } // namespace grpc_core
4675
main(int argc,char ** argv)4676 int main(int argc, char** argv) {
4677 testing::InitGoogleTest(&argc, argv);
4678 grpc::testing::TestEnvironment env(&argc, argv);
4679 auto result = RUN_ALL_TESTS();
4680 return result;
4681 }
4682