• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 Google LLC
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *    * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *    * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *
15  *    * Neither the name of Google LLC nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 package com.google.auth.oauth2;
33 
34 import static com.google.auth.Credentials.GOOGLE_DEFAULT_UNIVERSE;
35 import static org.junit.Assert.*;
36 import static org.junit.Assert.assertEquals;
37 import static org.junit.Assert.assertNotNull;
38 import static org.junit.Assert.assertNull;
39 import static org.junit.Assert.assertTrue;
40 import static org.junit.Assert.fail;
41 
42 import com.google.api.client.json.GenericJson;
43 import com.google.api.client.json.JsonParser;
44 import com.google.api.client.testing.http.MockLowLevelHttpRequest;
45 import com.google.api.client.util.Clock;
46 import com.google.auth.TestUtils;
47 import com.google.auth.oauth2.ExternalAccountCredentialsTest.MockExternalAccountCredentialsTransportFactory;
48 import java.io.IOException;
49 import java.io.InputStream;
50 import java.net.URI;
51 import java.net.URLDecoder;
52 import java.util.Arrays;
53 import java.util.Collections;
54 import java.util.HashMap;
55 import java.util.List;
56 import java.util.Map;
57 import java.util.function.Supplier;
58 import org.junit.Test;
59 import org.junit.runner.RunWith;
60 import org.junit.runners.JUnit4;
61 
62 /** Tests for {@link AwsCredentials}. */
63 @RunWith(JUnit4.class)
64 public class AwsCredentialsTest extends BaseSerializationTest {
65 
66   private static final String STS_URL = "https://sts.googleapis.com/v1/token";
67   private static final String AWS_CREDENTIALS_URL = "https://169.254.169.254";
68   private static final String AWS_CREDENTIALS_URL_WITH_ROLE = "https://169.254.169.254/roleName";
69   private static final String AWS_REGION_URL = "https://169.254.169.254/region";
70   private static final String AWS_IMDSV2_SESSION_TOKEN_URL = "https://169.254.169.254/imdsv2";
71   private static final String AWS_IMDSV2_SESSION_TOKEN = "sessiontoken";
72   private static final String DEFAULT_REGIONAL_CREDENTIAL_VERIFICATION_URL =
73       "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15";
74 
75   private static final String GET_CALLER_IDENTITY_URL =
76       "https://sts.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15";
77 
78   private static final String SERVICE_ACCOUNT_IMPERSONATION_URL =
79       "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/testn@test.iam.gserviceaccount.com:generateAccessToken";
80 
81   private static final Map<String, Object> AWS_CREDENTIAL_SOURCE_MAP =
82       new HashMap<String, Object>() {
83         {
84           put("environment_id", "aws1");
85           put("region_url", AWS_REGION_URL);
86           put("url", AWS_CREDENTIALS_URL);
87           put("regional_cred_verification_url", "regionalCredVerificationUrl");
88         }
89       };
90 
91   private static final Map<String, Object> EMPTY_METADATA_HEADERS = Collections.emptyMap();
92   private static final Map<String, String> EMPTY_STRING_HEADERS = Collections.emptyMap();
93 
94   private static final AwsCredentialSource AWS_CREDENTIAL_SOURCE =
95       new AwsCredentialSource(AWS_CREDENTIAL_SOURCE_MAP);
96 
97   private static final AwsCredentials AWS_CREDENTIAL =
98       AwsCredentials.newBuilder()
99           .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY)
100           .setAudience("audience")
101           .setSubjectTokenType("subjectTokenType")
102           .setTokenUrl(STS_URL)
103           .setTokenInfoUrl("tokenInfoUrl")
104           .setCredentialSource(AWS_CREDENTIAL_SOURCE)
105           .build();
106 
107   private static final AwsSecurityCredentials programmaticAwsCreds =
108       new AwsSecurityCredentials("testAccessKey", "testSecretAccessKey", null);
109 
110   private static final ExternalAccountSupplierContext emptyContext =
111       ExternalAccountSupplierContext.newBuilder().setAudience("").setSubjectTokenType("").build();
112 
113   @Test
test_awsCredentialSource()114   public void test_awsCredentialSource() {
115     String keys[] = {"region_url", "url", "imdsv2_session_token_url"};
116     for (String key : keys) {
117       Map<String, Object> credentialSourceWithInvalidUrl = buildAwsIpv6CredentialSourceMap();
118       credentialSourceWithInvalidUrl.put(key, "https://badhost.com/fake");
119 
120       // Should succeed as no validation is done.
121       new AwsCredentialSource(credentialSourceWithInvalidUrl);
122     }
123   }
124 
125   @Test
refreshAccessToken_withoutServiceAccountImpersonation()126   public void refreshAccessToken_withoutServiceAccountImpersonation() throws IOException {
127     MockExternalAccountCredentialsTransportFactory transportFactory =
128         new MockExternalAccountCredentialsTransportFactory();
129 
130     AwsCredentials awsCredential =
131         AwsCredentials.newBuilder(AWS_CREDENTIAL)
132             .setTokenUrl(transportFactory.transport.getStsUrl())
133             .setHttpTransportFactory(transportFactory)
134             .setCredentialSource(buildAwsCredentialSource(transportFactory))
135             .build();
136 
137     AccessToken accessToken = awsCredential.refreshAccessToken();
138 
139     assertEquals(transportFactory.transport.getAccessToken(), accessToken.getTokenValue());
140 
141     // Validate metrics header is set correctly on the sts request.
142     Map<String, List<String>> headers =
143         transportFactory.transport.getRequests().get(3).getHeaders();
144     ExternalAccountCredentialsTest.validateMetricsHeader(headers, "aws", false, false);
145   }
146 
147   @Test
refreshAccessToken_withServiceAccountImpersonation()148   public void refreshAccessToken_withServiceAccountImpersonation() throws IOException {
149     MockExternalAccountCredentialsTransportFactory transportFactory =
150         new MockExternalAccountCredentialsTransportFactory();
151 
152     transportFactory.transport.setExpireTime(TestUtils.getDefaultExpireTime());
153 
154     AwsCredentials awsCredential =
155         AwsCredentials.newBuilder()
156             .setHttpTransportFactory(transportFactory)
157             .setAudience("audience")
158             .setSubjectTokenType("subjectTokenType")
159             .setTokenUrl(transportFactory.transport.getStsUrl())
160             .setTokenInfoUrl("tokenInfoUrl")
161             .setCredentialSource(buildAwsCredentialSource(transportFactory))
162             .setServiceAccountImpersonationUrl(
163                 transportFactory.transport.getServiceAccountImpersonationUrl())
164             .build();
165 
166     AccessToken accessToken = awsCredential.refreshAccessToken();
167 
168     assertEquals(
169         transportFactory.transport.getServiceAccountAccessToken(), accessToken.getTokenValue());
170 
171     // Validate metrics header is set correctly on the sts request.
172     Map<String, List<String>> headers =
173         transportFactory.transport.getRequests().get(6).getHeaders();
174     ExternalAccountCredentialsTest.validateMetricsHeader(headers, "aws", true, false);
175   }
176 
177   @Test
refreshAccessToken_withServiceAccountImpersonationOptions()178   public void refreshAccessToken_withServiceAccountImpersonationOptions() throws IOException {
179     MockExternalAccountCredentialsTransportFactory transportFactory =
180         new MockExternalAccountCredentialsTransportFactory();
181 
182     transportFactory.transport.setExpireTime(TestUtils.getDefaultExpireTime());
183 
184     AwsCredentials awsCredential =
185         AwsCredentials.newBuilder()
186             .setHttpTransportFactory(transportFactory)
187             .setAudience("audience")
188             .setSubjectTokenType("subjectTokenType")
189             .setTokenUrl(transportFactory.transport.getStsUrl())
190             .setTokenInfoUrl("tokenInfoUrl")
191             .setCredentialSource(buildAwsCredentialSource(transportFactory))
192             .setServiceAccountImpersonationUrl(
193                 transportFactory.transport.getServiceAccountImpersonationUrl())
194             .setServiceAccountImpersonationOptions(
195                 ExternalAccountCredentialsTest.buildServiceAccountImpersonationOptions(2800))
196             .build();
197 
198     AccessToken accessToken = awsCredential.refreshAccessToken();
199 
200     assertEquals(
201         transportFactory.transport.getServiceAccountAccessToken(), accessToken.getTokenValue());
202 
203     // Validate that default lifetime was set correctly on the request.
204     GenericJson query =
205         OAuth2Utils.JSON_FACTORY
206             .createJsonParser(transportFactory.transport.getLastRequest().getContentAsString())
207             .parseAndClose(GenericJson.class);
208 
209     assertEquals("2800s", query.get("lifetime"));
210 
211     // Validate metrics header is set correctly on the sts request.
212     Map<String, List<String>> headers =
213         transportFactory.transport.getRequests().get(6).getHeaders();
214     ExternalAccountCredentialsTest.validateMetricsHeader(headers, "aws", true, true);
215   }
216 
217   @Test
refreshAccessTokenProgrammaticRefresh_withoutServiceAccountImpersonation()218   public void refreshAccessTokenProgrammaticRefresh_withoutServiceAccountImpersonation()
219       throws IOException {
220     MockExternalAccountCredentialsTransportFactory transportFactory =
221         new MockExternalAccountCredentialsTransportFactory();
222 
223     AwsSecurityCredentialsSupplier supplier =
224         new TestAwsSecurityCredentialsSupplier("test", programmaticAwsCreds, null, null);
225 
226     AwsCredentials awsCredential =
227         AwsCredentials.newBuilder()
228             .setAwsSecurityCredentialsSupplier(supplier)
229             .setHttpTransportFactory(transportFactory)
230             .setAudience("audience")
231             .setTokenUrl(STS_URL)
232             .setSubjectTokenType("subjectTokenType")
233             .build();
234 
235     AccessToken accessToken = awsCredential.refreshAccessToken();
236 
237     assertEquals(transportFactory.transport.getAccessToken(), accessToken.getTokenValue());
238 
239     // Validate metrics header is set correctly on the sts request.
240     Map<String, List<String>> headers =
241         transportFactory.transport.getRequests().get(0).getHeaders();
242     ExternalAccountCredentialsTest.validateMetricsHeader(headers, "programmatic", false, false);
243   }
244 
245   @Test
refreshAccessTokenProgrammaticRefresh_withServiceAccountImpersonation()246   public void refreshAccessTokenProgrammaticRefresh_withServiceAccountImpersonation()
247       throws IOException {
248     MockExternalAccountCredentialsTransportFactory transportFactory =
249         new MockExternalAccountCredentialsTransportFactory();
250 
251     transportFactory.transport.setExpireTime(TestUtils.getDefaultExpireTime());
252 
253     AwsSecurityCredentialsSupplier supplier =
254         new TestAwsSecurityCredentialsSupplier("test", programmaticAwsCreds, null, null);
255 
256     AwsCredentials awsCredential =
257         AwsCredentials.newBuilder()
258             .setAwsSecurityCredentialsSupplier(supplier)
259             .setHttpTransportFactory(transportFactory)
260             .setAudience("audience")
261             .setTokenUrl(STS_URL)
262             .setSubjectTokenType("subjectTokenType")
263             .setServiceAccountImpersonationUrl(
264                 transportFactory.transport.getServiceAccountImpersonationUrl())
265             .build();
266 
267     AccessToken accessToken = awsCredential.refreshAccessToken();
268 
269     assertEquals(
270         transportFactory.transport.getServiceAccountAccessToken(), accessToken.getTokenValue());
271 
272     // Validate metrics header is set correctly on the sts request.
273     Map<String, List<String>> headers =
274         transportFactory.transport.getRequests().get(0).getHeaders();
275     ExternalAccountCredentialsTest.validateMetricsHeader(headers, "programmatic", true, false);
276   }
277 
278   @Test
279   @SuppressWarnings("unchecked")
retrieveSubjectToken()280   public void retrieveSubjectToken() throws IOException {
281     MockExternalAccountCredentialsTransportFactory transportFactory =
282         new MockExternalAccountCredentialsTransportFactory();
283 
284     AwsCredentials awsCredential =
285         AwsCredentials.newBuilder(AWS_CREDENTIAL)
286             .setHttpTransportFactory(transportFactory)
287             .setCredentialSource(buildAwsCredentialSource(transportFactory))
288             .build();
289 
290     String subjectToken = URLDecoder.decode(awsCredential.retrieveSubjectToken(), "UTF-8");
291 
292     JsonParser parser = OAuth2Utils.JSON_FACTORY.createJsonParser(subjectToken);
293     GenericJson json = parser.parseAndClose(GenericJson.class);
294 
295     List<Map<String, String>> headersList = (List<Map<String, String>>) json.get("headers");
296     Map<String, String> headers = new HashMap<>();
297     for (Map<String, String> header : headersList) {
298       headers.put(header.get("key"), header.get("value"));
299     }
300 
301     assertEquals("POST", json.get("method"));
302     assertEquals(GET_CALLER_IDENTITY_URL, json.get("url"));
303     assertEquals(URI.create(GET_CALLER_IDENTITY_URL).getHost(), headers.get("host"));
304     assertEquals("token", headers.get("x-amz-security-token"));
305     assertEquals(awsCredential.getAudience(), headers.get("x-goog-cloud-target-resource"));
306     assertTrue(headers.containsKey("x-amz-date"));
307     assertNotNull(headers.get("Authorization"));
308 
309     List<MockLowLevelHttpRequest> requests = transportFactory.transport.getRequests();
310     assertEquals(3, requests.size());
311 
312     // Validate region request.
313     ValidateRequest(requests.get(0), AWS_REGION_URL, EMPTY_STRING_HEADERS);
314 
315     // Validate role request.
316     ValidateRequest(requests.get(1), AWS_CREDENTIALS_URL, EMPTY_STRING_HEADERS);
317 
318     // Validate security credentials request.
319     ValidateRequest(requests.get(2), AWS_CREDENTIALS_URL_WITH_ROLE, EMPTY_STRING_HEADERS);
320   }
321 
322   @Test
323   @SuppressWarnings("unchecked")
retrieveSubjectTokenWithSessionTokenUrl()324   public void retrieveSubjectTokenWithSessionTokenUrl() throws IOException {
325     MockExternalAccountCredentialsTransportFactory transportFactory =
326         new MockExternalAccountCredentialsTransportFactory();
327 
328     AwsCredentials awsCredential =
329         AwsCredentials.newBuilder(AWS_CREDENTIAL)
330             .setHttpTransportFactory(transportFactory)
331             .setCredentialSource(buildAwsImdsv2CredentialSource(transportFactory))
332             .build();
333 
334     String subjectToken = URLDecoder.decode(awsCredential.retrieveSubjectToken(), "UTF-8");
335 
336     JsonParser parser = OAuth2Utils.JSON_FACTORY.createJsonParser(subjectToken);
337     GenericJson json = parser.parseAndClose(GenericJson.class);
338 
339     List<Map<String, String>> headersList = (List<Map<String, String>>) json.get("headers");
340     Map<String, String> headers = new HashMap<>();
341     for (Map<String, String> header : headersList) {
342       headers.put(header.get("key"), header.get("value"));
343     }
344 
345     assertEquals("POST", json.get("method"));
346     assertEquals(GET_CALLER_IDENTITY_URL, json.get("url"));
347     assertEquals(URI.create(GET_CALLER_IDENTITY_URL).getHost(), headers.get("host"));
348     assertEquals("token", headers.get("x-amz-security-token"));
349     assertEquals(awsCredential.getAudience(), headers.get("x-goog-cloud-target-resource"));
350     assertTrue(headers.containsKey("x-amz-date"));
351     assertNotNull(headers.get("Authorization"));
352 
353     List<MockLowLevelHttpRequest> requests = transportFactory.transport.getRequests();
354     assertEquals(5, requests.size());
355 
356     // Validate the session token request
357     ValidateRequest(
358         requests.get(0),
359         AWS_IMDSV2_SESSION_TOKEN_URL,
360         new HashMap<String, String>() {
361           {
362             put(
363                 InternalAwsSecurityCredentialsSupplier.AWS_IMDSV2_SESSION_TOKEN_TTL_HEADER,
364                 InternalAwsSecurityCredentialsSupplier.AWS_IMDSV2_SESSION_TOKEN_TTL);
365           }
366         });
367 
368     Map<String, String> sessionTokenHeader =
369         new HashMap<String, String>() {
370           {
371             put(
372                 InternalAwsSecurityCredentialsSupplier.AWS_IMDSV2_SESSION_TOKEN_HEADER,
373                 AWS_IMDSV2_SESSION_TOKEN);
374           }
375         };
376 
377     // Validate region request.
378     ValidateRequest(requests.get(1), AWS_REGION_URL, sessionTokenHeader);
379 
380     // Validate role request.
381     ValidateRequest(requests.get(3), AWS_CREDENTIALS_URL, sessionTokenHeader);
382 
383     // Validate security credentials request.
384     ValidateRequest(requests.get(4), AWS_CREDENTIALS_URL_WITH_ROLE, sessionTokenHeader);
385   }
386 
387   @Test
retrieveSubjectToken_imdsv1EnvVariablesSet_metadataServerNotCalled()388   public void retrieveSubjectToken_imdsv1EnvVariablesSet_metadataServerNotCalled()
389       throws IOException {
390     MockExternalAccountCredentialsTransportFactory transportFactory =
391         new MockExternalAccountCredentialsTransportFactory();
392 
393     // Provide AWS credentials through environment vars.
394     TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider();
395     environmentProvider
396         .setEnv("AWS_REGION", "awsRegion")
397         .setEnv("AWS_ACCESS_KEY_ID", "awsAccessKeyId")
398         .setEnv("AWS_SECRET_ACCESS_KEY", "awsSecretAccessKey")
399         .setEnv("AWS_SESSION_TOKEN", "awsToken");
400 
401     AwsCredentials awsCredential =
402         AwsCredentials.newBuilder(AWS_CREDENTIAL)
403             .setHttpTransportFactory(transportFactory)
404             .setCredentialSource(buildAwsCredentialSource(transportFactory))
405             .setEnvironmentProvider(environmentProvider)
406             .build();
407 
408     String subjectToken = URLDecoder.decode(awsCredential.retrieveSubjectToken(), "UTF-8");
409 
410     JsonParser parser = OAuth2Utils.JSON_FACTORY.createJsonParser(subjectToken);
411     GenericJson json = parser.parseAndClose(GenericJson.class);
412 
413     List<Map<String, String>> headersList = (List<Map<String, String>>) json.get("headers");
414     Map<String, String> headers = new HashMap<>();
415     for (Map<String, String> header : headersList) {
416       headers.put(header.get("key"), header.get("value"));
417     }
418 
419     assertEquals("POST", json.get("method"));
420     assertEquals(GET_CALLER_IDENTITY_URL, json.get("url"));
421     assertEquals(URI.create(GET_CALLER_IDENTITY_URL).getHost(), headers.get("host"));
422     assertEquals("awsToken", headers.get("x-amz-security-token"));
423     assertEquals(awsCredential.getAudience(), headers.get("x-goog-cloud-target-resource"));
424     assertTrue(headers.containsKey("x-amz-date"));
425     assertNotNull(headers.get("Authorization"));
426 
427     // No requests should have been made since AWS credentials and region is passed through
428     // environment variables.
429     List<MockLowLevelHttpRequest> requests = transportFactory.transport.getRequests();
430     assertEquals(0, requests.size());
431   }
432 
433   @Test
retrieveSubjectToken_imdsv2EnvVariablesSet_metadataServerNotCalled()434   public void retrieveSubjectToken_imdsv2EnvVariablesSet_metadataServerNotCalled()
435       throws IOException {
436     MockExternalAccountCredentialsTransportFactory transportFactory =
437         new MockExternalAccountCredentialsTransportFactory();
438 
439     // Provide AWS credentials through environment vars.
440     TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider();
441     environmentProvider
442         .setEnv("AWS_REGION", "awsRegion")
443         .setEnv("AWS_ACCESS_KEY_ID", "awsAccessKeyId")
444         .setEnv("AWS_SECRET_ACCESS_KEY", "awsSecretAccessKey")
445         .setEnv("AWS_SESSION_TOKEN", "awsToken");
446 
447     AwsCredentials awsCredential =
448         AwsCredentials.newBuilder(AWS_CREDENTIAL)
449             .setHttpTransportFactory(transportFactory)
450             .setCredentialSource(buildAwsImdsv2CredentialSource(transportFactory))
451             .setEnvironmentProvider(environmentProvider)
452             .build();
453 
454     String subjectToken = URLDecoder.decode(awsCredential.retrieveSubjectToken(), "UTF-8");
455 
456     JsonParser parser = OAuth2Utils.JSON_FACTORY.createJsonParser(subjectToken);
457     GenericJson json = parser.parseAndClose(GenericJson.class);
458 
459     List<Map<String, String>> headersList = (List<Map<String, String>>) json.get("headers");
460     Map<String, String> headers = new HashMap<>();
461     for (Map<String, String> header : headersList) {
462       headers.put(header.get("key"), header.get("value"));
463     }
464 
465     assertEquals("POST", json.get("method"));
466     assertEquals(GET_CALLER_IDENTITY_URL, json.get("url"));
467     assertEquals(URI.create(GET_CALLER_IDENTITY_URL).getHost(), headers.get("host"));
468     assertEquals("awsToken", headers.get("x-amz-security-token"));
469     assertEquals(awsCredential.getAudience(), headers.get("x-goog-cloud-target-resource"));
470     assertTrue(headers.containsKey("x-amz-date"));
471     assertNotNull(headers.get("Authorization"));
472 
473     // No requests should have been made since AWS credentials and region is passed through
474     // environment variables.
475     List<MockLowLevelHttpRequest> requests = transportFactory.transport.getRequests();
476     assertEquals(0, requests.size());
477   }
478 
479   @Test
retrieveSubjectToken_noRegion_expectThrows()480   public void retrieveSubjectToken_noRegion_expectThrows() {
481     MockExternalAccountCredentialsTransportFactory transportFactory =
482         new MockExternalAccountCredentialsTransportFactory();
483 
484     IOException response = new IOException();
485     transportFactory.transport.addResponseErrorSequence(response);
486 
487     AwsCredentials awsCredential =
488         AwsCredentials.newBuilder(AWS_CREDENTIAL)
489             .setHttpTransportFactory(transportFactory)
490             .setCredentialSource(buildAwsCredentialSource(transportFactory))
491             .build();
492 
493     try {
494       awsCredential.retrieveSubjectToken();
495       fail("Should not be able to use credential without exception.");
496     } catch (IOException exception) {
497       assertEquals("Failed to retrieve AWS region.", exception.getMessage());
498     }
499 
500     List<MockLowLevelHttpRequest> requests = transportFactory.transport.getRequests();
501     assertEquals(1, requests.size());
502 
503     // Validate region request.
504     ValidateRequest(requests.get(0), AWS_REGION_URL, EMPTY_STRING_HEADERS);
505   }
506 
507   @Test
retrieveSubjectToken_noRole_expectThrows()508   public void retrieveSubjectToken_noRole_expectThrows() {
509     MockExternalAccountCredentialsTransportFactory transportFactory =
510         new MockExternalAccountCredentialsTransportFactory();
511 
512     IOException response = new IOException();
513     transportFactory.transport.addResponseErrorSequence(response);
514     transportFactory.transport.addResponseSequence(true, false);
515 
516     AwsCredentials awsCredential =
517         AwsCredentials.newBuilder(AWS_CREDENTIAL)
518             .setHttpTransportFactory(transportFactory)
519             .setCredentialSource(buildAwsCredentialSource(transportFactory))
520             .build();
521 
522     try {
523       awsCredential.retrieveSubjectToken();
524       fail("Should not be able to use credential without exception.");
525     } catch (IOException exception) {
526       assertEquals("Failed to retrieve AWS IAM role.", exception.getMessage());
527     }
528 
529     List<MockLowLevelHttpRequest> requests = transportFactory.transport.getRequests();
530     assertEquals(2, requests.size());
531 
532     // Validate region request.
533     ValidateRequest(requests.get(0), AWS_REGION_URL, EMPTY_STRING_HEADERS);
534 
535     // Validate role request.
536     ValidateRequest(requests.get(1), AWS_CREDENTIALS_URL, EMPTY_STRING_HEADERS);
537   }
538 
539   @Test
retrieveSubjectToken_noCredentials_expectThrows()540   public void retrieveSubjectToken_noCredentials_expectThrows() {
541     MockExternalAccountCredentialsTransportFactory transportFactory =
542         new MockExternalAccountCredentialsTransportFactory();
543 
544     IOException response = new IOException();
545     transportFactory.transport.addResponseErrorSequence(response);
546     transportFactory.transport.addResponseSequence(true, true, false);
547 
548     AwsCredentials awsCredential =
549         AwsCredentials.newBuilder(AWS_CREDENTIAL)
550             .setHttpTransportFactory(transportFactory)
551             .setCredentialSource(buildAwsCredentialSource(transportFactory))
552             .build();
553 
554     try {
555       awsCredential.retrieveSubjectToken();
556       fail("Should not be able to use credential without exception.");
557     } catch (IOException exception) {
558       assertEquals("Failed to retrieve AWS credentials.", exception.getMessage());
559     }
560 
561     List<MockLowLevelHttpRequest> requests = transportFactory.transport.getRequests();
562     assertEquals(3, requests.size());
563 
564     // Validate region request.
565     ValidateRequest(requests.get(0), AWS_REGION_URL, EMPTY_STRING_HEADERS);
566 
567     // Validate role request.
568     ValidateRequest(requests.get(1), AWS_CREDENTIALS_URL, EMPTY_STRING_HEADERS);
569 
570     // Validate security credentials request.
571     ValidateRequest(requests.get(2), AWS_CREDENTIALS_URL_WITH_ROLE, EMPTY_STRING_HEADERS);
572   }
573 
574   @Test
retrieveSubjectToken_noRegionUrlProvided()575   public void retrieveSubjectToken_noRegionUrlProvided() {
576     MockExternalAccountCredentialsTransportFactory transportFactory =
577         new MockExternalAccountCredentialsTransportFactory();
578 
579     Map<String, Object> credentialSource = new HashMap<>();
580     credentialSource.put("environment_id", "aws1");
581     credentialSource.put("regional_cred_verification_url", GET_CALLER_IDENTITY_URL);
582 
583     AwsCredentials awsCredential =
584         AwsCredentials.newBuilder(AWS_CREDENTIAL)
585             .setHttpTransportFactory(transportFactory)
586             .setCredentialSource(new AwsCredentialSource(credentialSource))
587             .build();
588 
589     try {
590       awsCredential.retrieveSubjectToken();
591       fail("Should not be able to use credential without exception.");
592     } catch (IOException exception) {
593       assertEquals(
594           "Unable to determine the AWS region. The credential source does not "
595               + "contain the region URL.",
596           exception.getMessage());
597     }
598 
599     // No requests because the credential source does not contain region URL.
600     List<MockLowLevelHttpRequest> requests = transportFactory.transport.getRequests();
601     assertTrue(requests.isEmpty());
602   }
603 
604   @Test
retrieveSubjectToken_withProgrammaticRefresh()605   public void retrieveSubjectToken_withProgrammaticRefresh() throws IOException {
606     MockExternalAccountCredentialsTransportFactory transportFactory =
607         new MockExternalAccountCredentialsTransportFactory();
608 
609     AwsSecurityCredentialsSupplier supplier =
610         new TestAwsSecurityCredentialsSupplier("test", programmaticAwsCreds, null, null);
611 
612     AwsCredentials awsCredential =
613         AwsCredentials.newBuilder()
614             .setAwsSecurityCredentialsSupplier(supplier)
615             .setHttpTransportFactory(transportFactory)
616             .setAudience("audience")
617             .setTokenUrl(STS_URL)
618             .setSubjectTokenType("subjectTokenType")
619             .build();
620 
621     String subjectToken = URLDecoder.decode(awsCredential.retrieveSubjectToken(), "UTF-8");
622 
623     JsonParser parser = OAuth2Utils.JSON_FACTORY.createJsonParser(subjectToken);
624     GenericJson json = parser.parseAndClose(GenericJson.class);
625 
626     List<Map<String, String>> headersList = (List<Map<String, String>>) json.get("headers");
627     Map<String, String> headers = new HashMap<>();
628     for (Map<String, String> header : headersList) {
629       headers.put(header.get("key"), header.get("value"));
630     }
631 
632     String expectedCredentialVerificationUrl =
633         DEFAULT_REGIONAL_CREDENTIAL_VERIFICATION_URL.replace("{region}", "test");
634 
635     assertEquals("POST", json.get("method"));
636     assertEquals(expectedCredentialVerificationUrl, json.get("url"));
637     assertEquals(URI.create(expectedCredentialVerificationUrl).getHost(), headers.get("host"));
638     assertEquals(awsCredential.getAudience(), headers.get("x-goog-cloud-target-resource"));
639     assertTrue(headers.containsKey("x-amz-date"));
640     assertNotNull(headers.get("Authorization"));
641   }
642 
643   @Test
retrieveSubjectToken_withProgrammaticRefreshSessionToken()644   public void retrieveSubjectToken_withProgrammaticRefreshSessionToken() throws IOException {
645     MockExternalAccountCredentialsTransportFactory transportFactory =
646         new MockExternalAccountCredentialsTransportFactory();
647 
648     AwsSecurityCredentials securityCredentialsWithToken =
649         new AwsSecurityCredentials("accessToken", "secretAccessKey", "token");
650 
651     AwsSecurityCredentialsSupplier supplier =
652         new TestAwsSecurityCredentialsSupplier("test", securityCredentialsWithToken, null, null);
653 
654     AwsCredentials awsCredential =
655         AwsCredentials.newBuilder()
656             .setAwsSecurityCredentialsSupplier(supplier)
657             .setHttpTransportFactory(transportFactory)
658             .setAudience("audience")
659             .setTokenUrl(STS_URL)
660             .setSubjectTokenType("subjectTokenType")
661             .build();
662 
663     String subjectToken = URLDecoder.decode(awsCredential.retrieveSubjectToken(), "UTF-8");
664 
665     JsonParser parser = OAuth2Utils.JSON_FACTORY.createJsonParser(subjectToken);
666     GenericJson json = parser.parseAndClose(GenericJson.class);
667 
668     List<Map<String, String>> headersList = (List<Map<String, String>>) json.get("headers");
669     Map<String, String> headers = new HashMap<>();
670     for (Map<String, String> header : headersList) {
671       headers.put(header.get("key"), header.get("value"));
672     }
673 
674     String expectedCredentialVerificationUrl =
675         DEFAULT_REGIONAL_CREDENTIAL_VERIFICATION_URL.replace("{region}", "test");
676 
677     assertEquals("POST", json.get("method"));
678     assertEquals(expectedCredentialVerificationUrl, json.get("url"));
679     assertEquals(URI.create(expectedCredentialVerificationUrl).getHost(), headers.get("host"));
680     assertEquals("token", headers.get("x-amz-security-token"));
681     assertEquals(awsCredential.getAudience(), headers.get("x-goog-cloud-target-resource"));
682     assertTrue(headers.containsKey("x-amz-date"));
683     assertNotNull(headers.get("Authorization"));
684   }
685 
686   @Test
retrieveSubjectToken_passesContext()687   public void retrieveSubjectToken_passesContext() throws IOException {
688     MockExternalAccountCredentialsTransportFactory transportFactory =
689         new MockExternalAccountCredentialsTransportFactory();
690 
691     AwsSecurityCredentials securityCredentialsWithToken =
692         new AwsSecurityCredentials("accessToken", "secretAccessKey", "token");
693 
694     ExternalAccountSupplierContext expectedContext =
695         ExternalAccountSupplierContext.newBuilder()
696             .setAudience("audience")
697             .setSubjectTokenType("subjectTokenType")
698             .build();
699 
700     AwsSecurityCredentialsSupplier supplier =
701         new TestAwsSecurityCredentialsSupplier(
702             "test", securityCredentialsWithToken, null, expectedContext);
703 
704     AwsCredentials awsCredential =
705         AwsCredentials.newBuilder()
706             .setAwsSecurityCredentialsSupplier(supplier)
707             .setHttpTransportFactory(transportFactory)
708             .setAudience("audience")
709             .setTokenUrl(STS_URL)
710             .setSubjectTokenType("subjectTokenType")
711             .build();
712 
713     awsCredential.retrieveSubjectToken();
714   }
715 
716   @Test
retrieveSubjectToken_withProgrammaticRefreshThrowsError()717   public void retrieveSubjectToken_withProgrammaticRefreshThrowsError() throws IOException {
718     MockExternalAccountCredentialsTransportFactory transportFactory =
719         new MockExternalAccountCredentialsTransportFactory();
720 
721     IOException testException = new IOException("test");
722 
723     AwsSecurityCredentialsSupplier supplier =
724         new TestAwsSecurityCredentialsSupplier("test", null, testException, null);
725 
726     AwsCredentials awsCredential =
727         AwsCredentials.newBuilder()
728             .setAwsSecurityCredentialsSupplier(supplier)
729             .setHttpTransportFactory(transportFactory)
730             .setAudience("audience")
731             .setTokenUrl(STS_URL)
732             .setSubjectTokenType("subjectTokenType")
733             .build();
734 
735     try {
736       String subjectToken = URLDecoder.decode(awsCredential.retrieveSubjectToken(), "UTF-8");
737       fail("retrieveSubjectToken should not succeed");
738     } catch (IOException e) {
739       assertEquals("test", e.getMessage());
740     }
741   }
742 
743   @Test
getAwsSecurityCredentials_fromEnvironmentVariablesNoToken()744   public void getAwsSecurityCredentials_fromEnvironmentVariablesNoToken() throws IOException {
745     TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider();
746     environmentProvider
747         .setEnv("AWS_ACCESS_KEY_ID", "awsAccessKeyId")
748         .setEnv("AWS_SECRET_ACCESS_KEY", "awsSecretAccessKey");
749 
750     AwsCredentials testAwsCredentials =
751         AwsCredentials.newBuilder(AWS_CREDENTIAL)
752             .setEnvironmentProvider(environmentProvider)
753             .build();
754 
755     AwsSecurityCredentials credentials =
756         testAwsCredentials.getAwsSecurityCredentialsSupplier().getCredentials(emptyContext);
757 
758     assertEquals("awsAccessKeyId", credentials.getAccessKeyId());
759     assertEquals("awsSecretAccessKey", credentials.getSecretAccessKey());
760     assertNull(credentials.getSessionToken());
761   }
762 
763   @Test
getAwsSecurityCredentials_fromEnvironmentVariablesWithToken()764   public void getAwsSecurityCredentials_fromEnvironmentVariablesWithToken() throws IOException {
765     TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider();
766     environmentProvider
767         .setEnv("AWS_ACCESS_KEY_ID", "awsAccessKeyId")
768         .setEnv("AWS_SECRET_ACCESS_KEY", "awsSecretAccessKey")
769         .setEnv("AWS_SESSION_TOKEN", "awsSessionToken");
770 
771     AwsCredentialSource credSource =
772         new AwsCredentialSource(
773             new HashMap<String, Object>() {
774               {
775                 put("environment_id", "aws1");
776                 put("region_url", "");
777                 put("url", "");
778                 put("regional_cred_verification_url", "regionalCredVerificationUrl");
779               }
780             });
781 
782     AwsCredentials testAwsCredentials =
783         AwsCredentials.newBuilder(AWS_CREDENTIAL)
784             .setEnvironmentProvider(environmentProvider)
785             .setCredentialSource(credSource)
786             .build();
787 
788     AwsSecurityCredentials credentials =
789         testAwsCredentials.getAwsSecurityCredentialsSupplier().getCredentials(emptyContext);
790 
791     assertEquals("awsAccessKeyId", credentials.getAccessKeyId());
792     assertEquals("awsSecretAccessKey", credentials.getSecretAccessKey());
793     assertEquals("awsSessionToken", credentials.getSessionToken());
794   }
795 
796   @Test
getAwsSecurityCredentials_fromEnvironmentVariables_noMetadataServerCall()797   public void getAwsSecurityCredentials_fromEnvironmentVariables_noMetadataServerCall()
798       throws IOException {
799     TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider();
800     environmentProvider
801         .setEnv("AWS_ACCESS_KEY_ID", "awsAccessKeyId")
802         .setEnv("AWS_SECRET_ACCESS_KEY", "awsSecretAccessKey")
803         .setEnv("AWS_SESSION_TOKEN", "awsSessionToken");
804 
805     AwsCredentials testAwsCredentials =
806         AwsCredentials.newBuilder(AWS_CREDENTIAL)
807             .setEnvironmentProvider(environmentProvider)
808             .build();
809 
810     AwsSecurityCredentials credentials =
811         testAwsCredentials.getAwsSecurityCredentialsSupplier().getCredentials(emptyContext);
812 
813     assertEquals("awsAccessKeyId", credentials.getAccessKeyId());
814     assertEquals("awsSecretAccessKey", credentials.getSecretAccessKey());
815     assertEquals("awsSessionToken", credentials.getSessionToken());
816   }
817 
818   @Test
getAwsSecurityCredentials_fromMetadataServer()819   public void getAwsSecurityCredentials_fromMetadataServer() throws IOException {
820     MockExternalAccountCredentialsTransportFactory transportFactory =
821         new MockExternalAccountCredentialsTransportFactory();
822 
823     AwsCredentials awsCredential =
824         AwsCredentials.newBuilder(AWS_CREDENTIAL)
825             .setHttpTransportFactory(transportFactory)
826             .setCredentialSource(buildAwsCredentialSource(transportFactory))
827             .build();
828 
829     AwsSecurityCredentials credentials =
830         awsCredential.getAwsSecurityCredentialsSupplier().getCredentials(emptyContext);
831 
832     assertEquals("accessKeyId", credentials.getAccessKeyId());
833     assertEquals("secretAccessKey", credentials.getSecretAccessKey());
834     assertEquals("token", credentials.getSessionToken());
835 
836     List<MockLowLevelHttpRequest> requests = transportFactory.transport.getRequests();
837     assertEquals(2, requests.size());
838 
839     // Validate role request.
840     ValidateRequest(requests.get(0), AWS_CREDENTIALS_URL, EMPTY_STRING_HEADERS);
841 
842     // Validate security credentials request.
843     ValidateRequest(requests.get(1), AWS_CREDENTIALS_URL_WITH_ROLE, EMPTY_STRING_HEADERS);
844   }
845 
846   @Test
getAwsSecurityCredentials_fromMetadataServer_noUrlProvided()847   public void getAwsSecurityCredentials_fromMetadataServer_noUrlProvided() {
848     MockExternalAccountCredentialsTransportFactory transportFactory =
849         new MockExternalAccountCredentialsTransportFactory();
850 
851     Map<String, Object> credentialSource = new HashMap<>();
852     credentialSource.put("environment_id", "aws1");
853     credentialSource.put("regional_cred_verification_url", GET_CALLER_IDENTITY_URL);
854 
855     AwsCredentials awsCredential =
856         AwsCredentials.newBuilder(AWS_CREDENTIAL)
857             .setHttpTransportFactory(transportFactory)
858             .setCredentialSource(new AwsCredentialSource(credentialSource))
859             .build();
860 
861     try {
862       awsCredential.getAwsSecurityCredentialsSupplier().getCredentials(emptyContext);
863       fail("Should not be able to use credential without exception.");
864     } catch (IOException exception) {
865       assertEquals(
866           "Unable to determine the AWS IAM role name. The credential source does not contain the url field.",
867           exception.getMessage());
868     }
869 
870     // No requests because url field is not present in credential source.
871     List<MockLowLevelHttpRequest> requests = transportFactory.transport.getRequests();
872     assertTrue(requests.isEmpty());
873   }
874 
875   @Test
getAwsRegion_awsRegionEnvironmentVariable()876   public void getAwsRegion_awsRegionEnvironmentVariable() throws IOException {
877     TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider();
878     environmentProvider.setEnv("AWS_REGION", "region");
879     environmentProvider.setEnv("AWS_DEFAULT_REGION", "defaultRegion");
880 
881     MockExternalAccountCredentialsTransportFactory transportFactory =
882         new MockExternalAccountCredentialsTransportFactory();
883     AwsCredentials awsCredentials =
884         AwsCredentials.newBuilder(AWS_CREDENTIAL)
885             .setHttpTransportFactory(transportFactory)
886             .setCredentialSource(buildAwsCredentialSource(transportFactory))
887             .setEnvironmentProvider(environmentProvider)
888             .build();
889 
890     String region = awsCredentials.getAwsSecurityCredentialsSupplier().getRegion(emptyContext);
891 
892     // Should attempt to retrieve the region from AWS_REGION env var first.
893     // Metadata server would return us-east-1b.
894     assertEquals("region", region);
895 
896     // No requests because region is obtained from environment variables.
897     List<MockLowLevelHttpRequest> requests = transportFactory.transport.getRequests();
898     assertTrue(requests.isEmpty());
899   }
900 
901   @Test
getAwsRegion_awsDefaultRegionEnvironmentVariable()902   public void getAwsRegion_awsDefaultRegionEnvironmentVariable() throws IOException {
903     TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider();
904     environmentProvider.setEnv("AWS_DEFAULT_REGION", "defaultRegion");
905 
906     MockExternalAccountCredentialsTransportFactory transportFactory =
907         new MockExternalAccountCredentialsTransportFactory();
908     AwsCredentials awsCredentials =
909         AwsCredentials.newBuilder(AWS_CREDENTIAL)
910             .setHttpTransportFactory(transportFactory)
911             .setCredentialSource(buildAwsCredentialSource(transportFactory))
912             .setEnvironmentProvider(environmentProvider)
913             .build();
914 
915     String region = awsCredentials.getAwsSecurityCredentialsSupplier().getRegion(emptyContext);
916 
917     // Should attempt to retrieve the region from DEFAULT_AWS_REGION before calling the metadata
918     // server. Metadata server would return us-east-1b.
919     assertEquals("defaultRegion", region);
920 
921     // No requests because region is obtained from environment variables.
922     List<MockLowLevelHttpRequest> requests = transportFactory.transport.getRequests();
923     assertTrue(requests.isEmpty());
924   }
925 
926   @Test
getAwsRegion_metadataServer()927   public void getAwsRegion_metadataServer() throws IOException {
928     MockExternalAccountCredentialsTransportFactory transportFactory =
929         new MockExternalAccountCredentialsTransportFactory();
930     AwsCredentials awsCredentials =
931         AwsCredentials.newBuilder(AWS_CREDENTIAL)
932             .setHttpTransportFactory(transportFactory)
933             .setCredentialSource(buildAwsCredentialSource(transportFactory))
934             .build();
935 
936     String region = awsCredentials.getAwsSecurityCredentialsSupplier().getRegion(emptyContext);
937 
938     // Should retrieve the region from the Metadata server.
939     String expectedRegion =
940         transportFactory
941             .transport
942             .getAwsRegion()
943             .substring(0, transportFactory.transport.getAwsRegion().length() - 1);
944     assertEquals(expectedRegion, region);
945 
946     List<MockLowLevelHttpRequest> requests = transportFactory.transport.getRequests();
947     assertEquals(1, requests.size());
948 
949     // Validate region request.
950     ValidateRequest(requests.get(0), AWS_REGION_URL, EMPTY_STRING_HEADERS);
951   }
952 
953   @Test
createdScoped_clonedCredentialWithAddedScopes()954   public void createdScoped_clonedCredentialWithAddedScopes() throws IOException {
955     AwsCredentials credentials =
956         AwsCredentials.newBuilder(AWS_CREDENTIAL)
957             .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL)
958             .setQuotaProjectId("quotaProjectId")
959             .setClientId("clientId")
960             .setClientSecret("clientSecret")
961             .setUniverseDomain("universeDomain")
962             .build();
963 
964     List<String> newScopes = Arrays.asList("scope1", "scope2");
965 
966     AwsCredentials newCredentials = (AwsCredentials) credentials.createScoped(newScopes);
967 
968     assertEquals(credentials.getAudience(), newCredentials.getAudience());
969     assertEquals(credentials.getSubjectTokenType(), newCredentials.getSubjectTokenType());
970     assertEquals(credentials.getTokenUrl(), newCredentials.getTokenUrl());
971     assertEquals(credentials.getTokenInfoUrl(), newCredentials.getTokenInfoUrl());
972     assertEquals(
973         credentials.getServiceAccountImpersonationUrl(),
974         newCredentials.getServiceAccountImpersonationUrl());
975     assertEquals(credentials.getCredentialSource(), newCredentials.getCredentialSource());
976     assertEquals(credentials.getQuotaProjectId(), newCredentials.getQuotaProjectId());
977     assertEquals(credentials.getClientId(), newCredentials.getClientId());
978     assertEquals(credentials.getClientSecret(), newCredentials.getClientSecret());
979     assertEquals(newScopes, newCredentials.getScopes());
980     assertEquals(credentials.getUniverseDomain(), newCredentials.getUniverseDomain());
981     assertEquals("universeDomain", newCredentials.getUniverseDomain());
982   }
983 
984   @Test
credentialSource_invalidAwsEnvironmentId()985   public void credentialSource_invalidAwsEnvironmentId() {
986     Map<String, Object> credentialSource = new HashMap<>();
987     credentialSource.put("regional_cred_verification_url", GET_CALLER_IDENTITY_URL);
988     credentialSource.put("environment_id", "azure1");
989 
990     try {
991       new AwsCredentialSource(credentialSource);
992       fail("Exception should be thrown.");
993     } catch (IllegalArgumentException e) {
994       assertEquals("Invalid AWS environment ID.", e.getMessage());
995     }
996   }
997 
998   @Test
credentialSource_invalidAwsEnvironmentVersion()999   public void credentialSource_invalidAwsEnvironmentVersion() {
1000     Map<String, Object> credentialSource = new HashMap<>();
1001     int environmentVersion = 2;
1002     credentialSource.put("regional_cred_verification_url", GET_CALLER_IDENTITY_URL);
1003     credentialSource.put("environment_id", "aws" + environmentVersion);
1004 
1005     try {
1006       new AwsCredentialSource(credentialSource);
1007       fail("Exception should be thrown.");
1008     } catch (IllegalArgumentException e) {
1009       assertEquals(
1010           String.format(
1011               "AWS version %s is not supported in the current build.", environmentVersion),
1012           e.getMessage());
1013     }
1014   }
1015 
1016   @Test
credentialSource_missingRegionalCredVerificationUrl()1017   public void credentialSource_missingRegionalCredVerificationUrl() {
1018     try {
1019       new AwsCredentialSource(new HashMap<String, Object>());
1020       fail("Exception should be thrown.");
1021     } catch (IllegalArgumentException e) {
1022       assertEquals(
1023           "A regional_cred_verification_url representing the GetCallerIdentity action URL must be specified.",
1024           e.getMessage());
1025     }
1026   }
1027 
1028   @Test
builder_allFields()1029   public void builder_allFields() throws IOException {
1030     List<String> scopes = Arrays.asList("scope1", "scope2");
1031 
1032     AwsCredentials credentials =
1033         AwsCredentials.newBuilder()
1034             .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY)
1035             .setAudience("audience")
1036             .setSubjectTokenType("subjectTokenType")
1037             .setTokenUrl(STS_URL)
1038             .setTokenInfoUrl("tokenInfoUrl")
1039             .setCredentialSource(AWS_CREDENTIAL_SOURCE)
1040             .setTokenInfoUrl("tokenInfoUrl")
1041             .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL)
1042             .setQuotaProjectId("quotaProjectId")
1043             .setClientId("clientId")
1044             .setClientSecret("clientSecret")
1045             .setScopes(scopes)
1046             .setUniverseDomain("universeDomain")
1047             .build();
1048 
1049     assertEquals("audience", credentials.getAudience());
1050     assertEquals("subjectTokenType", credentials.getSubjectTokenType());
1051     assertEquals(STS_URL, credentials.getTokenUrl());
1052     assertEquals("tokenInfoUrl", credentials.getTokenInfoUrl());
1053     assertEquals(
1054         SERVICE_ACCOUNT_IMPERSONATION_URL, credentials.getServiceAccountImpersonationUrl());
1055     assertEquals(AWS_CREDENTIAL_SOURCE, credentials.getCredentialSource());
1056     assertEquals("quotaProjectId", credentials.getQuotaProjectId());
1057     assertEquals("clientId", credentials.getClientId());
1058     assertEquals("clientSecret", credentials.getClientSecret());
1059     assertEquals(scopes, credentials.getScopes());
1060     assertEquals(SystemEnvironmentProvider.getInstance(), credentials.getEnvironmentProvider());
1061     assertEquals("universeDomain", credentials.getUniverseDomain());
1062   }
1063 
1064   @Test
builder_missingUniverseDomain_defaults()1065   public void builder_missingUniverseDomain_defaults() throws IOException {
1066     List<String> scopes = Arrays.asList("scope1", "scope2");
1067 
1068     AwsCredentials credentials =
1069         AwsCredentials.newBuilder()
1070             .setRegionalCredentialVerificationUrlOverride("https://test.com")
1071             .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY)
1072             .setAudience("audience")
1073             .setSubjectTokenType("subjectTokenType")
1074             .setTokenUrl(STS_URL)
1075             .setTokenInfoUrl("tokenInfoUrl")
1076             .setCredentialSource(AWS_CREDENTIAL_SOURCE)
1077             .setTokenInfoUrl("tokenInfoUrl")
1078             .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL)
1079             .setQuotaProjectId("quotaProjectId")
1080             .setClientId("clientId")
1081             .setClientSecret("clientSecret")
1082             .setScopes(scopes)
1083             .build();
1084 
1085     assertEquals("https://test.com", credentials.getRegionalCredentialVerificationUrlOverride());
1086     assertEquals("audience", credentials.getAudience());
1087     assertEquals("subjectTokenType", credentials.getSubjectTokenType());
1088     assertEquals(STS_URL, credentials.getTokenUrl());
1089     assertEquals("tokenInfoUrl", credentials.getTokenInfoUrl());
1090     assertEquals(
1091         SERVICE_ACCOUNT_IMPERSONATION_URL, credentials.getServiceAccountImpersonationUrl());
1092     assertEquals(AWS_CREDENTIAL_SOURCE, credentials.getCredentialSource());
1093     assertEquals("quotaProjectId", credentials.getQuotaProjectId());
1094     assertEquals("clientId", credentials.getClientId());
1095     assertEquals("clientSecret", credentials.getClientSecret());
1096     assertEquals(scopes, credentials.getScopes());
1097     assertEquals(SystemEnvironmentProvider.getInstance(), credentials.getEnvironmentProvider());
1098     assertEquals(GOOGLE_DEFAULT_UNIVERSE, credentials.getUniverseDomain());
1099   }
1100 
1101   @Test
newBuilder_allFields()1102   public void newBuilder_allFields() throws IOException {
1103     List<String> scopes = Arrays.asList("scope1", "scope2");
1104 
1105     AwsCredentials credentials =
1106         AwsCredentials.newBuilder()
1107             .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY)
1108             .setAudience("audience")
1109             .setSubjectTokenType("subjectTokenType")
1110             .setTokenUrl(STS_URL)
1111             .setTokenInfoUrl("tokenInfoUrl")
1112             .setCredentialSource(AWS_CREDENTIAL_SOURCE)
1113             .setTokenInfoUrl("tokenInfoUrl")
1114             .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL)
1115             .setQuotaProjectId("quotaProjectId")
1116             .setClientId("clientId")
1117             .setClientSecret("clientSecret")
1118             .setScopes(scopes)
1119             .setUniverseDomain("universeDomain")
1120             .build();
1121 
1122     AwsCredentials newBuilderCreds = AwsCredentials.newBuilder(credentials).build();
1123     assertEquals(credentials.getAudience(), newBuilderCreds.getAudience());
1124     assertEquals(credentials.getSubjectTokenType(), newBuilderCreds.getSubjectTokenType());
1125     assertEquals(credentials.getTokenUrl(), newBuilderCreds.getTokenUrl());
1126     assertEquals(credentials.getTokenInfoUrl(), newBuilderCreds.getTokenInfoUrl());
1127     assertEquals(
1128         credentials.getServiceAccountImpersonationUrl(),
1129         newBuilderCreds.getServiceAccountImpersonationUrl());
1130     assertEquals(credentials.getCredentialSource(), newBuilderCreds.getCredentialSource());
1131     assertEquals(credentials.getQuotaProjectId(), newBuilderCreds.getQuotaProjectId());
1132     assertEquals(credentials.getClientId(), newBuilderCreds.getClientId());
1133     assertEquals(credentials.getClientSecret(), newBuilderCreds.getClientSecret());
1134     assertEquals(credentials.getScopes(), newBuilderCreds.getScopes());
1135     assertEquals(credentials.getEnvironmentProvider(), newBuilderCreds.getEnvironmentProvider());
1136     assertEquals(credentials.getUniverseDomain(), newBuilderCreds.getUniverseDomain());
1137   }
1138 
1139   @Test
newBuilder_noUniverseDomain_defaults()1140   public void newBuilder_noUniverseDomain_defaults() throws IOException {
1141     List<String> scopes = Arrays.asList("scope1", "scope2");
1142 
1143     AwsCredentials credentials =
1144         AwsCredentials.newBuilder()
1145             .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY)
1146             .setAudience("audience")
1147             .setSubjectTokenType("subjectTokenType")
1148             .setTokenUrl(STS_URL)
1149             .setTokenInfoUrl("tokenInfoUrl")
1150             .setCredentialSource(AWS_CREDENTIAL_SOURCE)
1151             .setTokenInfoUrl("tokenInfoUrl")
1152             .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL)
1153             .setQuotaProjectId("quotaProjectId")
1154             .setClientId("clientId")
1155             .setClientSecret("clientSecret")
1156             .setScopes(scopes)
1157             .build();
1158 
1159     AwsCredentials newBuilderCreds = AwsCredentials.newBuilder(credentials).build();
1160     assertEquals(credentials.getAudience(), newBuilderCreds.getAudience());
1161     assertEquals(credentials.getSubjectTokenType(), newBuilderCreds.getSubjectTokenType());
1162     assertEquals(credentials.getTokenUrl(), newBuilderCreds.getTokenUrl());
1163     assertEquals(credentials.getTokenInfoUrl(), newBuilderCreds.getTokenInfoUrl());
1164     assertEquals(
1165         credentials.getServiceAccountImpersonationUrl(),
1166         newBuilderCreds.getServiceAccountImpersonationUrl());
1167     assertEquals(credentials.getCredentialSource(), newBuilderCreds.getCredentialSource());
1168     assertEquals(credentials.getQuotaProjectId(), newBuilderCreds.getQuotaProjectId());
1169     assertEquals(credentials.getClientId(), newBuilderCreds.getClientId());
1170     assertEquals(credentials.getClientSecret(), newBuilderCreds.getClientSecret());
1171     assertEquals(credentials.getScopes(), newBuilderCreds.getScopes());
1172     assertEquals(credentials.getEnvironmentProvider(), newBuilderCreds.getEnvironmentProvider());
1173     assertEquals(GOOGLE_DEFAULT_UNIVERSE, newBuilderCreds.getUniverseDomain());
1174   }
1175 
1176   @Test
builder_defaultRegionalCredentialVerificationUrlOverride()1177   public void builder_defaultRegionalCredentialVerificationUrlOverride() throws IOException {
1178     List<String> scopes = Arrays.asList("scope1", "scope2");
1179 
1180     AwsSecurityCredentialsSupplier supplier =
1181         new TestAwsSecurityCredentialsSupplier("region", null, null, null);
1182 
1183     AwsCredentials credentials =
1184         AwsCredentials.newBuilder()
1185             .setAwsSecurityCredentialsSupplier(supplier)
1186             .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY)
1187             .setAudience("audience")
1188             .setSubjectTokenType("subjectTokenType")
1189             .setTokenUrl(STS_URL)
1190             .setTokenInfoUrl("tokenInfoUrl")
1191             .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL)
1192             .setQuotaProjectId("quotaProjectId")
1193             .setClientId("clientId")
1194             .setClientSecret("clientSecret")
1195             .setScopes(scopes)
1196             .build();
1197 
1198     assertNull(credentials.getRegionalCredentialVerificationUrlOverride());
1199     assertEquals(
1200         DEFAULT_REGIONAL_CREDENTIAL_VERIFICATION_URL,
1201         credentials.getRegionalCredentialVerificationUrl());
1202   }
1203 
1204   @Test
builder_supplierAndCredSourceThrows()1205   public void builder_supplierAndCredSourceThrows() throws IOException {
1206     List<String> scopes = Arrays.asList("scope1", "scope2");
1207 
1208     AwsSecurityCredentialsSupplier supplier =
1209         new TestAwsSecurityCredentialsSupplier("region", null, null, null);
1210 
1211     try {
1212       AwsCredentials credentials =
1213           AwsCredentials.newBuilder()
1214               .setAwsSecurityCredentialsSupplier(supplier)
1215               .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY)
1216               .setAudience("audience")
1217               .setSubjectTokenType("subjectTokenType")
1218               .setTokenUrl(STS_URL)
1219               .setTokenInfoUrl("tokenInfoUrl")
1220               .setCredentialSource(AWS_CREDENTIAL_SOURCE)
1221               .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL)
1222               .setQuotaProjectId("quotaProjectId")
1223               .setClientId("clientId")
1224               .setClientSecret("clientSecret")
1225               .setScopes(scopes)
1226               .build();
1227       fail("Should not be able to continue without exception.");
1228     } catch (IllegalArgumentException exception) {
1229       assertEquals(
1230           "AwsCredentials cannot have both an awsSecurityCredentialsSupplier and a credentialSource.",
1231           exception.getMessage());
1232     }
1233   }
1234 
1235   @Test
builder_noSupplieOrCredSourceThrows()1236   public void builder_noSupplieOrCredSourceThrows() throws IOException {
1237     List<String> scopes = Arrays.asList("scope1", "scope2");
1238 
1239     Supplier<AwsSecurityCredentials> testSupplier = () -> null;
1240 
1241     try {
1242       AwsCredentials credentials =
1243           AwsCredentials.newBuilder()
1244               .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY)
1245               .setAudience("audience")
1246               .setSubjectTokenType("subjectTokenType")
1247               .setTokenUrl(STS_URL)
1248               .setTokenInfoUrl("tokenInfoUrl")
1249               .setTokenInfoUrl("tokenInfoUrl")
1250               .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL)
1251               .setQuotaProjectId("quotaProjectId")
1252               .setClientId("clientId")
1253               .setClientSecret("clientSecret")
1254               .setScopes(scopes)
1255               .build();
1256       fail("Should not be able to continue without exception.");
1257     } catch (IllegalArgumentException exception) {
1258       assertEquals(
1259           "An awsSecurityCredentialsSupplier or a credentialSource must be provided.",
1260           exception.getMessage());
1261     }
1262   }
1263 
1264   @Test
serialize()1265   public void serialize() throws IOException, ClassNotFoundException {
1266     List<String> scopes = Arrays.asList("scope1", "scope2");
1267 
1268     AwsCredentials testCredentials =
1269         AwsCredentials.newBuilder()
1270             .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY)
1271             .setAudience("audience")
1272             .setSubjectTokenType("subjectTokenType")
1273             .setTokenUrl(STS_URL)
1274             .setTokenInfoUrl("tokenInfoUrl")
1275             .setCredentialSource(AWS_CREDENTIAL_SOURCE)
1276             .setTokenInfoUrl("tokenInfoUrl")
1277             .setServiceAccountImpersonationUrl(SERVICE_ACCOUNT_IMPERSONATION_URL)
1278             .setQuotaProjectId("quotaProjectId")
1279             .setClientId("clientId")
1280             .setClientSecret("clientSecret")
1281             .setUniverseDomain("universeDomain")
1282             .setScopes(scopes)
1283             .build();
1284 
1285     AwsCredentials deserializedCredentials = serializeAndDeserialize(testCredentials);
1286     assertEquals(testCredentials, deserializedCredentials);
1287     assertEquals(testCredentials.hashCode(), deserializedCredentials.hashCode());
1288     assertEquals(testCredentials.toString(), deserializedCredentials.toString());
1289     assertSame(deserializedCredentials.clock, Clock.SYSTEM);
1290   }
1291 
ValidateRequest( MockLowLevelHttpRequest request, String expectedUrl, Map<String, String> expectedHeaders)1292   private static void ValidateRequest(
1293       MockLowLevelHttpRequest request, String expectedUrl, Map<String, String> expectedHeaders) {
1294     assertEquals(expectedUrl, request.getUrl());
1295     Map<String, List<String>> actualHeaders = request.getHeaders();
1296 
1297     for (Map.Entry<String, String> expectedHeader : expectedHeaders.entrySet()) {
1298       assertTrue(actualHeaders.containsKey(expectedHeader.getKey()));
1299       List<String> actualValues = actualHeaders.get(expectedHeader.getKey());
1300       assertEquals(1, actualValues.size());
1301       assertEquals(expectedHeader.getValue(), actualValues.get(0));
1302     }
1303   }
1304 
buildAwsCredentialSourceMap( MockExternalAccountCredentialsTransportFactory transportFactory)1305   private static Map<String, Object> buildAwsCredentialSourceMap(
1306       MockExternalAccountCredentialsTransportFactory transportFactory) {
1307     Map<String, Object> credentialSourceMap = new HashMap<>();
1308     credentialSourceMap.put("environment_id", "aws1");
1309     credentialSourceMap.put("region_url", transportFactory.transport.getAwsRegionUrl());
1310     credentialSourceMap.put("url", transportFactory.transport.getAwsCredentialsUrl());
1311     credentialSourceMap.put("regional_cred_verification_url", GET_CALLER_IDENTITY_URL);
1312     return credentialSourceMap;
1313   }
1314 
buildAwsCredentialSource( MockExternalAccountCredentialsTransportFactory transportFactory)1315   private static AwsCredentialSource buildAwsCredentialSource(
1316       MockExternalAccountCredentialsTransportFactory transportFactory) {
1317     return new AwsCredentialSource(buildAwsCredentialSourceMap(transportFactory));
1318   }
1319 
buildAwsImdsv2CredentialSource( MockExternalAccountCredentialsTransportFactory transportFactory)1320   static AwsCredentialSource buildAwsImdsv2CredentialSource(
1321       MockExternalAccountCredentialsTransportFactory transportFactory) {
1322     Map<String, Object> credentialSourceMap = buildAwsCredentialSourceMap(transportFactory);
1323     credentialSourceMap.put(
1324         "imdsv2_session_token_url", transportFactory.transport.getAwsImdsv2SessionTokenUrl());
1325     return new AwsCredentialSource(credentialSourceMap);
1326   }
1327 
buildAwsIpv6CredentialSourceMap()1328   private static Map<String, Object> buildAwsIpv6CredentialSourceMap() {
1329     String regionUrl = "http://[fd00:ec2::254]/region";
1330     String url = "http://[fd00:ec2::254]";
1331     String imdsv2SessionTokenUrl = "http://[fd00:ec2::254]/imdsv2";
1332     Map<String, Object> credentialSourceMap = new HashMap<>();
1333     credentialSourceMap.put("environment_id", "aws1");
1334     credentialSourceMap.put("region_url", regionUrl);
1335     credentialSourceMap.put("url", url);
1336     credentialSourceMap.put("imdsv2_session_token_url", imdsv2SessionTokenUrl);
1337     credentialSourceMap.put("regional_cred_verification_url", GET_CALLER_IDENTITY_URL);
1338 
1339     return credentialSourceMap;
1340   }
1341 
writeAwsCredentialsStream(String stsUrl, String regionUrl, String metadataUrl)1342   static InputStream writeAwsCredentialsStream(String stsUrl, String regionUrl, String metadataUrl)
1343       throws IOException {
1344     GenericJson json = new GenericJson();
1345     json.put("audience", "audience");
1346     json.put("subject_token_type", "subjectTokenType");
1347     json.put("token_url", stsUrl);
1348     json.put("token_info_url", "tokenInfoUrl");
1349     json.put("type", ExternalAccountCredentials.EXTERNAL_ACCOUNT_FILE_TYPE);
1350 
1351     GenericJson credentialSource = new GenericJson();
1352     credentialSource.put("environment_id", "aws1");
1353     credentialSource.put("region_url", regionUrl);
1354     credentialSource.put("url", metadataUrl);
1355     credentialSource.put("regional_cred_verification_url", GET_CALLER_IDENTITY_URL);
1356     json.put("credential_source", credentialSource);
1357 
1358     return TestUtils.jsonToInputStream(json);
1359   }
1360 
1361   class TestAwsSecurityCredentialsSupplier implements AwsSecurityCredentialsSupplier {
1362 
1363     private String region;
1364     private AwsSecurityCredentials credentials;
1365     private IOException credentialException;
1366     private ExternalAccountSupplierContext expectedContext;
1367 
TestAwsSecurityCredentialsSupplier( String region, AwsSecurityCredentials credentials, IOException credentialException, ExternalAccountSupplierContext expectedContext)1368     TestAwsSecurityCredentialsSupplier(
1369         String region,
1370         AwsSecurityCredentials credentials,
1371         IOException credentialException,
1372         ExternalAccountSupplierContext expectedContext) {
1373       this.region = region;
1374       this.credentials = credentials;
1375       this.credentialException = credentialException;
1376       this.expectedContext = expectedContext;
1377     }
1378 
1379     @Override
getRegion(ExternalAccountSupplierContext context)1380     public String getRegion(ExternalAccountSupplierContext context) throws IOException {
1381       if (expectedContext != null) {
1382         assertEquals(expectedContext.getAudience(), context.getAudience());
1383         assertEquals(expectedContext.getSubjectTokenType(), context.getSubjectTokenType());
1384       }
1385       return region;
1386     }
1387 
1388     @Override
getCredentials(ExternalAccountSupplierContext context)1389     public AwsSecurityCredentials getCredentials(ExternalAccountSupplierContext context)
1390         throws IOException {
1391       if (credentialException != null) {
1392         throw credentialException;
1393       }
1394       if (expectedContext != null) {
1395         assertEquals(expectedContext.getAudience(), context.getAudience());
1396         assertEquals(expectedContext.getSubjectTokenType(), context.getSubjectTokenType());
1397       }
1398       return credentials;
1399     }
1400   }
1401 }
1402