Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Adds support for user defined subject token suppliers in AWSCredentials and IdentityPoolCredentials #1336

Merged
merged 44 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
b21127a
feat: adds programmatic auth credentials for identity pool and aws cr…
aeitzman Nov 27, 2023
e6457d9
feat: add quality of life improvements for building external account …
aeitzman Nov 27, 2023
448a8ec
fix: formatting
aeitzman Nov 27, 2023
59eb856
fix: add formatting
aeitzman Nov 27, 2023
f2ab1a2
Merge remote-tracking branch 'upstream/main' into fix_builders
aeitzman Nov 27, 2023
0495b7f
Adds @CanIgnoreReturnValue on new builder methods
aeitzman Nov 27, 2023
8d12e07
Merge remote-tracking branch 'upstream/main' into programmatic-auth
aeitzman Nov 27, 2023
a8b2f92
Change test for impersonated credentials
aeitzman Dec 1, 2023
616fb13
formatting
aeitzman Dec 1, 2023
b2552eb
adding id_token type
aeitzman Dec 1, 2023
d32e19c
Merge branch 'fix_builders' into programmatic-auth
aeitzman Dec 1, 2023
6726160
formatting
aeitzman Dec 1, 2023
2fc4f99
Update oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java
aeitzman Dec 5, 2023
e5a9c59
PR comments
aeitzman Dec 5, 2023
bfb83fa
Added header value constants
aeitzman Dec 5, 2023
6e7a975
Merge branch 'main' into programmatic-auth
lsirac Dec 6, 2023
164ac25
updating java doc
aeitzman Dec 7, 2023
a257e55
adding integration tests
aeitzman Dec 7, 2023
f09adfa
fix tests
aeitzman Dec 7, 2023
97946b3
fix tests, add javadoc, and format
aeitzman Dec 7, 2023
eb08391
PR review comments
aeitzman Dec 11, 2023
5ae2645
Update oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java
aeitzman Dec 12, 2023
61f6ae5
PR comments
aeitzman Dec 12, 2023
e43d708
changing to aws_region instead of region to clarify usage and keep re…
aeitzman Dec 13, 2023
bd4604f
Merge branch 'main' into programmatic-auth
aeitzman Dec 18, 2023
08bde82
Merge branch 'main' into programmatic-auth
lsirac Dec 20, 2023
4a22e08
Adding Aws Security Credential Providers
aeitzman Jan 8, 2024
f40743e
Merge remote-tracking branch 'upstream/main' into programmatic-auth
aeitzman Jan 8, 2024
f480b84
Adding identity pool providers
aeitzman Jan 8, 2024
188b803
PR comments
aeitzman Jan 9, 2024
e69108e
fix test
aeitzman Jan 9, 2024
4f3e253
refactoring to expose rename provider to supplier and expose it publicly
aeitzman Jan 10, 2024
c22a4e9
Merge branch 'main' into programmatic-auth
aeitzman Jan 10, 2024
dd659c4
formatting
aeitzman Jan 10, 2024
9cd8d96
Merge branch 'main' into programmatic-auth
lsirac Jan 12, 2024
8963d68
updating codeowners
aeitzman Jan 17, 2024
f4cadd2
Merge branch 'main' into programmatic-auth
lsirac Jan 18, 2024
20c2f7d
make subject token supplier interface public
aeitzman Jan 19, 2024
abd462b
Making AwsSecurityCredentials public and change name to sessionToken
aeitzman Jan 22, 2024
9835df7
lint
aeitzman Jan 22, 2024
d06eb36
Merge branch 'main' into programmatic-auth
aeitzman Jan 23, 2024
d59fb92
Merge remote-tracking branch 'upstream/main' into programmatic-auth
aeitzman Jan 25, 2024
4371463
fix tests
aeitzman Jan 25, 2024
5b21010
lint
aeitzman Jan 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
251 changes: 243 additions & 8 deletions oauth2_http/java/com/google/auth/oauth2/AwsCredentials.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@
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;
Expand All @@ -50,10 +52,13 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import javax.annotation.Nullable;

/**
* AWS credentials representing a third-party identity for calling Google APIs.
* AWS credentials representing a third-party identity for calling Google APIs. AWS Security
aeitzman marked this conversation as resolved.
Show resolved Hide resolved
* credentials are either sourced by calling EC2 metadata endpoints, environment variables, or a
* user provided supplier method.
*
* <p>By default, attempts to exchange the external credential for a GCP access token.
*/
Expand All @@ -66,17 +71,50 @@ public class AwsCredentials extends ExternalAccountCredentials {
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_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";

static final String AWS_METRICS_HEADER_VALUE = "aws";

private static final long serialVersionUID = -3670131891574618105L;

private final AwsCredentialSource awsCredentialSource;
@Nullable private final AwsCredentialSource awsCredentialSource;
@Nullable private final Supplier<AwsSecurityCredentials> awsSecurityCredentialsSupplier;
@Nullable private final String regionalCredentialVerificationUrlOverride;
@Nullable private final String region;

/** 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) {
lsirac marked this conversation as resolved.
Show resolved Hide resolved
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.");
}
// If user has provided a security credential supplier, use that to retrieve the AWS security
// credentials.
if (builder.awsSecurityCredentialsSupplier != null) {
this.awsSecurityCredentialsSupplier = builder.awsSecurityCredentialsSupplier;
if (builder.region == null) {
throw new IllegalArgumentException(
"A region must be specified when using an aws security credential supplier.");
}
this.awsCredentialSource = null;
aeitzman marked this conversation as resolved.
Show resolved Hide resolved
} else {
this.awsCredentialSource = (AwsCredentialSource) builder.credentialSource;
this.awsSecurityCredentialsSupplier = null;
}
this.region = builder.region;
this.regionalCredentialVerificationUrlOverride =
builder.regionalCredentialVerificationUrlOverride;
}

@Override
Expand Down Expand Up @@ -115,7 +153,7 @@ public String retrieveSubjectToken() throws IOException {
AwsRequestSigner.newBuilder(
credentials,
"POST",
awsCredentialSource.regionalCredentialVerificationUrl.replace("{region}", region),
this.getRegionalCredentialVerificationUrl().replace("{region}", region),
region)
.setAdditionalHeaders(headers)
.build();
Expand All @@ -132,7 +170,10 @@ public GoogleCredentials createScoped(Collection<String> newScopes) {

@Override
String getCredentialSourceType() {
return "aws";
if (this.awsSecurityCredentialsSupplier != null) {
return PROGRAMMATIC_AUTH_METRICS_HEADER_VALUE;
}
return AWS_METRICS_HEADER_VALUE;
}

private String retrieveResource(String url, String resourceName, Map<String, Object> headers)
Expand Down Expand Up @@ -184,8 +225,7 @@ private String buildSubjectToken(AwsRequestSignature signature)
token.put("method", signature.getHttpMethod());
token.put(
"url",
awsCredentialSource.regionalCredentialVerificationUrl.replace(
"{region}", signature.getRegion()));
this.getRegionalCredentialVerificationUrl().replace("{region}", signature.getRegion()));
return URLEncoder.encode(token.toString(), "UTF-8");
}

Expand Down Expand Up @@ -218,7 +258,9 @@ private boolean canRetrieveSecurityCredentialsFromEnvironment() {

@VisibleForTesting
boolean shouldUseMetadataServer() {
return !canRetrieveRegionFromEnvironment() || !canRetrieveSecurityCredentialsFromEnvironment();
return this.awsSecurityCredentialsSupplier == null
&& (!canRetrieveRegionFromEnvironment()
|| !canRetrieveSecurityCredentialsFromEnvironment());
}

@VisibleForTesting
Expand Down Expand Up @@ -258,6 +300,11 @@ Map<String, Object> createMetadataRequestHeaders(AwsCredentialSource awsCredenti

@VisibleForTesting
String getAwsRegion(Map<String, Object> metadataRequestHeaders) throws IOException {
// If user has provided a region string, return that instead of checking environment or metadata
// server.
if (this.region != null) {
return this.region;
}
String region;
if (canRetrieveRegionFromEnvironment()) {
// For AWS Lambda, the region is retrieved through the AWS_REGION environment variable.
Expand All @@ -283,6 +330,19 @@ String getAwsRegion(Map<String, Object> metadataRequestHeaders) throws IOExcepti
@VisibleForTesting
AwsSecurityCredentials getAwsSecurityCredentials(Map<String, Object> metadataRequestHeaders)
throws IOException {
// If this credential is using programmatic auth, call the user provided supplier.
if (this.awsSecurityCredentialsSupplier != null) {
try {
return this.awsSecurityCredentialsSupplier.get();
} catch (Throwable e) {
throw new GoogleAuthException(
/* isRetryable= */ false,
/* retryCount= */ 0,
"Error retrieving token from AWS security credentials supplier.",
e);
}
}

// Check environment variables for credentials first.
if (canRetrieveSecurityCredentialsFromEnvironment()) {
String accessKeyId = getEnvironmentProvider().getEnv(AWS_ACCESS_KEY_ID);
Expand Down Expand Up @@ -319,11 +379,37 @@ AwsSecurityCredentials getAwsSecurityCredentials(Map<String, Object> metadataReq
return new AwsSecurityCredentials(accessKeyId, secretAccessKey, token);
}

@VisibleForTesting
String getRegionalCredentialVerificationUrl() {
if (this.regionalCredentialVerificationUrlOverride != null) {
return this.regionalCredentialVerificationUrlOverride;
} else if (this.awsCredentialSource != null) {
return this.awsCredentialSource.regionalCredentialVerificationUrl;
} else {
return DEFAULT_REGIONAL_CREDENTIAL_VERIFICATION_URL;
}
}

@VisibleForTesting
String getEnv(String name) {
return System.getenv(name);
}

@Nullable
public String getRegion() {
return this.region;
}

@Nullable
public String getRegionalCredentialVerificationUrlOverride() {
return this.regionalCredentialVerificationUrlOverride;
}

@Nullable
public Supplier<AwsSecurityCredentials> getAwsSecurityCredentialsSupplier() {
return this.awsSecurityCredentialsSupplier;
}

private static GenericJson formatTokenHeaderForSts(String key, String value) {
// The GCP STS endpoint expects the headers to be formatted as:
// [
Expand All @@ -348,10 +434,159 @@ public static AwsCredentials.Builder newBuilder(AwsCredentials awsCredentials) {

public static class Builder extends ExternalAccountCredentials.Builder {

private Supplier<AwsSecurityCredentials> awsSecurityCredentialsSupplier;

private String region;

private String regionalCredentialVerificationUrlOverride;

Builder() {}

Builder(AwsCredentials credentials) {
super(credentials);
this.region = credentials.region;
this.awsSecurityCredentialsSupplier = credentials.awsSecurityCredentialsSupplier;
this.regionalCredentialVerificationUrlOverride =
credentials.regionalCredentialVerificationUrlOverride;
}

/**
* Sets the AWS security credentials supplier. The supplier should return a valid {@code
* AwsSecurityCredentials} object. An AWS region also is required when using a supplier.
*
* @param awsSecurityCredentialsSupplier the supplier method to be called.
* @return this {@code Builder} object
*/
@CanIgnoreReturnValue
public Builder setAwsSecurityCredentialsSupplier(
Supplier<AwsSecurityCredentials> awsSecurityCredentialsSupplier) {
this.awsSecurityCredentialsSupplier = awsSecurityCredentialsSupplier;
return this;
}

/**
* Sets the AWS region. Required when using an AWS Security Credentials Supplier. If set, will
* override any region obtained via environment variables or the metadata endpoint.
*
* @param region the aws region to set.
* @return this {@code Builder} object
*/
@CanIgnoreReturnValue
public Builder setRegion(String region) {
this.region = region;
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<String> scopes) {
super.setScopes(scopes);
return this;
}

@CanIgnoreReturnValue
public Builder setWorkforcePoolUserProject(String workforcePoolUserProject) {
super.setWorkforcePoolUserProject(workforcePoolUserProject);
return this;
}

@CanIgnoreReturnValue
public Builder setServiceAccountImpersonationOptions(Map<String, Object> 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
Expand Down
Loading