From b8906861f3be82a78c187d9e6c53370dd9018ddd Mon Sep 17 00:00:00 2001
From: "release-please[bot]"
<55107282+release-please[bot]@users.noreply.github.com>
Date: Thu, 24 Feb 2022 18:32:56 +0000
Subject: [PATCH 01/14] chore(main): release 1.5.4-SNAPSHOT (#876)
:robot: I have created a release *beep* *boop*
---
### Updating meta-information for bleeding-edge SNAPSHOT release.
---
This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
---
appengine/pom.xml | 2 +-
bom/pom.xml | 2 +-
credentials/pom.xml | 2 +-
oauth2_http/pom.xml | 2 +-
pom.xml | 2 +-
versions.txt | 12 ++++++------
6 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/appengine/pom.xml b/appengine/pom.xml
index 625df3848..8fee1e6b9 100644
--- a/appengine/pom.xml
+++ b/appengine/pom.xml
@@ -5,7 +5,7 @@
com.google.auth
google-auth-library-parent
- 1.5.3
+ 1.5.4-SNAPSHOT
../pom.xml
diff --git a/bom/pom.xml b/bom/pom.xml
index 864a3b9e3..47274b2fb 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -3,7 +3,7 @@
4.0.0
com.google.auth
google-auth-library-bom
- 1.5.3
+ 1.5.4-SNAPSHOT
pom
Google Auth Library for Java BOM
diff --git a/credentials/pom.xml b/credentials/pom.xml
index 2f90eba96..3f1b34131 100644
--- a/credentials/pom.xml
+++ b/credentials/pom.xml
@@ -4,7 +4,7 @@
com.google.auth
google-auth-library-parent
- 1.5.3
+ 1.5.4-SNAPSHOT
../pom.xml
diff --git a/oauth2_http/pom.xml b/oauth2_http/pom.xml
index 93ac5409c..0af60f8aa 100644
--- a/oauth2_http/pom.xml
+++ b/oauth2_http/pom.xml
@@ -5,7 +5,7 @@
com.google.auth
google-auth-library-parent
- 1.5.3
+ 1.5.4-SNAPSHOT
../pom.xml
diff --git a/pom.xml b/pom.xml
index 05d687269..a4703dbac 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,7 +3,7 @@
4.0.0
com.google.auth
google-auth-library-parent
- 1.5.3
+ 1.5.4-SNAPSHOT
pom
Google Auth Library for Java
Client libraries providing authentication and
diff --git a/versions.txt b/versions.txt
index 1d25f3dc2..45894d9cc 100644
--- a/versions.txt
+++ b/versions.txt
@@ -1,9 +1,9 @@
# Format:
# module:released-version:current-version
-google-auth-library:1.5.3:1.5.3
-google-auth-library-bom:1.5.3:1.5.3
-google-auth-library-parent:1.5.3:1.5.3
-google-auth-library-appengine:1.5.3:1.5.3
-google-auth-library-credentials:1.5.3:1.5.3
-google-auth-library-oauth2-http:1.5.3:1.5.3
+google-auth-library:1.5.3:1.5.4-SNAPSHOT
+google-auth-library-bom:1.5.3:1.5.4-SNAPSHOT
+google-auth-library-parent:1.5.3:1.5.4-SNAPSHOT
+google-auth-library-appengine:1.5.3:1.5.4-SNAPSHOT
+google-auth-library-credentials:1.5.3:1.5.4-SNAPSHOT
+google-auth-library-oauth2-http:1.5.3:1.5.4-SNAPSHOT
From 577e9a52204b0d6026a302bb7efe2c6162d57945 Mon Sep 17 00:00:00 2001
From: sai-sunder-s <4540365+sai-sunder-s@users.noreply.github.com>
Date: Thu, 24 Feb 2022 22:48:31 +0000
Subject: [PATCH 02/14] feat: Add AWS Session Token to Metadata Requests (#850)
* feat: Add AWS Session Token to Metadata Requests
* Adding testing functionality for requests made. Refactoring header passing logic
---
README.md | 5 +
.../google/auth/oauth2/AwsCredentials.java | 92 ++++++++-
.../auth/oauth2/AwsCredentialsTest.java | 185 +++++++++++++++++-
.../ExternalAccountCredentialsTest.java | 8 +-
.../oauth2/IdentityPoolCredentialsTest.java | 2 +-
...ckExternalAccountCredentialsTransport.java | 31 ++-
6 files changed, 297 insertions(+), 26 deletions(-)
diff --git a/README.md b/README.md
index 90f92a06d..eb5e95486 100644
--- a/README.md
+++ b/README.md
@@ -211,6 +211,11 @@ Where the following variables need to be substituted:
This generates the configuration file in the specified output file.
+If you want to use the AWS IMDSv2 flow, you can add the field below to the credential_source in your AWS ADC configuration file:
+"imdsv2_session_token_url": "http://169.254.169.254/latest/api/token"
+
+The gcloud create-cred-config command will be updated to support this soon.
+
You can now [use the Auth library](#using-external-identities) to call Google Cloud
resources from AWS.
diff --git a/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java b/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java
index 54c7a13e9..168af3054 100644
--- a/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java
+++ b/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java
@@ -32,6 +32,9 @@
package com.google.auth.oauth2;
import com.google.api.client.http.GenericUrl;
+import com.google.api.client.http.HttpContent;
+import com.google.api.client.http.HttpHeaders;
+import com.google.api.client.http.HttpMethods;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponse;
@@ -48,6 +51,7 @@
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import javax.annotation.Nullable;
/**
* AWS credentials representing a third-party identity for calling Google APIs.
@@ -56,15 +60,22 @@
*/
public class AwsCredentials extends ExternalAccountCredentials {
+ static final String AWS_IMDSV2_SESSION_TOKEN_HEADER = "x-aws-ec2-metadata-token";
+ static final String AWS_IMDSV2_SESSION_TOKEN_TTL_HEADER = "x-aws-ec2-metadata-token-ttl-seconds";
+ static final String AWS_IMDSV2_SESSION_TOKEN_TTL = "300";
+
/**
* The AWS credential source. Stores data required to retrieve the AWS credential from the AWS
* metadata server.
*/
static class AwsCredentialSource extends CredentialSource {
+ private static final String IMDSV2_SESSION_TOKEN_URL_FIELD_NAME = "imdsv2_session_token_url";
+
private final String regionUrl;
private final String url;
private final String regionalCredentialVerificationUrl;
+ private final String imdsv2SessionTokenUrl;
/**
* The source of the AWS credential. The credential source map must contain the
@@ -107,6 +118,13 @@ static class AwsCredentialSource extends CredentialSource {
this.url = (String) credentialSourceMap.get("url");
this.regionalCredentialVerificationUrl =
(String) credentialSourceMap.get("regional_cred_verification_url");
+
+ if (credentialSourceMap.containsKey(IMDSV2_SESSION_TOKEN_URL_FIELD_NAME)) {
+ this.imdsv2SessionTokenUrl =
+ (String) credentialSourceMap.get(IMDSV2_SESSION_TOKEN_URL_FIELD_NAME);
+ } else {
+ this.imdsv2SessionTokenUrl = null;
+ }
}
}
@@ -135,11 +153,13 @@ public AccessToken refreshAccessToken() throws IOException {
@Override
public String retrieveSubjectToken() throws IOException {
+ Map metadataRequestHeaders = createMetadataRequestHeaders(awsCredentialSource);
+
// The targeted region is required to generate the signed request. The regional
// endpoint must also be used.
- String region = getAwsRegion();
+ String region = getAwsRegion(metadataRequestHeaders);
- AwsSecurityCredentials credentials = getAwsSecurityCredentials();
+ AwsSecurityCredentials credentials = getAwsSecurityCredentials(metadataRequestHeaders);
// Generate the signed request to the AWS STS GetCallerIdentity API.
Map headers = new HashMap<>();
@@ -164,10 +184,28 @@ public GoogleCredentials createScoped(Collection newScopes) {
return new AwsCredentials((AwsCredentials.Builder) newBuilder(this).setScopes(newScopes));
}
- private String retrieveResource(String url, String resourceName) throws IOException {
+ private String retrieveResource(String url, String resourceName, Map headers)
+ throws IOException {
+ return retrieveResource(url, resourceName, HttpMethods.GET, headers, /* content= */ null);
+ }
+
+ private String retrieveResource(
+ String url,
+ String resourceName,
+ String requestMethod,
+ Map headers,
+ @Nullable HttpContent content)
+ throws IOException {
try {
HttpRequestFactory requestFactory = transportFactory.create().createRequestFactory();
- HttpRequest request = requestFactory.buildGetRequest(new GenericUrl(url));
+ HttpRequest request =
+ requestFactory.buildRequest(requestMethod, new GenericUrl(url), content);
+
+ HttpHeaders requestHeaders = request.getHeaders();
+ for (Map.Entry header : headers.entrySet()) {
+ requestHeaders.set(header.getKey(), header.getValue());
+ }
+
HttpResponse response = request.execute();
return response.parseAsString();
} catch (IOException e) {
@@ -200,8 +238,42 @@ private String buildSubjectToken(AwsRequestSignature signature)
return URLEncoder.encode(token.toString(), "UTF-8");
}
+ Map createMetadataRequestHeaders(AwsCredentialSource awsCredentialSource)
+ throws IOException {
+ Map metadataRequestHeaders = new HashMap<>();
+
+ // AWS IDMSv2 introduced a requirement for a session token to be present
+ // with the requests made to metadata endpoints. This requirement is to help
+ // prevent SSRF attacks.
+ // Presence of "imdsv2_session_token_url" in Credential Source of config file
+ // will trigger a flow with session token, else there will not be a session
+ // token with the metadata requests.
+ // Both flows work for IDMS v1 and v2. But if IDMSv2 is enabled, then if
+ // session token is not present, Unauthorized exception will be thrown.
+ if (awsCredentialSource.imdsv2SessionTokenUrl != null) {
+ Map tokenRequestHeaders =
+ new HashMap() {
+ {
+ put(AWS_IMDSV2_SESSION_TOKEN_TTL_HEADER, AWS_IMDSV2_SESSION_TOKEN_TTL);
+ }
+ };
+
+ String imdsv2SessionToken =
+ retrieveResource(
+ awsCredentialSource.imdsv2SessionTokenUrl,
+ "Session Token",
+ HttpMethods.PUT,
+ tokenRequestHeaders,
+ /* content= */ null);
+
+ metadataRequestHeaders.put(AWS_IMDSV2_SESSION_TOKEN_HEADER, imdsv2SessionToken);
+ }
+
+ return metadataRequestHeaders;
+ }
+
@VisibleForTesting
- String getAwsRegion() throws IOException {
+ String getAwsRegion(Map metadataRequestHeaders) throws IOException {
// For AWS Lambda, the region is retrieved through the AWS_REGION environment variable.
String region = getEnvironmentProvider().getEnv("AWS_REGION");
if (region != null) {
@@ -218,7 +290,7 @@ String getAwsRegion() throws IOException {
"Unable to determine the AWS region. The credential source does not contain the region URL.");
}
- region = retrieveResource(awsCredentialSource.regionUrl, "region");
+ region = retrieveResource(awsCredentialSource.regionUrl, "region", metadataRequestHeaders);
// There is an extra appended character that must be removed. If `us-east-1b` is returned,
// we want `us-east-1`.
@@ -226,7 +298,8 @@ String getAwsRegion() throws IOException {
}
@VisibleForTesting
- AwsSecurityCredentials getAwsSecurityCredentials() throws IOException {
+ AwsSecurityCredentials getAwsSecurityCredentials(Map metadataRequestHeaders)
+ throws IOException {
// Check environment variables for credentials first.
String accessKeyId = getEnvironmentProvider().getEnv("AWS_ACCESS_KEY_ID");
String secretAccessKey = getEnvironmentProvider().getEnv("AWS_SECRET_ACCESS_KEY");
@@ -243,12 +316,13 @@ AwsSecurityCredentials getAwsSecurityCredentials() throws IOException {
"Unable to determine the AWS IAM role name. The credential source does not contain the"
+ " url field.");
}
- String roleName = retrieveResource(awsCredentialSource.url, "IAM role");
+ String roleName = retrieveResource(awsCredentialSource.url, "IAM role", metadataRequestHeaders);
// Retrieve the AWS security credentials by calling the endpoint specified by the credential
// source.
String awsCredentials =
- retrieveResource(awsCredentialSource.url + "/" + roleName, "credentials");
+ retrieveResource(
+ awsCredentialSource.url + "/" + roleName, "credentials", metadataRequestHeaders);
JsonParser parser = OAuth2Utils.JSON_FACTORY.createJsonParser(awsCredentials);
GenericJson genericJson = parser.parseAndClose(GenericJson.class);
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java
index c36bdf3bc..e52365c80 100644
--- a/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java
+++ b/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java
@@ -39,6 +39,7 @@
import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonParser;
+import com.google.api.client.testing.http.MockLowLevelHttpRequest;
import com.google.auth.TestUtils;
import com.google.auth.oauth2.AwsCredentials.AwsCredentialSource;
import com.google.auth.oauth2.ExternalAccountCredentialsTest.MockExternalAccountCredentialsTransportFactory;
@@ -47,6 +48,7 @@
import java.net.URI;
import java.net.URLDecoder;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -56,6 +58,12 @@
class AwsCredentialsTest {
private static final String STS_URL = "https://sts.googleapis.com";
+ private static final String AWS_CREDENTIALS_URL = "https://www.aws-credentials.com";
+ private static final String AWS_CREDENTIALS_URL_WITH_ROLE =
+ "https://www.aws-credentials.com/roleName";
+ private static final String AWS_REGION_URL = "https://www.aws-region.com";
+ private static final String AWS_IMDSV2_SESSION_TOKEN_URL = "https://www.aws-session-token.com";
+ private static final String AWS_IMDSV2_SESSION_TOKEN = "sessiontoken";
private static final String GET_CALLER_IDENTITY_URL =
"https://sts.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15";
@@ -73,6 +81,9 @@ class AwsCredentialsTest {
}
};
+ private static final Map EMPTY_METADATA_HEADERS = Collections.emptyMap();
+ private static final Map EMPTY_STRING_HEADERS = Collections.emptyMap();
+
private static final AwsCredentialSource AWS_CREDENTIAL_SOURCE =
new AwsCredentialSource(AWS_CREDENTIAL_SOURCE_MAP);
@@ -158,6 +169,89 @@ void retrieveSubjectToken() throws IOException {
assertEquals(awsCredential.getAudience(), headers.get("x-goog-cloud-target-resource"));
assertTrue(headers.containsKey("x-amz-date"));
assertNotNull(headers.get("Authorization"));
+
+ List requests = transportFactory.transport.getRequests();
+ assertEquals(3, requests.size());
+
+ // Validate region request.
+ ValidateRequest(requests.get(0), AWS_REGION_URL, EMPTY_STRING_HEADERS);
+
+ // Validate role request.
+ ValidateRequest(requests.get(1), AWS_CREDENTIALS_URL, EMPTY_STRING_HEADERS);
+
+ // Validate security credentials request.
+ ValidateRequest(requests.get(2), AWS_CREDENTIALS_URL_WITH_ROLE, EMPTY_STRING_HEADERS);
+ }
+
+ @Test
+ void retrieveSubjectTokenWithSessionTokenUrl() throws IOException {
+ MockExternalAccountCredentialsTransportFactory transportFactory =
+ new MockExternalAccountCredentialsTransportFactory();
+
+ Map credentialSourceMap = new HashMap<>();
+ credentialSourceMap.put("environment_id", "aws1");
+ credentialSourceMap.put("region_url", transportFactory.transport.getAwsRegionUrl());
+ credentialSourceMap.put("url", transportFactory.transport.getAwsCredentialsUrl());
+ credentialSourceMap.put("regional_cred_verification_url", GET_CALLER_IDENTITY_URL);
+ credentialSourceMap.put(
+ "imdsv2_session_token_url", transportFactory.transport.getAwsImdsv2SessionTokenUrl());
+
+ AwsCredentials awsCredential =
+ (AwsCredentials)
+ AwsCredentials.newBuilder(AWS_CREDENTIAL)
+ .setHttpTransportFactory(transportFactory)
+ .setCredentialSource(new AwsCredentialSource(credentialSourceMap))
+ .build();
+
+ String subjectToken = URLDecoder.decode(awsCredential.retrieveSubjectToken(), "UTF-8");
+
+ JsonParser parser = OAuth2Utils.JSON_FACTORY.createJsonParser(subjectToken);
+ GenericJson json = parser.parseAndClose(GenericJson.class);
+
+ List