Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -385,8 +385,11 @@ public AccessToken refreshAccessToken() throws IOException {
int expiresInSeconds =
OAuth2Utils.validateInt32(responseData, "expires_in", PARSE_ERROR_PREFIX);
long expiresAtMilliseconds = clock.currentTimeMillis() + expiresInSeconds * 1000;
AccessToken newAccessToken = new AccessToken(accessToken, new Date(expiresAtMilliseconds));

return new AccessToken(accessToken, new Date(expiresAtMilliseconds));
refreshTrustBoundary(newAccessToken, getTrustBoundaryUrl(), transportFactory);

return newAccessToken;
}

/**
Expand Down Expand Up @@ -703,6 +706,18 @@ public String getAccount() {
return principal;
}

@Override
Boolean supportsTrustBoundary() {
return true;
}

String getTrustBoundaryUrl() throws IOException {
return String.format(
OAuth2Utils.IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_SERVICE_ACCOUNT,
getUniverseDomain(),
getAccount());
}

/**
* Signs the provided bytes using the private key associated with the service account.
*
Expand Down
85 changes: 81 additions & 4 deletions oauth2_http/java/com/google/auth/oauth2/GoogleCredentials.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.util.Preconditions;
import com.google.api.core.InternalApi;
import com.google.api.core.ObsoleteApi;
import com.google.auth.Credentials;
import com.google.auth.http.HttpTransportFactory;
Expand Down Expand Up @@ -107,6 +108,8 @@ String getFileType() {
private final String universeDomain;
private final boolean isExplicitUniverseDomain;

private TrustBoundary trustBoundary;

protected final String quotaProjectId;

private static final DefaultCredentialsProvider defaultCredentialsProvider =
Expand Down Expand Up @@ -331,6 +334,74 @@ public GoogleCredentials createWithQuotaProject(String quotaProject) {
return this.toBuilder().setQuotaProjectId(quotaProject).build();
}

@VisibleForTesting
TrustBoundary getTrustBoundary() {
return trustBoundary;
}

/**
* Returns whether the credentials support trust boundary.
*
* @return {@code true} if the credentials support trust boundary, {@code false} otherwise.
*/
Boolean supportsTrustBoundary() {
return false;
}

/**
* Refreshes the trust boundary by making a call to the trust boundary URL.
*
* <p>This method is for internal use only and should not be called by users directly. It is used
* to enforce security policies by ensuring that the credentials used to access Google Cloud APIs
* are not used outside a trusted environment.
*
* @param newAccessToken The new access token to be used for the refresh.
* @param trustBoundaryUrl The URL of the trust boundary service.
* @param transportFactory The HTTP transport factory to be used for the refresh.
* @throws IOException If the refresh fails and no cached value is available.
*/
@InternalApi
void refreshTrustBoundary(
AccessToken newAccessToken, String trustBoundaryUrl, HttpTransportFactory transportFactory)
throws IOException {

if (!supportsTrustBoundary()
|| !TrustBoundary.isTrustBoundaryEnabled()
|| !isDefaultUniverseDomain()) {
return;
}

TrustBoundary cachedTrustBoundary;

synchronized (lock) {
// Do not refresh if the cached value is already NO_OP.
if (trustBoundary != null && trustBoundary.isNoOp()) {
return;
}
cachedTrustBoundary = trustBoundary;
}

TrustBoundary newTrustBoundary;
try {
newTrustBoundary =
TrustBoundary.refresh(
transportFactory, trustBoundaryUrl, newAccessToken, cachedTrustBoundary);
} catch (IOException e) {
// If refresh fails, check for a cached value.
if (cachedTrustBoundary == null) {
// No cached value, so fail hard.
throw new IOException(
"Failed to refresh trust boundary and no cached value is available.", e);
}
return;
}

// A lock is required to safely update the shared field.
synchronized (lock) {
trustBoundary = newTrustBoundary;
}
}

/**
* Gets the universe domain for the credential.
*
Expand Down Expand Up @@ -362,7 +433,7 @@ protected boolean isExplicitUniverseDomain() {
* @return true if universe domain equals to {@link Credentials#GOOGLE_DEFAULT_UNIVERSE}, false
* otherwise
*/
boolean isDefaultUniverseDomain() throws IOException {
public boolean isDefaultUniverseDomain() throws IOException {
return getUniverseDomain().equals(Credentials.GOOGLE_DEFAULT_UNIVERSE);
}

Expand All @@ -384,12 +455,18 @@ static Map<String, List<String>> addQuotaProjectIdToRequestMetadata(

@Override
protected Map<String, List<String>> getAdditionalHeaders() {
Map<String, List<String>> headers = super.getAdditionalHeaders();
Map<String, List<String>> headers = new HashMap<>(super.getAdditionalHeaders());
String quotaProjectId = this.getQuotaProjectId();
if (quotaProjectId != null) {
return addQuotaProjectIdToRequestMetadata(quotaProjectId, headers);
headers.put(QUOTA_PROJECT_ID_HEADER_KEY, Collections.singletonList(quotaProjectId));
}
return headers;

if (this.trustBoundary != null) {
String headerValue = trustBoundary.isNoOp() ? "" : trustBoundary.getEncodedLocations();
headers.put(TrustBoundary.TRUST_BOUNDARY_KEY, Collections.singletonList(headerValue));
}

return Collections.unmodifiableMap(headers);
}

/** Default constructor. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,18 @@ public GoogleCredentials getSourceCredentials() {
return sourceCredentials;
}

@Override
Boolean supportsTrustBoundary() {
return true;
}

String getTrustBoundaryUrl() throws IOException {
return String.format(
OAuth2Utils.IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_SERVICE_ACCOUNT,
getUniverseDomain(),
getAccount());
}

int getLifetime() {
return this.lifetime;
}
Expand Down Expand Up @@ -587,7 +599,11 @@ public AccessToken refreshAccessToken() throws IOException {
format.setCalendar(calendar);
try {
Date date = format.parse(expireTime);
return new AccessToken(accessToken, date);
AccessToken newAccessToken = new AccessToken(accessToken, date);

refreshTrustBoundary(newAccessToken, getTrustBoundaryUrl(), transportFactory);

return newAccessToken;
} catch (ParseException pe) {
throw new IOException("Error parsing expireTime: " + pe.getMessage());
}
Expand Down
10 changes: 3 additions & 7 deletions oauth2_http/java/com/google/auth/oauth2/OAuth2Credentials.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -264,11 +263,8 @@ private AsyncRefreshResult getOrCreateRefreshTask() {

final ListenableFutureTask<OAuthValue> task =
ListenableFutureTask.create(
new Callable<OAuthValue>() {
@Override
public OAuthValue call() throws Exception {
return OAuthValue.create(refreshAccessToken(), getAdditionalHeaders());
}
() -> {
return OAuthValue.create(refreshAccessToken(), getAdditionalHeaders());
});

refreshTask = new RefreshTask(task, new RefreshTaskListener(task));
Expand Down Expand Up @@ -373,7 +369,7 @@ public AccessToken refreshAccessToken() throws IOException {
/**
* Provide additional headers to return as request metadata.
*
* @return additional headers
* @return additional headers.
*/
protected Map<String, List<String>> getAdditionalHeaders() {
return EMPTY_EXTRA_HEADERS;
Expand Down
3 changes: 3 additions & 0 deletions oauth2_http/java/com/google/auth/oauth2/OAuth2Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ public class OAuth2Utils {
static final URI TOKEN_SERVER_URI = URI.create("https://oauth2.googleapis.com/token");

static final URI TOKEN_REVOKE_URI = URI.create("https://oauth2.googleapis.com/revoke");

public static final String IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_SERVICE_ACCOUNT =
"https://iamcredentials.%s/v1/projects/-/serviceAccounts/%s/allowedLocations";
static final URI USER_AUTH_URI = URI.create("https://accounts.google.com/o/oauth2/auth");

static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,11 @@ public AccessToken refreshAccessToken() throws IOException {
int expiresInSeconds =
OAuth2Utils.validateInt32(responseData, "expires_in", PARSE_ERROR_PREFIX);
long expiresAtMilliseconds = clock.currentTimeMillis() + expiresInSeconds * 1000L;
return new AccessToken(accessToken, new Date(expiresAtMilliseconds));
AccessToken newAccessToken = new AccessToken(accessToken, new Date(expiresAtMilliseconds));

refreshTrustBoundary(newAccessToken, getTrustBoundaryUrl(), transportFactory);

return newAccessToken;
}

/**
Expand Down Expand Up @@ -819,6 +823,18 @@ public boolean getUseJwtAccessWithScope() {
return useJwtAccessWithScope;
}

@Override
Boolean supportsTrustBoundary() {
return true;
}

String getTrustBoundaryUrl() throws IOException {
return String.format(
OAuth2Utils.IAM_CREDENTIALS_ALLOWED_LOCATIONS_URL_FORMAT_SERVICE_ACCOUNT,
getUniverseDomain(),
getAccount());
}

@VisibleForTesting
JwtCredentials getSelfSignedJwtCredentialsWithScope() {
return selfSignedJwtCredentialsWithScope;
Expand Down
Loading
Loading