Skip to content

Commit b721e43

Browse files
authored
feat(auth): Add support for Regional Access Boundaries (#13499)
The Regional Access Boundaries PR to main. Contains all the changes merged to the feature branch rebased on top of main. P.S. Opening the PR directly to main as feature branch regional-access-boundaries has drifted from main and opening a rebased-PR to the feature branch shows 5k+ lines of code diff.
1 parent a7d8f75 commit b721e43

31 files changed

Lines changed: 2916 additions & 50 deletions

google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import com.google.api.client.http.HttpStatusCodes;
4242
import com.google.api.client.json.JsonObjectParser;
4343
import com.google.api.client.util.GenericData;
44+
import com.google.api.core.InternalApi;
4445
import com.google.auth.CredentialTypeForMetrics;
4546
import com.google.auth.Credentials;
4647
import com.google.auth.Retryable;
@@ -71,6 +72,7 @@
7172
import java.util.Objects;
7273
import java.util.logging.Level;
7374
import java.util.logging.Logger;
75+
import java.util.regex.Pattern;
7476

7577
/**
7678
* OAuth2 credentials representing the built-in service account for a Google Compute Engine VM.
@@ -80,7 +82,7 @@
8082
* <p>These credentials use the IAM API to sign data. See {@link #sign(byte[])} for more details.
8183
*/
8284
public class ComputeEngineCredentials extends GoogleCredentials
83-
implements ServiceAccountSigner, IdTokenProvider {
85+
implements ServiceAccountSigner, IdTokenProvider, RegionalAccessBoundaryProvider {
8486

8587
static final String METADATA_RESPONSE_EMPTY_CONTENT_ERROR_MESSAGE =
8688
"Empty content from metadata token server request.";
@@ -116,6 +118,7 @@ public class ComputeEngineCredentials extends GoogleCredentials
116118

117119
private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. ";
118120
private static final String PARSE_ERROR_ACCOUNT = "Error parsing service account response. ";
121+
private static final Pattern EMAIL_PATTERN = Pattern.compile("^[^@]+@[^@]+\\.[^@]+$");
119122
private static final long serialVersionUID = -4113476462526554235L;
120123

121124
private final String transportFactoryClassName;
@@ -454,7 +457,6 @@ public AccessToken refreshAccessToken() throws IOException {
454457
int expiresInSeconds =
455458
OAuth2Utils.validateInt32(responseData, "expires_in", PARSE_ERROR_PREFIX);
456459
long expiresAtMilliseconds = clock.currentTimeMillis() + expiresInSeconds * 1000;
457-
458460
return new AccessToken(accessToken, new Date(expiresAtMilliseconds));
459461
}
460462

@@ -779,6 +781,11 @@ public static Builder newBuilder() {
779781
*
780782
* @throws RuntimeException if the default service account cannot be read
781783
*/
784+
@Override
785+
HttpTransportFactory getTransportFactory() {
786+
return transportFactory;
787+
}
788+
782789
@Override
783790
// todo(#314) getAccount should not throw a RuntimeException
784791
public String getAccount() {
@@ -792,6 +799,24 @@ public String getAccount() {
792799
return principal;
793800
}
794801

802+
@InternalApi
803+
@Override
804+
public String getRegionalAccessBoundaryUrl() throws IOException {
805+
String account = getAccount();
806+
// The MDS may return a non-email value for the account and we should skip RAB refresh in that
807+
// scenario.
808+
if (account == null || !EMAIL_PATTERN.matcher(account).matches()) {
809+
LoggingUtils.log(
810+
LOGGER_PROVIDER,
811+
Level.INFO,
812+
Collections.emptyMap(),
813+
"Unable to retrieve this instance's email and will skip the regional request routing. Proceeding with request");
814+
return null;
815+
}
816+
return String.format(
817+
OAuth2Utils.IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_SERVICE_ACCOUNT, account);
818+
}
819+
795820
/**
796821
* Signs the provided bytes using the private key associated with the service account.
797822
*

google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/ExternalAccountAuthorizedUserCredentials.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@
3131

3232
package com.google.auth.oauth2;
3333

34+
import static com.google.auth.oauth2.OAuth2Utils.IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_WORKFORCE_POOL;
3435
import static com.google.auth.oauth2.OAuth2Utils.JSON_FACTORY;
36+
import static com.google.auth.oauth2.OAuth2Utils.WORKFORCE_AUDIENCE_PATTERN;
3537

3638
import com.google.api.client.http.GenericUrl;
3739
import com.google.api.client.http.HttpHeaders;
@@ -43,6 +45,7 @@
4345
import com.google.api.client.json.JsonObjectParser;
4446
import com.google.api.client.util.GenericData;
4547
import com.google.api.client.util.Preconditions;
48+
import com.google.api.core.InternalApi;
4649
import com.google.auth.http.HttpTransportFactory;
4750
import com.google.common.base.MoreObjects;
4851
import com.google.common.io.BaseEncoding;
@@ -54,6 +57,7 @@
5457
import java.util.Date;
5558
import java.util.Map;
5659
import java.util.Objects;
60+
import java.util.regex.Matcher;
5761
import javax.annotation.Nullable;
5862

5963
/**
@@ -74,7 +78,8 @@
7478
* }
7579
* </pre>
7680
*/
77-
public class ExternalAccountAuthorizedUserCredentials extends GoogleCredentials {
81+
public class ExternalAccountAuthorizedUserCredentials extends GoogleCredentials
82+
implements RegionalAccessBoundaryProvider {
7883
private static final LoggerProvider LOGGER_PROVIDER =
7984
LoggerProvider.forClazz(ExternalAccountAuthorizedUserCredentials.class);
8085

@@ -229,6 +234,29 @@ public AccessToken refreshAccessToken() throws IOException {
229234
.build();
230235
}
231236

237+
@InternalApi
238+
@Override
239+
public String getRegionalAccessBoundaryUrl() throws IOException {
240+
String audience = getAudience();
241+
if (audience == null) {
242+
throw new IllegalStateException(
243+
"The audience is null, which is not in the correct format for a workforce pool.");
244+
}
245+
Matcher matcher = WORKFORCE_AUDIENCE_PATTERN.matcher(audience);
246+
if (!matcher.matches()) {
247+
throw new IllegalStateException(
248+
"The provided audience is not in the correct format for a workforce pool. "
249+
+ "Refer: https://docs.cloud.google.com/iam/docs/principal-identifiers");
250+
}
251+
String poolId = matcher.group("pool");
252+
return String.format(IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_WORKFORCE_POOL, poolId);
253+
}
254+
255+
@Override
256+
HttpTransportFactory getTransportFactory() {
257+
return transportFactory;
258+
}
259+
232260
@Nullable
233261
public String getAudience() {
234262
return audience;

google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/ExternalAccountCredentials.java

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,14 @@
3131

3232
package com.google.auth.oauth2;
3333

34+
import static com.google.auth.oauth2.OAuth2Utils.WORKFORCE_AUDIENCE_PATTERN;
35+
import static com.google.auth.oauth2.OAuth2Utils.WORKLOAD_AUDIENCE_PATTERN;
3436
import static com.google.common.base.Preconditions.checkNotNull;
3537

3638
import com.google.api.client.http.HttpHeaders;
3739
import com.google.api.client.json.GenericJson;
3840
import com.google.api.client.util.Data;
41+
import com.google.api.core.InternalApi;
3942
import com.google.auth.RequestMetadataCallback;
4043
import com.google.auth.http.HttpTransportFactory;
4144
import com.google.common.base.MoreObjects;
@@ -54,6 +57,7 @@
5457
import java.util.Locale;
5558
import java.util.Map;
5659
import java.util.concurrent.Executor;
60+
import java.util.regex.Matcher;
5761
import java.util.regex.Pattern;
5862
import javax.annotation.Nullable;
5963

@@ -63,7 +67,8 @@
6367
* <p>Handles initializing external credentials, calls to the Security Token Service, and service
6468
* account impersonation.
6569
*/
66-
public abstract class ExternalAccountCredentials extends GoogleCredentials {
70+
public abstract class ExternalAccountCredentials extends GoogleCredentials
71+
implements RegionalAccessBoundaryProvider {
6772

6873
private static final long serialVersionUID = 8049126194174465023L;
6974

@@ -577,6 +582,11 @@ protected AccessToken exchangeExternalCredentialForAccessToken(
577582
*/
578583
public abstract String retrieveSubjectToken() throws IOException;
579584

585+
@Override
586+
HttpTransportFactory getTransportFactory() {
587+
return transportFactory;
588+
}
589+
580590
public String getAudience() {
581591
return audience;
582592
}
@@ -620,6 +630,43 @@ public String getServiceAccountEmail() {
620630
return ImpersonatedCredentials.extractTargetPrincipal(serviceAccountImpersonationUrl);
621631
}
622632

633+
@InternalApi
634+
@Override
635+
public String getRegionalAccessBoundaryUrl() throws IOException {
636+
if (getServiceAccountEmail() != null) {
637+
return String.format(
638+
OAuth2Utils.IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_SERVICE_ACCOUNT,
639+
getServiceAccountEmail());
640+
}
641+
642+
String audience = getAudience();
643+
if (audience == null) {
644+
throw new IllegalStateException(
645+
"The audience is null, which is not in a valid format for either a workload identity pool or a workforce pool.");
646+
}
647+
648+
Matcher workforceMatcher = WORKFORCE_AUDIENCE_PATTERN.matcher(audience);
649+
if (workforceMatcher.matches()) {
650+
String poolId = workforceMatcher.group("pool");
651+
return String.format(
652+
OAuth2Utils.IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_WORKFORCE_POOL, poolId);
653+
}
654+
655+
Matcher workloadMatcher = WORKLOAD_AUDIENCE_PATTERN.matcher(audience);
656+
if (workloadMatcher.matches()) {
657+
String projectNumber = workloadMatcher.group("project");
658+
String poolId = workloadMatcher.group("pool");
659+
return String.format(
660+
OAuth2Utils.IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_WORKLOAD_POOL,
661+
projectNumber,
662+
poolId);
663+
}
664+
665+
throw new IllegalStateException(
666+
"The provided audience is not in a valid format for either a workload identity pool or a workforce pool."
667+
+ " Refer: https://docs.cloud.google.com/iam/docs/principal-identifiers");
668+
}
669+
623670
@Nullable
624671
public String getClientId() {
625672
return clientId;

0 commit comments

Comments
 (0)