-
Notifications
You must be signed in to change notification settings - Fork 258
feat: Add TrustBoundaries support for ExternalAccounts. #1836
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
base: feat-tb-sa
Are you sure you want to change the base?
Changes from all commits
d305b2f
c17e26d
2b486bd
63f508b
5bddb2f
9fb3547
dab6d3b
d58147c
283d8b5
b4e5199
458bad4
8cd8f2f
f737866
fe772d8
1efce2d
6eb249c
2071071
f013aa5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,7 +31,9 @@ | |
|
|
||
| package com.google.auth.oauth2; | ||
|
|
||
| import static com.google.auth.oauth2.OAuth2Utils.IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_WORKFORCE_POOL; | ||
| import static com.google.auth.oauth2.OAuth2Utils.JSON_FACTORY; | ||
| import static com.google.auth.oauth2.OAuth2Utils.WORKFORCE_AUDIENCE_PATTERN; | ||
| import static com.google.common.base.Preconditions.checkNotNull; | ||
|
|
||
| import com.google.api.client.http.GenericUrl; | ||
|
|
@@ -44,6 +46,7 @@ | |
| import com.google.api.client.json.JsonObjectParser; | ||
| import com.google.api.client.util.GenericData; | ||
| import com.google.api.client.util.Preconditions; | ||
| import com.google.api.core.InternalApi; | ||
| import com.google.auth.http.HttpTransportFactory; | ||
| import com.google.common.base.MoreObjects; | ||
| import com.google.common.io.BaseEncoding; | ||
|
|
@@ -55,6 +58,7 @@ | |
| import java.util.Date; | ||
| import java.util.Map; | ||
| import java.util.Objects; | ||
| import java.util.regex.Matcher; | ||
| import javax.annotation.Nullable; | ||
|
|
||
| /** | ||
|
|
@@ -75,12 +79,12 @@ | |
| * } | ||
| * </pre> | ||
| */ | ||
| public class ExternalAccountAuthorizedUserCredentials extends GoogleCredentials { | ||
| public class ExternalAccountAuthorizedUserCredentials extends GoogleCredentials | ||
| implements TrustBoundaryProvider { | ||
|
|
||
| private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. "; | ||
|
|
||
| private static final long serialVersionUID = -2181779590486283287L; | ||
|
|
||
| private final String transportFactoryClassName; | ||
| private final String audience; | ||
| private final String tokenUrl; | ||
|
|
@@ -210,10 +214,27 @@ public AccessToken refreshAccessToken() throws IOException { | |
| this.refreshToken = refreshToken; | ||
| } | ||
|
|
||
| return AccessToken.newBuilder() | ||
| .setExpirationTime(expiresAtMilliseconds) | ||
| .setTokenValue(accessToken) | ||
| .build(); | ||
| AccessToken newAccessToken = | ||
| AccessToken.newBuilder() | ||
| .setExpirationTime(expiresAtMilliseconds) | ||
| .setTokenValue(accessToken) | ||
| .build(); | ||
|
|
||
| refreshTrustBoundary(newAccessToken, transportFactory); | ||
| return newAccessToken; | ||
| } | ||
|
|
||
| @InternalApi | ||
| @Override | ||
| public String getTrustBoundaryUrl() throws IOException { | ||
| Matcher matcher = WORKFORCE_AUDIENCE_PATTERN.matcher(getAudience()); | ||
| if (!matcher.matches()) { | ||
| throw new IllegalStateException( | ||
| "The provided audience is not in the correct format for a workforce pool."); | ||
|
Comment on lines
+232
to
+233
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: If possible, can we link to a public offical doc that has the format for workforce pool? |
||
| } | ||
| String poolId = matcher.group("pool"); | ||
| return String.format( | ||
| IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_WORKFORCE_POOL, getUniverseDomain(), poolId); | ||
| } | ||
|
|
||
| @Nullable | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,12 +31,15 @@ | |
|
|
||
| package com.google.auth.oauth2; | ||
|
|
||
| import static com.google.auth.oauth2.OAuth2Utils.WORKFORCE_AUDIENCE_PATTERN; | ||
| import static com.google.auth.oauth2.OAuth2Utils.WORKLOAD_AUDIENCE_PATTERN; | ||
| import static com.google.common.base.Preconditions.checkNotNull; | ||
|
|
||
| import com.google.api.client.http.HttpHeaders; | ||
| import com.google.api.client.json.GenericJson; | ||
| import com.google.api.client.json.JsonObjectParser; | ||
| import com.google.api.client.util.Data; | ||
| import com.google.api.core.InternalApi; | ||
| import com.google.auth.RequestMetadataCallback; | ||
| import com.google.auth.http.HttpTransportFactory; | ||
| import com.google.common.base.MoreObjects; | ||
|
|
@@ -55,6 +58,7 @@ | |
| import java.util.Locale; | ||
| import java.util.Map; | ||
| import java.util.concurrent.Executor; | ||
| import java.util.regex.Matcher; | ||
| import java.util.regex.Pattern; | ||
| import javax.annotation.Nullable; | ||
|
|
||
|
|
@@ -64,7 +68,8 @@ | |
| * <p>Handles initializing external credentials, calls to the Security Token Service, and service | ||
| * account impersonation. | ||
| */ | ||
| public abstract class ExternalAccountCredentials extends GoogleCredentials { | ||
| public abstract class ExternalAccountCredentials extends GoogleCredentials | ||
| implements TrustBoundaryProvider { | ||
|
|
||
| private static final long serialVersionUID = 8049126194174465023L; | ||
|
|
||
|
|
@@ -527,7 +532,11 @@ protected AccessToken exchangeExternalCredentialForAccessToken( | |
| this.impersonatedCredentials = this.buildImpersonatedCredentials(); | ||
| } | ||
| if (this.impersonatedCredentials != null) { | ||
| return this.impersonatedCredentials.refreshAccessToken(); | ||
| AccessToken accessToken = this.impersonatedCredentials.refreshAccessToken(); | ||
| // After the impersonated credential refreshes, its trust boundary is | ||
| // also refreshed. That is the trust boundary we will use. | ||
| this.trustBoundary = this.impersonatedCredentials.getTrustBoundary(); | ||
| return accessToken; | ||
| } | ||
|
|
||
| StsRequestHandler.Builder requestHandler = | ||
|
|
@@ -556,7 +565,9 @@ protected AccessToken exchangeExternalCredentialForAccessToken( | |
| } | ||
|
|
||
| StsTokenExchangeResponse response = requestHandler.build().exchangeToken(); | ||
| return response.getAccessToken(); | ||
| AccessToken accessToken = response.getAccessToken(); | ||
| refreshTrustBoundary(accessToken, transportFactory); | ||
| return accessToken; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -613,6 +624,33 @@ public String getServiceAccountEmail() { | |
| return ImpersonatedCredentials.extractTargetPrincipal(serviceAccountImpersonationUrl); | ||
| } | ||
|
|
||
| @InternalApi | ||
| @Override | ||
| public String getTrustBoundaryUrl() { | ||
| Matcher workforceMatcher = WORKFORCE_AUDIENCE_PATTERN.matcher(getAudience()); | ||
| if (workforceMatcher.matches()) { | ||
| String poolId = workforceMatcher.group("pool"); | ||
| return String.format( | ||
| OAuth2Utils.IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_WORKFORCE_POOL, | ||
| getUniverseDomain(), | ||
| poolId); | ||
| } | ||
|
|
||
| Matcher workloadMatcher = WORKLOAD_AUDIENCE_PATTERN.matcher(getAudience()); | ||
| if (workloadMatcher.matches()) { | ||
| String projectNumber = workloadMatcher.group("project"); | ||
| String poolId = workloadMatcher.group("pool"); | ||
| return String.format( | ||
| OAuth2Utils.IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_WORKLOAD_POOL, | ||
| getUniverseDomain(), | ||
| projectNumber, | ||
| poolId); | ||
| } | ||
|
|
||
| throw new IllegalStateException( | ||
| "The provided audience is not in a valid format for either a workload identity pool or a workforce pool."); | ||
| } | ||
|
Comment on lines
+650
to
+652
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: If possible, is there an official doc that we can link to with the format for workload pool and workforce pool? |
||
|
|
||
| @Nullable | ||
| public String getClientId() { | ||
| return clientId; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -108,7 +108,7 @@ String getFileType() { | |
| private final String universeDomain; | ||
| private final boolean isExplicitUniverseDomain; | ||
|
|
||
| private TrustBoundary trustBoundary; | ||
| TrustBoundary trustBoundary; | ||
|
|
||
| protected final String quotaProjectId; | ||
|
|
||
|
|
@@ -339,6 +339,10 @@ TrustBoundary getTrustBoundary() { | |
| return trustBoundary; | ||
| } | ||
|
|
||
| protected void setTrustBoundary(TrustBoundary trustBoundary) { | ||
| this.trustBoundary = trustBoundary; | ||
| } | ||
|
Comment on lines
+342
to
+344
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is this still needed? |
||
|
|
||
| /** | ||
| * Refreshes the trust boundary by making a call to the trust boundary URL. | ||
| * | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1399,4 +1399,34 @@ public AwsSecurityCredentials getCredentials(ExternalAccountSupplierContext cont | |
| return credentials; | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void testRefresh_trustBoundarySuccess() throws IOException { | ||
| TestEnvironmentProvider environmentProvider = new TestEnvironmentProvider(); | ||
| TrustBoundary.setEnvironmentProviderForTest(environmentProvider); | ||
| environmentProvider.setEnv("GOOGLE_AUTH_TRUST_BOUNDARY_ENABLE_EXPERIMENT", "1"); | ||
|
|
||
| MockExternalAccountCredentialsTransportFactory transportFactory = | ||
| new MockExternalAccountCredentialsTransportFactory(); | ||
|
|
||
| AwsSecurityCredentialsSupplier supplier = | ||
| new TestAwsSecurityCredentialsSupplier("test", programmaticAwsCreds, null, null); | ||
|
|
||
| AwsCredentials awsCredential = | ||
| AwsCredentials.newBuilder() | ||
| .setAwsSecurityCredentialsSupplier(supplier) | ||
| .setHttpTransportFactory(transportFactory) | ||
| .setAudience( | ||
| "//iam.googleapis.com/projects/12345/locations/global/workloadIdentityPools/pool/providers/provider") | ||
| .setTokenUrl(STS_URL) | ||
| .setSubjectTokenType("subjectTokenType") | ||
| .build(); | ||
|
|
||
| awsCredential.refreshAccessToken(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I think most of the other test classes use |
||
|
|
||
| TrustBoundary trustBoundary = awsCredential.getTrustBoundary(); | ||
| assertNotNull(trustBoundary); | ||
| assertEquals(TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, trustBoundary.getEncodedLocations()); | ||
| TrustBoundary.setEnvironmentProviderForTest(null); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,7 @@ | |
|
|
||
| import static com.google.auth.oauth2.ComputeEngineCredentials.METADATA_RESPONSE_EMPTY_CONTENT_ERROR_MESSAGE; | ||
| import static com.google.auth.oauth2.ImpersonatedCredentialsTest.SA_CLIENT_EMAIL; | ||
| import static com.google.auth.oauth2.TrustBoundary.TRUST_BOUNDARY_KEY; | ||
| import static org.junit.Assert.assertArrayEquals; | ||
| import static org.junit.Assert.assertEquals; | ||
| import static org.junit.Assert.assertFalse; | ||
|
|
@@ -1159,15 +1160,17 @@ public void refresh_trustBoundarySuccess() throws IOException { | |
| String defaultAccountEmail = "[email protected]"; | ||
| MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory(); | ||
| TrustBoundary trustBoundary = | ||
| new TrustBoundary("0x80000", Collections.singletonList("us-central1")); | ||
| new TrustBoundary( | ||
| TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION, TestUtils.TRUST_BOUNDARY_LOCATIONS); | ||
| transportFactory.transport.setTrustBoundary(trustBoundary); | ||
| transportFactory.transport.setServiceAccountEmail(defaultAccountEmail); | ||
|
|
||
| ComputeEngineCredentials credentials = | ||
| ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build(); | ||
|
|
||
| Map<String, List<String>> headers = credentials.getRequestMetadata(); | ||
| assertEquals(headers.get("x-allowed-locations"), Arrays.asList("0x80000")); | ||
| assertEquals( | ||
| headers.get(TRUST_BOUNDARY_KEY), Arrays.asList(TestUtils.TRUST_BOUNDARY_ENCODED_LOCATION)); | ||
| } | ||
|
|
||
| @Test | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.