diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index b25d5f6db..08eaf507a 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -18,32 +18,37 @@ samples/**/*.java @googleapis/java-samples-reviewers
samples/snippets/generated/ @googleapis/yoshi-java
# 3PI-related files and related base classes - joint ownership between googleapis-auth and aion-sdk
-oauth2_http/java/com/google/auth/oauth2/ActingParty.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/AwsDates.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/AwsRequestSignature.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/AwsRequestSigner.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/AwsSecurityCredentials.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/CredentialAccessBoundary.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/DownscopedCredentials.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/EnvironmentProvider.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/ExecutableHandler.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/ExecutableResponse.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/OAuth2Credentials.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/OAuth2CredentialsWithRefresh.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/OAuthException.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/PluggableAuthCredentials.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/PluggableAuthException.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/PluggableAuthHandler.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/StsRequestHandler.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/StsTokenExchangeRequest.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/StsTokenExchangeResponse.java @googleapis/googleapis-auth @googleapis/aion-sdk
-oauth2_http/java/com/google/auth/oauth2/SystemEnvironmentProvider.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/ActingParty.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/AwsSecurityCredentialsSupplier.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/AwsDates.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/AwsRequestSignature.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/AwsRequestSigner.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/AwsSecurityCredentials.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/CredentialAccessBoundary.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/DownscopedCredentials.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/EnvironmentProvider.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/ExecutableHandler.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/ExecutableResponse.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/FileIdentityPoolSubjectTokenSupplier.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/IdentityPoolSubjectTokenSupplier.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/InternalAwsSecurityCredentialsSupplier.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/OAuth2Credentials.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/OAuth2CredentialsWithRefresh.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/OAuthException.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/PluggableAuthCredentials.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/PluggableAuthException.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/PluggableAuthHandler.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/StsRequestHandler.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/StsTokenExchangeRequest.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/StsTokenExchangeResponse.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/SystemEnvironmentProvider.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/java/com/google/auth/oauth2/UrlIdentityPoolSubjectTokenSupplier.java @googleapis/googleapis-auth @googleapis/aion-sdk
oauth2_http/javatests/com/google/auth/AwsCredentialsTest.java @googleapis/googleapis-auth @googleapis/aion-sdk
oauth2_http/javatests/com/google/auth/AwsRequestSignerTest.java @googleapis/googleapis-auth @googleapis/aion-sdk
oauth2_http/javatests/com/google/auth/CredentialAccessBoundaryTest.java @googleapis/googleapis-auth @googleapis/aion-sdk
@@ -55,6 +60,7 @@ oauth2_http/javatests/com/google/auth/ITDownscopingTest.java
oauth2_http/javatests/com/google/auth/ITWorkloadIdentityFederationTest.java @googleapis/googleapis-auth @googleapis/aion-sdk
oauth2_http/javatests/com/google/auth/IdentityPoolCredentialsTest.java @googleapis/googleapis-auth @googleapis/aion-sdk
oauth2_http/javatests/com/google/auth/ImpersonatedCredentialsTest.java @googleapis/googleapis-auth @googleapis/aion-sdk
+oauth2_http/javatests/com/google/auth/InternalAwsSecurityCredentialsSupplierTest.java @googleapis/googleapis-auth @googleapis/aion-sdk
oauth2_http/javatests/com/google/auth/MockExternalAccountCredentialsTransport.java @googleapis/googleapis-auth @googleapis/aion-sdk
oauth2_http/javatests/com/google/auth/MockStsTransport.java @googleapis/googleapis-auth @googleapis/aion-sdk
oauth2_http/javatests/com/google/auth/OAuth2CredentialsTest.java @googleapis/googleapis-auth @googleapis/aion-sdk
@@ -65,4 +71,4 @@ oauth2_http/javatests/com/google/auth/PluggableAuthExceptionTest.java
oauth2_http/javatests/com/google/auth/PluggableAuthHandlerTest.java @googleapis/googleapis-auth @googleapis/aion-sdk
oauth2_http/javatests/com/google/auth/StsRequestHandlerTest.java @googleapis/googleapis-auth @googleapis/aion-sdk
oauth2_http/javatests/com/google/auth/TestEnvironmentProvider.java @googleapis/googleapis-auth @googleapis/aion-sdk
-README.md @googleapis/googleapis-auth @googleapis/aion-sdk
\ No newline at end of file
+README.md @googleapis/googleapis-auth @googleapis/aion-sdk
diff --git a/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java b/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java
index e47bbe167..215c77a64 100644
--- a/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java
+++ b/oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java
@@ -31,17 +31,10 @@
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;
import com.google.api.client.json.GenericJson;
-import com.google.api.client.json.JsonParser;
+import com.google.auth.http.HttpTransportFactory;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableList;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
@@ -53,30 +46,66 @@
import javax.annotation.Nullable;
/**
- * AWS credentials representing a third-party identity for calling Google APIs.
+ * Credentials representing an AWS third-party identity for calling Google APIs. AWS security
+ * credentials are either sourced by calling EC2 metadata endpoints, environment variables, or a
+ * user provided supplier method.
*
*
By default, attempts to exchange the external credential for a GCP access token.
*/
public class AwsCredentials extends ExternalAccountCredentials {
- // Supported environment variables.
- static final String AWS_REGION = "AWS_REGION";
- static final String AWS_DEFAULT_REGION = "AWS_DEFAULT_REGION";
- static final String AWS_ACCESS_KEY_ID = "AWS_ACCESS_KEY_ID";
- static final String AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY";
- static final String AWS_SESSION_TOKEN = "AWS_SESSION_TOKEN";
+ static final String DEFAULT_REGIONAL_CREDENTIAL_VERIFICATION_URL =
+ "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15";
+
+ static final String AWS_METRICS_HEADER_VALUE = "aws";
- 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";
private static final long serialVersionUID = -3670131891574618105L;
- private final AwsCredentialSource awsCredentialSource;
+ @Nullable private final AwsSecurityCredentialsSupplier awsSecurityCredentialsSupplier;
+ // Regional credential verification url override. This needs to be its own value so we can
+ // correctly pass it to a builder.
+ @Nullable private final String regionalCredentialVerificationUrlOverride;
+ @Nullable private final String regionalCredentialVerificationUrl;
+ private final String metricsHeaderValue;
/** Internal constructor. See {@link AwsCredentials.Builder}. */
AwsCredentials(Builder builder) {
super(builder);
- this.awsCredentialSource = (AwsCredentialSource) builder.credentialSource;
+ // Check that one and only one of supplier or credential source are provided.
+ if (builder.awsSecurityCredentialsSupplier != null && builder.credentialSource != null) {
+ throw new IllegalArgumentException(
+ "AwsCredentials cannot have both an awsSecurityCredentialsSupplier and a credentialSource.");
+ }
+ if (builder.awsSecurityCredentialsSupplier == null && builder.credentialSource == null) {
+ throw new IllegalArgumentException(
+ "An awsSecurityCredentialsSupplier or a credentialSource must be provided.");
+ }
+
+ AwsCredentialSource credentialSource = (AwsCredentialSource) builder.credentialSource;
+ // Set regional credential verification url override if provided.
+ this.regionalCredentialVerificationUrlOverride =
+ builder.regionalCredentialVerificationUrlOverride;
+
+ // Set regional credential verification url depending on inputs.
+ if (this.regionalCredentialVerificationUrlOverride != null) {
+ this.regionalCredentialVerificationUrl = this.regionalCredentialVerificationUrlOverride;
+ } else if (credentialSource != null) {
+ this.regionalCredentialVerificationUrl = credentialSource.regionalCredentialVerificationUrl;
+ } else {
+ this.regionalCredentialVerificationUrl = DEFAULT_REGIONAL_CREDENTIAL_VERIFICATION_URL;
+ }
+
+ // If user has provided a security credential supplier, use that to retrieve the AWS security
+ // credentials.
+ if (builder.awsSecurityCredentialsSupplier != null) {
+ this.awsSecurityCredentialsSupplier = builder.awsSecurityCredentialsSupplier;
+ this.metricsHeaderValue = PROGRAMMATIC_METRICS_HEADER_VALUE;
+ } else {
+ this.awsSecurityCredentialsSupplier =
+ new InternalAwsSecurityCredentialsSupplier(
+ credentialSource, this.getEnvironmentProvider(), this.transportFactory);
+ this.metricsHeaderValue = AWS_METRICS_HEADER_VALUE;
+ }
}
@Override
@@ -96,16 +125,12 @@ public AccessToken refreshAccessToken() throws IOException {
@Override
public String retrieveSubjectToken() throws IOException {
- Map metadataRequestHeaders = new HashMap<>();
- if (shouldUseMetadataServer()) {
- metadataRequestHeaders = createMetadataRequestHeaders(awsCredentialSource);
- }
// The targeted region is required to generate the signed request. The regional
// endpoint must also be used.
- String region = getAwsRegion(metadataRequestHeaders);
+ String region = awsSecurityCredentialsSupplier.getRegion();
- AwsSecurityCredentials credentials = getAwsSecurityCredentials(metadataRequestHeaders);
+ AwsSecurityCredentials credentials = awsSecurityCredentialsSupplier.getCredentials();
// Generate the signed request to the AWS STS GetCallerIdentity API.
Map headers = new HashMap<>();
@@ -115,7 +140,7 @@ public String retrieveSubjectToken() throws IOException {
AwsRequestSigner.newBuilder(
credentials,
"POST",
- awsCredentialSource.regionalCredentialVerificationUrl.replace("{region}", region),
+ this.regionalCredentialVerificationUrl.replace("{region}", region),
region)
.setAdditionalHeaders(headers)
.build();
@@ -132,36 +157,7 @@ public GoogleCredentials createScoped(Collection newScopes) {
@Override
String getCredentialSourceType() {
- return "aws";
- }
-
- 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.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) {
- throw new IOException(String.format("Failed to retrieve AWS %s.", resourceName), e);
- }
+ return this.metricsHeaderValue;
}
private String buildSubjectToken(AwsRequestSignature signature)
@@ -183,145 +179,28 @@ private String buildSubjectToken(AwsRequestSignature signature)
token.put("headers", headerList);
token.put("method", signature.getHttpMethod());
token.put(
- "url",
- awsCredentialSource.regionalCredentialVerificationUrl.replace(
- "{region}", signature.getRegion()));
+ "url", this.regionalCredentialVerificationUrl.replace("{region}", signature.getRegion()));
return URLEncoder.encode(token.toString(), "UTF-8");
}
- private boolean canRetrieveRegionFromEnvironment() {
- // The AWS region can be provided through AWS_REGION or AWS_DEFAULT_REGION. Only one is
- // required.
- List keys = ImmutableList.of(AWS_REGION, AWS_DEFAULT_REGION);
- for (String env : keys) {
- String value = getEnvironmentProvider().getEnv(env);
- if (value != null && value.trim().length() > 0) {
- // Region available.
- return true;
- }
- }
- return false;
- }
-
- private boolean canRetrieveSecurityCredentialsFromEnvironment() {
- // Check if both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are available.
- List keys = ImmutableList.of(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY);
- for (String env : keys) {
- String value = getEnvironmentProvider().getEnv(env);
- if (value == null || value.trim().length() == 0) {
- // Return false if one of them are missing.
- return false;
- }
- }
- return true;
- }
-
@VisibleForTesting
- boolean shouldUseMetadataServer() {
- return !canRetrieveRegionFromEnvironment() || !canRetrieveSecurityCredentialsFromEnvironment();
+ String getRegionalCredentialVerificationUrl() {
+ return this.regionalCredentialVerificationUrl;
}
@VisibleForTesting
- 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;
+ String getEnv(String name) {
+ return System.getenv(name);
}
@VisibleForTesting
- String getAwsRegion(Map metadataRequestHeaders) throws IOException {
- String region;
- if (canRetrieveRegionFromEnvironment()) {
- // For AWS Lambda, the region is retrieved through the AWS_REGION environment variable.
- region = getEnvironmentProvider().getEnv(AWS_REGION);
- if (region != null && region.trim().length() > 0) {
- return region;
- }
- return getEnvironmentProvider().getEnv(AWS_DEFAULT_REGION);
- }
-
- if (awsCredentialSource.regionUrl == null || awsCredentialSource.regionUrl.isEmpty()) {
- throw new IOException(
- "Unable to determine the AWS region. The credential source does not contain the region URL.");
- }
-
- 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`.
- return region.substring(0, region.length() - 1);
+ AwsSecurityCredentialsSupplier getAwsSecurityCredentialsSupplier() {
+ return this.awsSecurityCredentialsSupplier;
}
- @VisibleForTesting
- AwsSecurityCredentials getAwsSecurityCredentials(Map metadataRequestHeaders)
- throws IOException {
- // Check environment variables for credentials first.
- if (canRetrieveSecurityCredentialsFromEnvironment()) {
- String accessKeyId = getEnvironmentProvider().getEnv(AWS_ACCESS_KEY_ID);
- String secretAccessKey = getEnvironmentProvider().getEnv(AWS_SECRET_ACCESS_KEY);
- String token = getEnvironmentProvider().getEnv(AWS_SESSION_TOKEN);
- return new AwsSecurityCredentials(accessKeyId, secretAccessKey, token);
- }
-
- // Credentials not retrievable from environment variables - call metadata server.
- // Retrieve the IAM role that is attached to the VM. This is required to retrieve the AWS
- // security credentials.
- if (awsCredentialSource.url == null || awsCredentialSource.url.isEmpty()) {
- throw new 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", metadataRequestHeaders);
-
- // Retrieve the AWS security credentials by calling the endpoint specified by the credential
- // source.
- String awsCredentials =
- retrieveResource(
- awsCredentialSource.url + "/" + roleName, "credentials", metadataRequestHeaders);
-
- JsonParser parser = OAuth2Utils.JSON_FACTORY.createJsonParser(awsCredentials);
- GenericJson genericJson = parser.parseAndClose(GenericJson.class);
-
- String accessKeyId = (String) genericJson.get("AccessKeyId");
- String secretAccessKey = (String) genericJson.get("SecretAccessKey");
- String token = (String) genericJson.get("Token");
-
- // These credentials last for a few hours - we may consider caching these in the
- // future.
- return new AwsSecurityCredentials(accessKeyId, secretAccessKey, token);
- }
-
- @VisibleForTesting
- String getEnv(String name) {
- return System.getenv(name);
+ @Nullable
+ public String getRegionalCredentialVerificationUrlOverride() {
+ return this.regionalCredentialVerificationUrlOverride;
}
private static GenericJson formatTokenHeaderForSts(String key, String value) {
@@ -348,10 +227,145 @@ public static AwsCredentials.Builder newBuilder(AwsCredentials awsCredentials) {
public static class Builder extends ExternalAccountCredentials.Builder {
+ private AwsSecurityCredentialsSupplier awsSecurityCredentialsSupplier;
+
+ private String regionalCredentialVerificationUrlOverride;
+
Builder() {}
Builder(AwsCredentials credentials) {
super(credentials);
+ if (this.credentialSource == null) {
+ this.awsSecurityCredentialsSupplier = credentials.awsSecurityCredentialsSupplier;
+ }
+ this.regionalCredentialVerificationUrlOverride =
+ credentials.regionalCredentialVerificationUrlOverride;
+ }
+
+ /**
+ * Sets the AWS security credentials supplier. The supplier should return a valid {@code
+ * AwsSecurityCredentials} object and a valid AWS region.
+ *
+ * @param awsSecurityCredentialsSupplier the supplier to use.
+ * @return this {@code Builder} object
+ */
+ @CanIgnoreReturnValue
+ public Builder setAwsSecurityCredentialsSupplier(
+ AwsSecurityCredentialsSupplier awsSecurityCredentialsSupplier) {
+ this.awsSecurityCredentialsSupplier = awsSecurityCredentialsSupplier;
+ return this;
+ }
+
+ /**
+ * Sets the AWS regional credential verification URL. If set, will override any credential
+ * verification URL provided in the credential source. If not set, the credential verification
+ * URL will default to
+ * https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
+ *
+ * @param regionalCredentialVerificationUrlOverride the AWS credential verification url to set.
+ * @return this {@code Builder} object
+ */
+ @CanIgnoreReturnValue
+ public Builder setRegionalCredentialVerificationUrlOverride(
+ String regionalCredentialVerificationUrlOverride) {
+ this.regionalCredentialVerificationUrlOverride = regionalCredentialVerificationUrlOverride;
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setHttpTransportFactory(HttpTransportFactory transportFactory) {
+ super.setHttpTransportFactory(transportFactory);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setAudience(String audience) {
+ super.setAudience(audience);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setSubjectTokenType(String subjectTokenType) {
+ super.setSubjectTokenType(subjectTokenType);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setSubjectTokenType(SubjectTokenTypes subjectTokenType) {
+ super.setSubjectTokenType(subjectTokenType);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setTokenUrl(String tokenUrl) {
+ super.setTokenUrl(tokenUrl);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setCredentialSource(AwsCredentialSource credentialSource) {
+ super.setCredentialSource(credentialSource);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setServiceAccountImpersonationUrl(String serviceAccountImpersonationUrl) {
+ super.setServiceAccountImpersonationUrl(serviceAccountImpersonationUrl);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setTokenInfoUrl(String tokenInfoUrl) {
+ super.setTokenInfoUrl(tokenInfoUrl);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setQuotaProjectId(String quotaProjectId) {
+ super.setQuotaProjectId(quotaProjectId);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setClientId(String clientId) {
+ super.setClientId(clientId);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setClientSecret(String clientSecret) {
+ super.setClientSecret(clientSecret);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setScopes(Collection scopes) {
+ super.setScopes(scopes);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setWorkforcePoolUserProject(String workforcePoolUserProject) {
+ super.setWorkforcePoolUserProject(workforcePoolUserProject);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setServiceAccountImpersonationOptions(Map optionsMap) {
+ super.setServiceAccountImpersonationOptions(optionsMap);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setUniverseDomain(String universeDomain) {
+ super.setUniverseDomain(universeDomain);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ Builder setEnvironmentProvider(EnvironmentProvider environmentProvider) {
+ super.setEnvironmentProvider(environmentProvider);
+ return this;
}
@Override
diff --git a/oauth2_http/java/com/google/auth/oauth2/AwsRequestSigner.java b/oauth2_http/java/com/google/auth/oauth2/AwsRequestSigner.java
index a890c8814..275c15105 100644
--- a/oauth2_http/java/com/google/auth/oauth2/AwsRequestSigner.java
+++ b/oauth2_http/java/com/google/auth/oauth2/AwsRequestSigner.java
@@ -237,8 +237,9 @@ private Map getCanonicalHeaders(String defaultDate) {
headers.put("x-amz-date", defaultDate);
}
- if (awsSecurityCredentials.getToken() != null && !awsSecurityCredentials.getToken().isEmpty()) {
- headers.put("x-amz-security-token", awsSecurityCredentials.getToken());
+ if (awsSecurityCredentials.getSessionToken() != null
+ && !awsSecurityCredentials.getSessionToken().isEmpty()) {
+ headers.put("x-amz-security-token", awsSecurityCredentials.getSessionToken());
}
// Add all additional headers.
diff --git a/oauth2_http/java/com/google/auth/oauth2/AwsSecurityCredentials.java b/oauth2_http/java/com/google/auth/oauth2/AwsSecurityCredentials.java
index b7865049a..7101dda3e 100644
--- a/oauth2_http/java/com/google/auth/oauth2/AwsSecurityCredentials.java
+++ b/oauth2_http/java/com/google/auth/oauth2/AwsSecurityCredentials.java
@@ -37,29 +37,52 @@
* Defines AWS security credentials. These are either retrieved from the AWS security_credentials
* endpoint or AWS environment variables.
*/
-class AwsSecurityCredentials {
+public class AwsSecurityCredentials {
private final String accessKeyId;
private final String secretAccessKey;
- @Nullable private final String token;
+ @Nullable private final String sessionToken;
- AwsSecurityCredentials(String accessKeyId, String secretAccessKey, @Nullable String token) {
+ /**
+ * Constructor for AWSSecurityCredentials.
+ *
+ * @param accessKeyId the AWS access Key Id.
+ * @param secretAccessKey the AWS secret access key.
+ * @param sessionToken the AWS session token. Optional.
+ */
+ public AwsSecurityCredentials(
+ String accessKeyId, String secretAccessKey, @Nullable String sessionToken) {
this.accessKeyId = accessKeyId;
this.secretAccessKey = secretAccessKey;
- this.token = token;
+ this.sessionToken = sessionToken;
}
- String getAccessKeyId() {
+ /**
+ * Gets the AWS access key id.
+ *
+ * @return the AWS access key id.
+ */
+ public String getAccessKeyId() {
return accessKeyId;
}
- String getSecretAccessKey() {
+ /**
+ * Gets the AWS secret access key.
+ *
+ * @return the AWS secret access key.
+ */
+ public String getSecretAccessKey() {
return secretAccessKey;
}
+ /**
+ * Gets the AWS session token.
+ *
+ * @return the AWS session token.
+ */
@Nullable
- String getToken() {
- return token;
+ public String getSessionToken() {
+ return sessionToken;
}
}
diff --git a/oauth2_http/java/com/google/auth/oauth2/AwsSecurityCredentialsSupplier.java b/oauth2_http/java/com/google/auth/oauth2/AwsSecurityCredentialsSupplier.java
new file mode 100644
index 000000000..b28dd858d
--- /dev/null
+++ b/oauth2_http/java/com/google/auth/oauth2/AwsSecurityCredentialsSupplier.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.auth.oauth2;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+/**
+ * Supplier for retrieving AWS Security credentials for {@Link AwsCredentials} to exchange for GCP
+ * access tokens.
+ */
+public interface AwsSecurityCredentialsSupplier extends Serializable {
+
+ /**
+ * Gets the AWS region to use.
+ *
+ * @return the AWS region that should be used for the credential.
+ * @throws IOException
+ */
+ String getRegion() throws IOException;
+
+ /**
+ * Gets AWS security credentials.
+ *
+ * @return valid AWS security credentials that can be exchanged for a GCP access token.
+ * @throws IOException
+ */
+ AwsSecurityCredentials getCredentials() throws IOException;
+}
diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java
index 6e6bb2e8b..ad9633da8 100644
--- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java
+++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java
@@ -67,22 +67,15 @@ public abstract class ExternalAccountCredentials extends GoogleCredentials {
private static final long serialVersionUID = 8049126194174465023L;
- /** Base credential source class. Dictates the retrieval method of the external credential. */
- abstract static class CredentialSource implements java.io.Serializable {
-
- private static final long serialVersionUID = 8204657811562399944L;
-
- CredentialSource(Map credentialSourceMap) {
- checkNotNull(credentialSourceMap);
- }
- }
-
private static final String CLOUD_PLATFORM_SCOPE =
"https://www.googleapis.com/auth/cloud-platform";
static final String EXTERNAL_ACCOUNT_FILE_TYPE = "external_account";
static final String EXECUTABLE_SOURCE_KEY = "executable";
+ static final String DEFAULT_TOKEN_URL = "https://sts.googleapis.com/v1/token";
+ static final String PROGRAMMATIC_METRICS_HEADER_VALUE = "programmatic";
+
private final String transportFactoryClassName;
private final String audience;
private final String subjectTokenType;
@@ -104,11 +97,7 @@ abstract static class CredentialSource implements java.io.Serializable {
protected transient HttpTransportFactory transportFactory;
- @Nullable protected final ImpersonatedCredentials impersonatedCredentials;
-
- // Internal override for impersonated credentials. This is done to keep
- // impersonatedCredentials final.
- @Nullable private ImpersonatedCredentials impersonatedCredentialsOverride;
+ @Nullable protected ImpersonatedCredentials impersonatedCredentials;
private EnvironmentProvider environmentProvider;
@@ -224,8 +213,6 @@ protected ExternalAccountCredentials(
}
this.metricsHandler = new ExternalAccountMetricsHandler(this);
-
- this.impersonatedCredentials = buildImpersonatedCredentials();
}
/**
@@ -243,12 +230,12 @@ protected ExternalAccountCredentials(ExternalAccountCredentials.Builder builder)
this.transportFactoryClassName = checkNotNull(this.transportFactory.getClass().getName());
this.audience = checkNotNull(builder.audience);
this.subjectTokenType = checkNotNull(builder.subjectTokenType);
- this.tokenUrl = checkNotNull(builder.tokenUrl);
- this.credentialSource = checkNotNull(builder.credentialSource);
+ this.credentialSource = builder.credentialSource;
this.tokenInfoUrl = builder.tokenInfoUrl;
this.serviceAccountImpersonationUrl = builder.serviceAccountImpersonationUrl;
this.clientId = builder.clientId;
this.clientSecret = builder.clientSecret;
+ this.tokenUrl = builder.tokenUrl == null ? DEFAULT_TOKEN_URL : builder.tokenUrl;
this.scopes =
(builder.scopes == null || builder.scopes.isEmpty())
? Arrays.asList(CLOUD_PLATFORM_SCOPE)
@@ -277,8 +264,6 @@ protected ExternalAccountCredentials(ExternalAccountCredentials.Builder builder)
builder.metricsHandler == null
? new ExternalAccountMetricsHandler(this)
: builder.metricsHandler;
-
- this.impersonatedCredentials = buildImpersonatedCredentials();
}
ImpersonatedCredentials buildImpersonatedCredentials() {
@@ -316,10 +301,6 @@ ImpersonatedCredentials buildImpersonatedCredentials() {
.build();
}
- void overrideImpersonatedCredentials(ImpersonatedCredentials credentials) {
- this.impersonatedCredentialsOverride = credentials;
- }
-
@Override
public void getRequestMetadata(
URI uri, Executor executor, final RequestMetadataCallback callback) {
@@ -479,6 +460,10 @@ private static boolean isAwsCredential(Map credentialSource) {
&& ((String) credentialSource.get("environment_id")).startsWith("aws");
}
+ private boolean shouldBuildImpersonatedCredential() {
+ return this.serviceAccountImpersonationUrl != null && this.impersonatedCredentials == null;
+ }
+
/**
* Exchanges the external credential for a Google Cloud access token.
*
@@ -489,11 +474,11 @@ private static boolean isAwsCredential(Map credentialSource) {
protected AccessToken exchangeExternalCredentialForAccessToken(
StsTokenExchangeRequest stsTokenExchangeRequest) throws IOException {
// Handle service account impersonation if necessary.
- // Internal override takes priority.
- if (impersonatedCredentialsOverride != null) {
- return impersonatedCredentialsOverride.refreshAccessToken();
- } else if (impersonatedCredentials != null) {
- return impersonatedCredentials.refreshAccessToken();
+ if (this.shouldBuildImpersonatedCredential()) {
+ this.impersonatedCredentials = this.buildImpersonatedCredentials();
+ }
+ if (this.impersonatedCredentials != null) {
+ return this.impersonatedCredentials.refreshAccessToken();
}
StsRequestHandler.Builder requestHandler =
@@ -799,6 +784,19 @@ public Builder setSubjectTokenType(String subjectTokenType) {
return this;
}
+ /**
+ * Sets the Security Token Service subject token type based on the OAuth 2.0 token exchange
+ * spec. Indicates the type of the security token in the credential file.
+ *
+ * @param subjectTokenType the {@code SubjectTokenType} to set
+ * @return this {@code Builder} object
+ */
+ @CanIgnoreReturnValue
+ public Builder setSubjectTokenType(SubjectTokenTypes subjectTokenType) {
+ this.subjectTokenType = subjectTokenType.value;
+ return this;
+ }
+
/**
* Sets the Security Token Service token exchange endpoint.
*
@@ -953,4 +951,30 @@ Builder setEnvironmentProvider(EnvironmentProvider environmentProvider) {
@Override
public abstract ExternalAccountCredentials build();
}
+
+ /**
+ * Enum specifying values for the subjectTokenType field in {@code ExternalAccountCredentials}.
+ */
+ public enum SubjectTokenTypes {
+ AWS4("urn:ietf:params:aws:token-type:aws4_request"),
+ JWT("urn:ietf:params:oauth:token-type:jwt"),
+ SAML2("urn:ietf:params:oauth:token-type:saml2"),
+ ID_TOKEN("urn:ietf:params:oauth:token-type:id_token");
+
+ public final String value;
+
+ private SubjectTokenTypes(String value) {
+ this.value = value;
+ }
+ }
+
+ /** Base credential source class. Dictates the retrieval method of the external credential. */
+ abstract static class CredentialSource implements java.io.Serializable {
+
+ private static final long serialVersionUID = 8204657811562399944L;
+
+ CredentialSource(Map credentialSourceMap) {
+ checkNotNull(credentialSourceMap);
+ }
+ }
}
diff --git a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountMetricsHandler.java b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountMetricsHandler.java
index fcb656b5d..18fc124b8 100644
--- a/oauth2_http/java/com/google/auth/oauth2/ExternalAccountMetricsHandler.java
+++ b/oauth2_http/java/com/google/auth/oauth2/ExternalAccountMetricsHandler.java
@@ -43,7 +43,7 @@ class ExternalAccountMetricsHandler implements java.io.Serializable {
private final boolean configLifetime;
private final boolean saImpersonation;
- private String credentialSourceType;
+ private ExternalAccountCredentials credentials;
/**
* Constructor for the external account metrics handler.
@@ -55,7 +55,7 @@ class ExternalAccountMetricsHandler implements java.io.Serializable {
this.saImpersonation = creds.getServiceAccountImpersonationUrl() != null;
this.configLifetime =
creds.getServiceAccountImpersonationOptions().customTokenLifetimeRequested;
- this.credentialSourceType = creds.getCredentialSourceType();
+ this.credentials = creds;
}
/**
@@ -69,7 +69,7 @@ String getExternalAccountMetricsHeader() {
MetricsUtils.getLanguageAndAuthLibraryVersions(),
BYOID_METRICS_SECTION,
SOURCE_KEY,
- this.credentialSourceType,
+ this.credentials.getCredentialSourceType(),
IMPERSONATION_KEY,
this.saImpersonation,
CONFIG_LIFETIME_KEY,
diff --git a/oauth2_http/java/com/google/auth/oauth2/FileIdentityPoolSubjectTokenSupplier.java b/oauth2_http/java/com/google/auth/oauth2/FileIdentityPoolSubjectTokenSupplier.java
new file mode 100644
index 000000000..e46df2d9e
--- /dev/null
+++ b/oauth2_http/java/com/google/auth/oauth2/FileIdentityPoolSubjectTokenSupplier.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.auth.oauth2;
+
+import com.google.api.client.json.GenericJson;
+import com.google.api.client.json.JsonObjectParser;
+import com.google.auth.oauth2.IdentityPoolCredentialSource.CredentialFormatType;
+import com.google.common.io.CharStreams;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Paths;
+
+/**
+ * Internal provider for retrieving subject tokens for {@Link IdentityPoolCredentials} to exchange
+ * for GCP access tokens via a local file.
+ */
+class FileIdentityPoolSubjectTokenSupplier implements IdentityPoolSubjectTokenSupplier {
+
+ private final long serialVersionUID = 2475549052347431992L;
+
+ private final IdentityPoolCredentialSource credentialSource;
+
+ /**
+ * Constructor for FileIdentitySubjectTokenProvider
+ *
+ * @param credentialSource the credential source to use.
+ */
+ FileIdentityPoolSubjectTokenSupplier(IdentityPoolCredentialSource credentialSource) {
+ this.credentialSource = credentialSource;
+ }
+
+ @Override
+ public String getSubjectToken() throws IOException {
+ String credentialFilePath = this.credentialSource.credentialLocation;
+ if (!Files.exists(Paths.get(credentialFilePath), LinkOption.NOFOLLOW_LINKS)) {
+ throw new IOException(
+ String.format(
+ "Invalid credential location. The file at %s does not exist.", credentialFilePath));
+ }
+ try {
+ return parseToken(new FileInputStream(new File(credentialFilePath)), this.credentialSource);
+ } catch (IOException e) {
+ throw new IOException(
+ "Error when attempting to read the subject token from the credential file.", e);
+ }
+ }
+
+ static String parseToken(InputStream inputStream, IdentityPoolCredentialSource credentialSource)
+ throws IOException {
+ if (credentialSource.credentialFormatType == CredentialFormatType.TEXT) {
+ BufferedReader reader =
+ new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
+ return CharStreams.toString(reader);
+ }
+
+ JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY);
+ GenericJson fileContents =
+ parser.parseAndClose(inputStream, StandardCharsets.UTF_8, GenericJson.class);
+
+ if (!fileContents.containsKey(credentialSource.subjectTokenFieldName)) {
+ throw new IOException("Invalid subject token field name. No subject token was found.");
+ }
+ return (String) fileContents.get(credentialSource.subjectTokenFieldName);
+ }
+}
diff --git a/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java b/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java
index 776a01e05..fea188ccf 100644
--- a/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java
+++ b/oauth2_http/java/com/google/auth/oauth2/IdentityPoolCredentials.java
@@ -31,44 +31,55 @@
package com.google.auth.oauth2;
-import com.google.api.client.http.GenericUrl;
-import com.google.api.client.http.HttpHeaders;
-import com.google.api.client.http.HttpRequest;
-import com.google.api.client.http.HttpResponse;
-import com.google.api.client.json.GenericJson;
-import com.google.api.client.json.JsonObjectParser;
-import com.google.auth.oauth2.IdentityPoolCredentialSource.CredentialFormatType;
-import com.google.auth.oauth2.IdentityPoolCredentialSource.IdentityPoolCredentialSourceType;
-import com.google.common.io.CharStreams;
+import com.google.auth.http.HttpTransportFactory;
+import com.google.common.annotations.VisibleForTesting;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.LinkOption;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Map;
/**
- * Url-sourced and file-sourced external account credentials.
+ * Url-sourced, file-sourced, or user provided supplier method-sourced external account credentials.
*
* By default, attempts to exchange the external credential for a GCP access token.
*/
public class IdentityPoolCredentials extends ExternalAccountCredentials {
+ static final String FILE_METRICS_HEADER_VALUE = "file";
+ static final String URL_METRICS_HEADER_VALUE = "url";
private static final long serialVersionUID = 2471046175477275881L;
- private final IdentityPoolCredentialSource identityPoolCredentialSource;
+ private final IdentityPoolSubjectTokenSupplier subjectTokenSupplier;
+ private final String metricsHeaderValue;
/** Internal constructor. See {@link Builder}. */
IdentityPoolCredentials(Builder builder) {
super(builder);
- this.identityPoolCredentialSource = (IdentityPoolCredentialSource) builder.credentialSource;
+ IdentityPoolCredentialSource credentialSource =
+ (IdentityPoolCredentialSource) builder.credentialSource;
+
+ // Check that one and only one of supplier or credential source are provided.
+ if (builder.subjectTokenSupplier != null && credentialSource != null) {
+ throw new IllegalArgumentException(
+ "IdentityPoolCredentials cannot have both a subjectTokenSupplier and a credentialSource.");
+ }
+ if (builder.subjectTokenSupplier == null && credentialSource == null) {
+ throw new IllegalArgumentException(
+ "A subjectTokenSupplier or a credentialSource must be provided.");
+ }
+ if (builder.subjectTokenSupplier != null) {
+ this.subjectTokenSupplier = builder.subjectTokenSupplier;
+ this.metricsHeaderValue = PROGRAMMATIC_METRICS_HEADER_VALUE;
+ } else if (credentialSource.credentialSourceType
+ == IdentityPoolCredentialSource.IdentityPoolCredentialSourceType.FILE) {
+ this.subjectTokenSupplier = new FileIdentityPoolSubjectTokenSupplier(credentialSource);
+ this.metricsHeaderValue = FILE_METRICS_HEADER_VALUE;
+ } else {
+ this.subjectTokenSupplier =
+ new UrlIdentityPoolSubjectTokenSupplier(credentialSource, this.transportFactory);
+ this.metricsHeaderValue = URL_METRICS_HEADER_VALUE;
+ }
}
@Override
@@ -88,76 +99,17 @@ public AccessToken refreshAccessToken() throws IOException {
@Override
public String retrieveSubjectToken() throws IOException {
- if (identityPoolCredentialSource.credentialSourceType
- == IdentityPoolCredentialSource.IdentityPoolCredentialSourceType.FILE) {
- return retrieveSubjectTokenFromCredentialFile();
- }
- return getSubjectTokenFromMetadataServer();
+ return this.subjectTokenSupplier.getSubjectToken();
}
@Override
String getCredentialSourceType() {
- if (((IdentityPoolCredentialSource) this.getCredentialSource()).credentialSourceType
- == IdentityPoolCredentialSourceType.FILE) {
- return "file";
- } else {
- return "url";
- }
+ return this.metricsHeaderValue;
}
- private String retrieveSubjectTokenFromCredentialFile() throws IOException {
- String credentialFilePath = identityPoolCredentialSource.credentialLocation;
- if (!Files.exists(Paths.get(credentialFilePath), LinkOption.NOFOLLOW_LINKS)) {
- throw new IOException(
- String.format(
- "Invalid credential location. The file at %s does not exist.", credentialFilePath));
- }
- try {
- return parseToken(new FileInputStream(new File(credentialFilePath)));
- } catch (IOException e) {
- throw new IOException(
- "Error when attempting to read the subject token from the credential file.", e);
- }
- }
-
- private String parseToken(InputStream inputStream) throws IOException {
- if (identityPoolCredentialSource.credentialFormatType == CredentialFormatType.TEXT) {
- BufferedReader reader =
- new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
- return CharStreams.toString(reader);
- }
-
- JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY);
- GenericJson fileContents =
- parser.parseAndClose(inputStream, StandardCharsets.UTF_8, GenericJson.class);
-
- if (!fileContents.containsKey(identityPoolCredentialSource.subjectTokenFieldName)) {
- throw new IOException("Invalid subject token field name. No subject token was found.");
- }
- return (String) fileContents.get(identityPoolCredentialSource.subjectTokenFieldName);
- }
-
- private String getSubjectTokenFromMetadataServer() throws IOException {
- HttpRequest request =
- transportFactory
- .create()
- .createRequestFactory()
- .buildGetRequest(new GenericUrl(identityPoolCredentialSource.credentialLocation));
- request.setParser(new JsonObjectParser(OAuth2Utils.JSON_FACTORY));
-
- if (identityPoolCredentialSource.hasHeaders()) {
- HttpHeaders headers = new HttpHeaders();
- headers.putAll(identityPoolCredentialSource.headers);
- request.setHeaders(headers);
- }
-
- try {
- HttpResponse response = request.execute();
- return parseToken(response.getContent());
- } catch (IOException e) {
- throw new IOException(
- String.format("Error getting subject token from metadata server: %s", e.getMessage()), e);
- }
+ @VisibleForTesting
+ IdentityPoolSubjectTokenSupplier getIdentityPoolSubjectTokenSupplier() {
+ return this.subjectTokenSupplier;
}
/** Clones the IdentityPoolCredentials with the specified scopes. */
@@ -177,10 +129,99 @@ public static Builder newBuilder(IdentityPoolCredentials identityPoolCredentials
public static class Builder extends ExternalAccountCredentials.Builder {
+ private IdentityPoolSubjectTokenSupplier subjectTokenSupplier;
+
Builder() {}
Builder(IdentityPoolCredentials credentials) {
super(credentials);
+ if (this.credentialSource == null) {
+ this.subjectTokenSupplier = credentials.subjectTokenSupplier;
+ }
+ }
+
+ /**
+ * Sets the subject token supplier. The supplier should return a valid subject token string.
+ *
+ * @param subjectTokenSupplier the supplier to use.
+ * @return this {@code Builder} object
+ */
+ @CanIgnoreReturnValue
+ public Builder setSubjectTokenSupplier(IdentityPoolSubjectTokenSupplier subjectTokenSupplier) {
+ this.subjectTokenSupplier = subjectTokenSupplier;
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setHttpTransportFactory(HttpTransportFactory transportFactory) {
+ super.setHttpTransportFactory(transportFactory);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setAudience(String audience) {
+ super.setAudience(audience);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setSubjectTokenType(String subjectTokenType) {
+ super.setSubjectTokenType(subjectTokenType);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setSubjectTokenType(SubjectTokenTypes subjectTokenType) {
+ super.setSubjectTokenType(subjectTokenType);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setTokenUrl(String tokenUrl) {
+ super.setTokenUrl(tokenUrl);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setCredentialSource(IdentityPoolCredentialSource credentialSource) {
+ super.setCredentialSource(credentialSource);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setServiceAccountImpersonationUrl(String serviceAccountImpersonationUrl) {
+ super.setServiceAccountImpersonationUrl(serviceAccountImpersonationUrl);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setTokenInfoUrl(String tokenInfoUrl) {
+ super.setTokenInfoUrl(tokenInfoUrl);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setQuotaProjectId(String quotaProjectId) {
+ super.setQuotaProjectId(quotaProjectId);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setClientId(String clientId) {
+ super.setClientId(clientId);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setClientSecret(String clientSecret) {
+ super.setClientSecret(clientSecret);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setScopes(Collection scopes) {
+ super.setScopes(scopes);
+ return this;
}
@Override
@@ -190,6 +231,24 @@ public Builder setWorkforcePoolUserProject(String workforcePoolUserProject) {
return this;
}
+ @CanIgnoreReturnValue
+ public Builder setServiceAccountImpersonationOptions(Map optionsMap) {
+ super.setServiceAccountImpersonationOptions(optionsMap);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setUniverseDomain(String universeDomain) {
+ super.setUniverseDomain(universeDomain);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ Builder setEnvironmentProvider(EnvironmentProvider environmentProvider) {
+ super.setEnvironmentProvider(environmentProvider);
+ return this;
+ }
+
@Override
public IdentityPoolCredentials build() {
return new IdentityPoolCredentials(this);
diff --git a/oauth2_http/java/com/google/auth/oauth2/IdentityPoolSubjectTokenSupplier.java b/oauth2_http/java/com/google/auth/oauth2/IdentityPoolSubjectTokenSupplier.java
new file mode 100644
index 000000000..a057bba48
--- /dev/null
+++ b/oauth2_http/java/com/google/auth/oauth2/IdentityPoolSubjectTokenSupplier.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.auth.oauth2;
+
+import java.io.IOException;
+import java.io.Serializable;
+
+@FunctionalInterface
+/**
+ * Provider for retrieving subject tokens for {@Link IdentityPoolCredentials} to exchange for GCP
+ * access tokens.
+ */
+public interface IdentityPoolSubjectTokenSupplier extends Serializable {
+
+ /**
+ * Gets a subject token that can be exchanged for a GCP access token.
+ *
+ * @return a valid subject token.
+ * @throws IOException
+ */
+ String getSubjectToken() throws IOException;
+}
diff --git a/oauth2_http/java/com/google/auth/oauth2/InternalAwsSecurityCredentialsSupplier.java b/oauth2_http/java/com/google/auth/oauth2/InternalAwsSecurityCredentialsSupplier.java
new file mode 100644
index 000000000..308717470
--- /dev/null
+++ b/oauth2_http/java/com/google/auth/oauth2/InternalAwsSecurityCredentialsSupplier.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+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;
+import com.google.api.client.json.GenericJson;
+import com.google.api.client.json.JsonParser;
+import com.google.auth.http.HttpTransportFactory;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+
+/**
+ * Internal provider for retrieving AWS security credentials for {@Link AwsCredentials} to exchange
+ * for GCP access tokens. The credentials are retrieved either via environment variables or metadata
+ * endpoints.
+ */
+class InternalAwsSecurityCredentialsSupplier implements AwsSecurityCredentialsSupplier {
+ private static final long serialVersionUID = 4438370785261365013L;
+
+ // Supported environment variables.
+ static final String AWS_REGION = "AWS_REGION";
+ static final String AWS_DEFAULT_REGION = "AWS_DEFAULT_REGION";
+ static final String AWS_ACCESS_KEY_ID = "AWS_ACCESS_KEY_ID";
+ static final String AWS_SECRET_ACCESS_KEY = "AWS_SECRET_ACCESS_KEY";
+ static final String AWS_SESSION_TOKEN = "AWS_SESSION_TOKEN";
+
+ 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";
+
+ private final AwsCredentialSource awsCredentialSource;
+ private EnvironmentProvider environmentProvider;
+ private transient HttpTransportFactory transportFactory;
+
+ /**
+ * Constructor for InternalAwsSecurityCredentialsProvider
+ *
+ * @param awsCredentialSource the credential source to use.
+ * @param environmentProvider the environment provider to use for environment variables.
+ * @param transportFactory the transport factory to use for metadata requests.
+ */
+ InternalAwsSecurityCredentialsSupplier(
+ AwsCredentialSource awsCredentialSource,
+ EnvironmentProvider environmentProvider,
+ HttpTransportFactory transportFactory) {
+ this.environmentProvider = environmentProvider;
+ this.awsCredentialSource = awsCredentialSource;
+ this.transportFactory = transportFactory;
+ }
+
+ @Override
+ public AwsSecurityCredentials getCredentials() throws IOException {
+ // Check environment variables for credentials first.
+ if (canRetrieveSecurityCredentialsFromEnvironment()) {
+ String accessKeyId = environmentProvider.getEnv(AWS_ACCESS_KEY_ID);
+ String secretAccessKey = environmentProvider.getEnv(AWS_SECRET_ACCESS_KEY);
+ String token = environmentProvider.getEnv(AWS_SESSION_TOKEN);
+ return new AwsSecurityCredentials(accessKeyId, secretAccessKey, token);
+ }
+
+ Map metadataRequestHeaders = createMetadataRequestHeaders(awsCredentialSource);
+
+ // Credentials not retrievable from environment variables - call metadata server.
+ // Retrieve the IAM role that is attached to the VM. This is required to retrieve the AWS
+ // security credentials.
+ if (awsCredentialSource.url == null || awsCredentialSource.url.isEmpty()) {
+ throw new 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", metadataRequestHeaders);
+
+ // Retrieve the AWS security credentials by calling the endpoint specified by the credential
+ // source.
+ String awsCredentials =
+ retrieveResource(
+ awsCredentialSource.url + "/" + roleName, "credentials", metadataRequestHeaders);
+
+ JsonParser parser = OAuth2Utils.JSON_FACTORY.createJsonParser(awsCredentials);
+ GenericJson genericJson = parser.parseAndClose(GenericJson.class);
+
+ String accessKeyId = (String) genericJson.get("AccessKeyId");
+ String secretAccessKey = (String) genericJson.get("SecretAccessKey");
+ String token = (String) genericJson.get("Token");
+
+ // These credentials last for a few hours - we may consider caching these in the
+ // future.
+ return new AwsSecurityCredentials(accessKeyId, secretAccessKey, token);
+ }
+
+ @Override
+ public String getRegion() throws IOException {
+ String region;
+ if (canRetrieveRegionFromEnvironment()) {
+ // For AWS Lambda, the region is retrieved through the AWS_REGION environment variable.
+ region = environmentProvider.getEnv(AWS_REGION);
+ if (region != null && region.trim().length() > 0) {
+ return region;
+ }
+ return environmentProvider.getEnv(AWS_DEFAULT_REGION);
+ }
+
+ Map metadataRequestHeaders = createMetadataRequestHeaders(awsCredentialSource);
+
+ if (awsCredentialSource.regionUrl == null || awsCredentialSource.regionUrl.isEmpty()) {
+ throw new IOException(
+ "Unable to determine the AWS region. The credential source does not contain the region URL.");
+ }
+
+ 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`.
+ return region.substring(0, region.length() - 1);
+ }
+
+ private boolean canRetrieveRegionFromEnvironment() {
+ // The AWS region can be provided through AWS_REGION or AWS_DEFAULT_REGION. Only one is
+ // required.
+ List keys = ImmutableList.of(AWS_REGION, AWS_DEFAULT_REGION);
+ for (String env : keys) {
+ String value = environmentProvider.getEnv(env);
+ if (value != null && value.trim().length() > 0) {
+ // Region available.
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean canRetrieveSecurityCredentialsFromEnvironment() {
+ // Check if both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are available.
+ List keys = ImmutableList.of(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY);
+ for (String env : keys) {
+ String value = environmentProvider.getEnv(env);
+ if (value == null || value.trim().length() == 0) {
+ // Return false if one of them are missing.
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @VisibleForTesting
+ boolean shouldUseMetadataServer() {
+ return (!canRetrieveRegionFromEnvironment()
+ || !canRetrieveSecurityCredentialsFromEnvironment());
+ }
+
+ 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.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) {
+ throw new IOException(String.format("Failed to retrieve AWS %s.", resourceName), e);
+ }
+ }
+
+ @VisibleForTesting
+ 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;
+ }
+}
diff --git a/oauth2_http/java/com/google/auth/oauth2/PluggableAuthCredentials.java b/oauth2_http/java/com/google/auth/oauth2/PluggableAuthCredentials.java
index 5caa567c1..e984c8173 100644
--- a/oauth2_http/java/com/google/auth/oauth2/PluggableAuthCredentials.java
+++ b/oauth2_http/java/com/google/auth/oauth2/PluggableAuthCredentials.java
@@ -31,6 +31,7 @@
package com.google.auth.oauth2;
+import com.google.auth.http.HttpTransportFactory;
import com.google.auth.oauth2.ExecutableHandler.ExecutableOptions;
import com.google.common.annotations.VisibleForTesting;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
@@ -97,6 +98,9 @@
* Please see this repositories README for a complete executable request/response specification.
*/
public class PluggableAuthCredentials extends ExternalAccountCredentials {
+
+ static final String PLUGGABLE_AUTH_METRICS_HEADER_VALUE = "executable";
+
private final PluggableAuthCredentialSource config;
private final ExecutableHandler handler;
@@ -111,10 +115,6 @@ public class PluggableAuthCredentials extends ExternalAccountCredentials {
} else {
handler = new PluggableAuthHandler(getEnvironmentProvider());
}
-
- // Re-initialize impersonated credentials as the handler hasn't been set yet when
- // this is called in the base class.
- overrideImpersonatedCredentials(buildImpersonatedCredentials());
}
@Override
@@ -192,7 +192,7 @@ public PluggableAuthCredentials createScoped(Collection newScopes) {
@Override
String getCredentialSourceType() {
- return "executable";
+ return PLUGGABLE_AUTH_METRICS_HEADER_VALUE;
}
public static Builder newBuilder() {
@@ -226,6 +226,102 @@ public Builder setExecutableHandler(ExecutableHandler handler) {
return this;
}
+ @CanIgnoreReturnValue
+ public Builder setHttpTransportFactory(HttpTransportFactory transportFactory) {
+ super.setHttpTransportFactory(transportFactory);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setAudience(String audience) {
+ super.setAudience(audience);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setSubjectTokenType(String subjectTokenType) {
+ super.setSubjectTokenType(subjectTokenType);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setSubjectTokenType(SubjectTokenTypes subjectTokenType) {
+ super.setSubjectTokenType(subjectTokenType);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setTokenUrl(String tokenUrl) {
+ super.setTokenUrl(tokenUrl);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setCredentialSource(PluggableAuthCredentialSource credentialSource) {
+ super.setCredentialSource(credentialSource);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setServiceAccountImpersonationUrl(String serviceAccountImpersonationUrl) {
+ super.setServiceAccountImpersonationUrl(serviceAccountImpersonationUrl);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setTokenInfoUrl(String tokenInfoUrl) {
+ super.setTokenInfoUrl(tokenInfoUrl);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setQuotaProjectId(String quotaProjectId) {
+ super.setQuotaProjectId(quotaProjectId);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setClientId(String clientId) {
+ super.setClientId(clientId);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setClientSecret(String clientSecret) {
+ super.setClientSecret(clientSecret);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setScopes(Collection scopes) {
+ super.setScopes(scopes);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setWorkforcePoolUserProject(String workforcePoolUserProject) {
+ super.setWorkforcePoolUserProject(workforcePoolUserProject);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setServiceAccountImpersonationOptions(Map optionsMap) {
+ super.setServiceAccountImpersonationOptions(optionsMap);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ public Builder setUniverseDomain(String universeDomain) {
+ super.setUniverseDomain(universeDomain);
+ return this;
+ }
+
+ @CanIgnoreReturnValue
+ Builder setEnvironmentProvider(EnvironmentProvider environmentProvider) {
+ super.setEnvironmentProvider(environmentProvider);
+ return this;
+ }
+
@Override
public PluggableAuthCredentials build() {
return new PluggableAuthCredentials(this);
diff --git a/oauth2_http/java/com/google/auth/oauth2/UrlIdentityPoolSubjectTokenSupplier.java b/oauth2_http/java/com/google/auth/oauth2/UrlIdentityPoolSubjectTokenSupplier.java
new file mode 100644
index 000000000..f886bfdd0
--- /dev/null
+++ b/oauth2_http/java/com/google/auth/oauth2/UrlIdentityPoolSubjectTokenSupplier.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * * Neither the name of Google LLC nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.google.auth.oauth2;
+
+import static com.google.auth.oauth2.FileIdentityPoolSubjectTokenSupplier.parseToken;
+
+import com.google.api.client.http.GenericUrl;
+import com.google.api.client.http.HttpHeaders;
+import com.google.api.client.http.HttpRequest;
+import com.google.api.client.http.HttpResponse;
+import com.google.api.client.json.JsonObjectParser;
+import com.google.auth.http.HttpTransportFactory;
+import java.io.IOException;
+
+/**
+ * Provider for retrieving subject tokens for {@Link IdentityPoolCredentials} to exchange for GCP
+ * access tokens. The subject token is retrieved by calling a URL that returns the token.
+ */
+class UrlIdentityPoolSubjectTokenSupplier implements IdentityPoolSubjectTokenSupplier {
+
+ private static final long serialVersionUID = 4964578313468011844L;
+
+ private final IdentityPoolCredentialSource credentialSource;
+ private final transient HttpTransportFactory transportFactory;
+
+ /**
+ * Constructor for UrlIdentityPoolSubjectTokenProvider.
+ *
+ * @param credentialSource the credential source to use.
+ * @param transportFactory the transport factory to use for calling the URL.
+ */
+ UrlIdentityPoolSubjectTokenSupplier(
+ IdentityPoolCredentialSource credentialSource, HttpTransportFactory transportFactory) {
+ this.credentialSource = credentialSource;
+ this.transportFactory = transportFactory;
+ }
+
+ @Override
+ public String getSubjectToken() throws IOException {
+ HttpRequest request =
+ transportFactory
+ .create()
+ .createRequestFactory()
+ .buildGetRequest(new GenericUrl(credentialSource.credentialLocation));
+ request.setParser(new JsonObjectParser(OAuth2Utils.JSON_FACTORY));
+
+ if (credentialSource.hasHeaders()) {
+ HttpHeaders headers = new HttpHeaders();
+ headers.putAll(credentialSource.headers);
+ request.setHeaders(headers);
+ }
+
+ try {
+ HttpResponse response = request.execute();
+ return parseToken(response.getContent(), this.credentialSource);
+ } catch (IOException e) {
+ throw new IOException(
+ String.format("Error getting subject token from metadata server: %s", e.getMessage()), e);
+ }
+ }
+}
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java b/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java
index 94dbf5845..aea3f2906 100644
--- a/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java
+++ b/oauth2_http/javatests/com/google/auth/oauth2/AwsCredentialsTest.java
@@ -45,7 +45,6 @@
import com.google.api.client.util.Clock;
import com.google.auth.TestUtils;
import com.google.auth.oauth2.ExternalAccountCredentialsTest.MockExternalAccountCredentialsTransportFactory;
-import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
@@ -55,6 +54,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.function.Supplier;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -63,12 +63,14 @@
@RunWith(JUnit4.class)
public class AwsCredentialsTest extends BaseSerializationTest {
- private static final String STS_URL = "https://sts.googleapis.com";
+ private static final String STS_URL = "https://sts.googleapis.com/v1/token";
private static final String AWS_CREDENTIALS_URL = "https://169.254.169.254";
private static final String AWS_CREDENTIALS_URL_WITH_ROLE = "https://169.254.169.254/roleName";
private static final String AWS_REGION_URL = "https://169.254.169.254/region";
private static final String AWS_IMDSV2_SESSION_TOKEN_URL = "https://169.254.169.254/imdsv2";
private static final String AWS_IMDSV2_SESSION_TOKEN = "sessiontoken";
+ private static final String DEFAULT_REGIONAL_CREDENTIAL_VERIFICATION_URL =
+ "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15";
private static final String GET_CALLER_IDENTITY_URL =
"https://sts.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15";
@@ -93,15 +95,17 @@ public class AwsCredentialsTest extends BaseSerializationTest {
new AwsCredentialSource(AWS_CREDENTIAL_SOURCE_MAP);
private static final AwsCredentials AWS_CREDENTIAL =
- (AwsCredentials)
- AwsCredentials.newBuilder()
- .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY)
- .setAudience("audience")
- .setSubjectTokenType("subjectTokenType")
- .setTokenUrl(STS_URL)
- .setTokenInfoUrl("tokenInfoUrl")
- .setCredentialSource(AWS_CREDENTIAL_SOURCE)
- .build();
+ AwsCredentials.newBuilder()
+ .setHttpTransportFactory(OAuth2Utils.HTTP_TRANSPORT_FACTORY)
+ .setAudience("audience")
+ .setSubjectTokenType("subjectTokenType")
+ .setTokenUrl(STS_URL)
+ .setTokenInfoUrl("tokenInfoUrl")
+ .setCredentialSource(AWS_CREDENTIAL_SOURCE)
+ .build();
+
+ private static final AwsSecurityCredentials programmaticAwsCreds =
+ new AwsSecurityCredentials("testAccessKey", "testSecretAccessKey", null);
@Test
public void test_awsCredentialSource() {
@@ -121,12 +125,11 @@ public void refreshAccessToken_withoutServiceAccountImpersonation() throws IOExc
new MockExternalAccountCredentialsTransportFactory();
AwsCredentials awsCredential =
- (AwsCredentials)
- AwsCredentials.newBuilder(AWS_CREDENTIAL)
- .setTokenUrl(transportFactory.transport.getStsUrl())
- .setHttpTransportFactory(transportFactory)
- .setCredentialSource(buildAwsCredentialSource(transportFactory))
- .build();
+ AwsCredentials.newBuilder(AWS_CREDENTIAL)
+ .setTokenUrl(transportFactory.transport.getStsUrl())
+ .setHttpTransportFactory(transportFactory)
+ .setCredentialSource(buildAwsCredentialSource(transportFactory))
+ .build();
AccessToken accessToken = awsCredential.refreshAccessToken();
@@ -146,17 +149,16 @@ public void refreshAccessToken_withServiceAccountImpersonation() throws IOExcept
transportFactory.transport.setExpireTime(TestUtils.getDefaultExpireTime());
AwsCredentials awsCredential =
- (AwsCredentials)
- AwsCredentials.newBuilder()
- .setHttpTransportFactory(transportFactory)
- .setAudience("audience")
- .setSubjectTokenType("subjectTokenType")
- .setTokenUrl(transportFactory.transport.getStsUrl())
- .setTokenInfoUrl("tokenInfoUrl")
- .setCredentialSource(buildAwsCredentialSource(transportFactory))
- .setServiceAccountImpersonationUrl(
- transportFactory.transport.getServiceAccountImpersonationUrl())
- .build();
+ AwsCredentials.newBuilder()
+ .setHttpTransportFactory(transportFactory)
+ .setAudience("audience")
+ .setSubjectTokenType("subjectTokenType")
+ .setTokenUrl(transportFactory.transport.getStsUrl())
+ .setTokenInfoUrl("tokenInfoUrl")
+ .setCredentialSource(buildAwsCredentialSource(transportFactory))
+ .setServiceAccountImpersonationUrl(
+ transportFactory.transport.getServiceAccountImpersonationUrl())
+ .build();
AccessToken accessToken = awsCredential.refreshAccessToken();
@@ -177,19 +179,18 @@ public void refreshAccessToken_withServiceAccountImpersonationOptions() throws I
transportFactory.transport.setExpireTime(TestUtils.getDefaultExpireTime());
AwsCredentials awsCredential =
- (AwsCredentials)
- AwsCredentials.newBuilder()
- .setHttpTransportFactory(transportFactory)
- .setAudience("audience")
- .setSubjectTokenType("subjectTokenType")
- .setTokenUrl(transportFactory.transport.getStsUrl())
- .setTokenInfoUrl("tokenInfoUrl")
- .setCredentialSource(buildAwsCredentialSource(transportFactory))
- .setServiceAccountImpersonationUrl(
- transportFactory.transport.getServiceAccountImpersonationUrl())
- .setServiceAccountImpersonationOptions(
- ExternalAccountCredentialsTest.buildServiceAccountImpersonationOptions(2800))
- .build();
+ AwsCredentials.newBuilder()
+ .setHttpTransportFactory(transportFactory)
+ .setAudience("audience")
+ .setSubjectTokenType("subjectTokenType")
+ .setTokenUrl(transportFactory.transport.getStsUrl())
+ .setTokenInfoUrl("tokenInfoUrl")
+ .setCredentialSource(buildAwsCredentialSource(transportFactory))
+ .setServiceAccountImpersonationUrl(
+ transportFactory.transport.getServiceAccountImpersonationUrl())
+ .setServiceAccountImpersonationOptions(
+ ExternalAccountCredentialsTest.buildServiceAccountImpersonationOptions(2800))
+ .build();
AccessToken accessToken = awsCredential.refreshAccessToken();
@@ -210,6 +211,67 @@ public void refreshAccessToken_withServiceAccountImpersonationOptions() throws I
ExternalAccountCredentialsTest.validateMetricsHeader(headers, "aws", true, true);
}
+ @Test
+ public void refreshAccessTokenProgrammaticRefresh_withoutServiceAccountImpersonation()
+ throws IOException {
+ MockExternalAccountCredentialsTransportFactory transportFactory =
+ new MockExternalAccountCredentialsTransportFactory();
+
+ AwsSecurityCredentialsSupplier supplier =
+ new TestAwsSecurityCredentialsSupplier("test", programmaticAwsCreds, null);
+
+ AwsCredentials awsCredential =
+ AwsCredentials.newBuilder()
+ .setAwsSecurityCredentialsSupplier(supplier)
+ .setHttpTransportFactory(transportFactory)
+ .setAudience("audience")
+ .setTokenUrl(STS_URL)
+ .setSubjectTokenType("subjectTokenType")
+ .build();
+
+ AccessToken accessToken = awsCredential.refreshAccessToken();
+
+ assertEquals(transportFactory.transport.getAccessToken(), accessToken.getTokenValue());
+
+ // Validate metrics header is set correctly on the sts request.
+ Map> headers =
+ transportFactory.transport.getRequests().get(0).getHeaders();
+ ExternalAccountCredentialsTest.validateMetricsHeader(headers, "programmatic", false, false);
+ }
+
+ @Test
+ public void refreshAccessTokenProgrammaticRefresh_withServiceAccountImpersonation()
+ throws IOException {
+ MockExternalAccountCredentialsTransportFactory transportFactory =
+ new MockExternalAccountCredentialsTransportFactory();
+
+ transportFactory.transport.setExpireTime(TestUtils.getDefaultExpireTime());
+
+ AwsSecurityCredentialsSupplier supplier =
+ new TestAwsSecurityCredentialsSupplier("test", programmaticAwsCreds, null);
+
+ AwsCredentials awsCredential =
+ AwsCredentials.newBuilder()
+ .setAwsSecurityCredentialsSupplier(supplier)
+ .setHttpTransportFactory(transportFactory)
+ .setAudience("audience")
+ .setTokenUrl(STS_URL)
+ .setSubjectTokenType("subjectTokenType")
+ .setServiceAccountImpersonationUrl(
+ transportFactory.transport.getServiceAccountImpersonationUrl())
+ .build();
+
+ AccessToken accessToken = awsCredential.refreshAccessToken();
+
+ assertEquals(
+ transportFactory.transport.getServiceAccountAccessToken(), accessToken.getTokenValue());
+
+ // Validate metrics header is set correctly on the sts request.
+ Map> headers =
+ transportFactory.transport.getRequests().get(0).getHeaders();
+ ExternalAccountCredentialsTest.validateMetricsHeader(headers, "programmatic", true, false);
+ }
+
@Test
@SuppressWarnings("unchecked")
public void retrieveSubjectToken() throws IOException {
@@ -217,11 +279,10 @@ public void retrieveSubjectToken() throws IOException {
new MockExternalAccountCredentialsTransportFactory();
AwsCredentials awsCredential =
- (AwsCredentials)
- AwsCredentials.newBuilder(AWS_CREDENTIAL)
- .setHttpTransportFactory(transportFactory)
- .setCredentialSource(buildAwsCredentialSource(transportFactory))
- .build();
+ AwsCredentials.newBuilder(AWS_CREDENTIAL)
+ .setHttpTransportFactory(transportFactory)
+ .setCredentialSource(buildAwsCredentialSource(transportFactory))
+ .build();
String subjectToken = URLDecoder.decode(awsCredential.retrieveSubjectToken(), "UTF-8");
@@ -262,11 +323,10 @@ public void retrieveSubjectTokenWithSessionTokenUrl() throws IOException {
new MockExternalAccountCredentialsTransportFactory();
AwsCredentials awsCredential =
- (AwsCredentials)
- AwsCredentials.newBuilder(AWS_CREDENTIAL)
- .setHttpTransportFactory(transportFactory)
- .setCredentialSource(buildAwsImdsv2CredentialSource(transportFactory))
- .build();
+ AwsCredentials.newBuilder(AWS_CREDENTIAL)
+ .setHttpTransportFactory(transportFactory)
+ .setCredentialSource(buildAwsImdsv2CredentialSource(transportFactory))
+ .build();
String subjectToken = URLDecoder.decode(awsCredential.retrieveSubjectToken(), "UTF-8");
@@ -288,7 +348,7 @@ public void retrieveSubjectTokenWithSessionTokenUrl() throws IOException {
assertNotNull(headers.get("Authorization"));
List requests = transportFactory.transport.getRequests();
- assertEquals(4, requests.size());
+ assertEquals(5, requests.size());
// Validate the session token request
ValidateRequest(
@@ -297,15 +357,17 @@ public void retrieveSubjectTokenWithSessionTokenUrl() throws IOException {
new HashMap() {
{
put(
- AwsCredentials.AWS_IMDSV2_SESSION_TOKEN_TTL_HEADER,
- AwsCredentials.AWS_IMDSV2_SESSION_TOKEN_TTL);
+ InternalAwsSecurityCredentialsSupplier.AWS_IMDSV2_SESSION_TOKEN_TTL_HEADER,
+ InternalAwsSecurityCredentialsSupplier.AWS_IMDSV2_SESSION_TOKEN_TTL);
}
});
Map sessionTokenHeader =
new HashMap() {
{
- put(AwsCredentials.AWS_IMDSV2_SESSION_TOKEN_HEADER, AWS_IMDSV2_SESSION_TOKEN);
+ put(
+ InternalAwsSecurityCredentialsSupplier.AWS_IMDSV2_SESSION_TOKEN_HEADER,
+ AWS_IMDSV2_SESSION_TOKEN);
}
};
@@ -313,10 +375,10 @@ public void retrieveSubjectTokenWithSessionTokenUrl() throws IOException {
ValidateRequest(requests.get(1), AWS_REGION_URL, sessionTokenHeader);
// Validate role request.
- ValidateRequest(requests.get(2), AWS_CREDENTIALS_URL, sessionTokenHeader);
+ ValidateRequest(requests.get(3), AWS_CREDENTIALS_URL, sessionTokenHeader);
// Validate security credentials request.
- ValidateRequest(requests.get(3), AWS_CREDENTIALS_URL_WITH_ROLE, sessionTokenHeader);
+ ValidateRequest(requests.get(4), AWS_CREDENTIALS_URL_WITH_ROLE, sessionTokenHeader);
}
@Test
@@ -334,12 +396,11 @@ public void retrieveSubjectToken_imdsv1EnvVariablesSet_metadataServerNotCalled()
.setEnv("AWS_SESSION_TOKEN", "awsToken");
AwsCredentials awsCredential =
- (AwsCredentials)
- AwsCredentials.newBuilder(AWS_CREDENTIAL)
- .setHttpTransportFactory(transportFactory)
- .setCredentialSource(buildAwsCredentialSource(transportFactory))
- .setEnvironmentProvider(environmentProvider)
- .build();
+ AwsCredentials.newBuilder(AWS_CREDENTIAL)
+ .setHttpTransportFactory(transportFactory)
+ .setCredentialSource(buildAwsCredentialSource(transportFactory))
+ .setEnvironmentProvider(environmentProvider)
+ .build();
String subjectToken = URLDecoder.decode(awsCredential.retrieveSubjectToken(), "UTF-8");
@@ -381,12 +442,11 @@ public void retrieveSubjectToken_imdsv2EnvVariablesSet_metadataServerNotCalled()
.setEnv("AWS_SESSION_TOKEN", "awsToken");
AwsCredentials awsCredential =
- (AwsCredentials)
- AwsCredentials.newBuilder(AWS_CREDENTIAL)
- .setHttpTransportFactory(transportFactory)
- .setCredentialSource(buildAwsImdsv2CredentialSource(transportFactory))
- .setEnvironmentProvider(environmentProvider)
- .build();
+ AwsCredentials.newBuilder(AWS_CREDENTIAL)
+ .setHttpTransportFactory(transportFactory)
+ .setCredentialSource(buildAwsImdsv2CredentialSource(transportFactory))
+ .setEnvironmentProvider(environmentProvider)
+ .build();
String subjectToken = URLDecoder.decode(awsCredential.retrieveSubjectToken(), "UTF-8");
@@ -422,11 +482,10 @@ public void retrieveSubjectToken_noRegion_expectThrows() {
transportFactory.transport.addResponseErrorSequence(response);
AwsCredentials awsCredential =
- (AwsCredentials)
- AwsCredentials.newBuilder(AWS_CREDENTIAL)
- .setHttpTransportFactory(transportFactory)
- .setCredentialSource(buildAwsCredentialSource(transportFactory))
- .build();
+ AwsCredentials.newBuilder(AWS_CREDENTIAL)
+ .setHttpTransportFactory(transportFactory)
+ .setCredentialSource(buildAwsCredentialSource(transportFactory))
+ .build();
try {
awsCredential.retrieveSubjectToken();
@@ -452,11 +511,10 @@ public void retrieveSubjectToken_noRole_expectThrows() {
transportFactory.transport.addResponseSequence(true, false);
AwsCredentials awsCredential =
- (AwsCredentials)
- AwsCredentials.newBuilder(AWS_CREDENTIAL)
- .setHttpTransportFactory(transportFactory)
- .setCredentialSource(buildAwsCredentialSource(transportFactory))
- .build();
+ AwsCredentials.newBuilder(AWS_CREDENTIAL)
+ .setHttpTransportFactory(transportFactory)
+ .setCredentialSource(buildAwsCredentialSource(transportFactory))
+ .build();
try {
awsCredential.retrieveSubjectToken();
@@ -485,11 +543,10 @@ public void retrieveSubjectToken_noCredentials_expectThrows() {
transportFactory.transport.addResponseSequence(true, true, false);
AwsCredentials awsCredential =
- (AwsCredentials)
- AwsCredentials.newBuilder(AWS_CREDENTIAL)
- .setHttpTransportFactory(transportFactory)
- .setCredentialSource(buildAwsCredentialSource(transportFactory))
- .build();
+ AwsCredentials.newBuilder(AWS_CREDENTIAL)
+ .setHttpTransportFactory(transportFactory)
+ .setCredentialSource(buildAwsCredentialSource(transportFactory))
+ .build();
try {
awsCredential.retrieveSubjectToken();
@@ -521,11 +578,10 @@ public void retrieveSubjectToken_noRegionUrlProvided() {
credentialSource.put("regional_cred_verification_url", GET_CALLER_IDENTITY_URL);
AwsCredentials awsCredential =
- (AwsCredentials)
- AwsCredentials.newBuilder(AWS_CREDENTIAL)
- .setHttpTransportFactory(transportFactory)
- .setCredentialSource(new AwsCredentialSource(credentialSource))
- .build();
+ AwsCredentials.newBuilder(AWS_CREDENTIAL)
+ .setHttpTransportFactory(transportFactory)
+ .setCredentialSource(new AwsCredentialSource(credentialSource))
+ .build();
try {
awsCredential.retrieveSubjectToken();
@@ -542,6 +598,115 @@ public void retrieveSubjectToken_noRegionUrlProvided() {
assertTrue(requests.isEmpty());
}
+ @Test
+ public void retrieveSubjectToken_withProgrammaticRefresh() throws IOException {
+ MockExternalAccountCredentialsTransportFactory transportFactory =
+ new MockExternalAccountCredentialsTransportFactory();
+
+ AwsSecurityCredentialsSupplier supplier =
+ new TestAwsSecurityCredentialsSupplier("test", programmaticAwsCreds, null);
+
+ AwsCredentials awsCredential =
+ AwsCredentials.newBuilder()
+ .setAwsSecurityCredentialsSupplier(supplier)
+ .setHttpTransportFactory(transportFactory)
+ .setAudience("audience")
+ .setTokenUrl(STS_URL)
+ .setSubjectTokenType("subjectTokenType")
+ .build();
+
+ String subjectToken = URLDecoder.decode(awsCredential.retrieveSubjectToken(), "UTF-8");
+
+ JsonParser parser = OAuth2Utils.JSON_FACTORY.createJsonParser(subjectToken);
+ GenericJson json = parser.parseAndClose(GenericJson.class);
+
+ List