Skip to content

Commit

Permalink
feat: adds universe domain support for compute credentials (#1346)
Browse files Browse the repository at this point in the history
* feat: adds universe domain suppor for compute credentials

* fix: make universe domain field access synchronized

* fix: more tests for getUniverseDomain and updates to  metadata mock

* fix: new create() signature with universe_domain and more tests

Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
  • Loading branch information
TimurSadykov and gcf-owl-bot[bot] committed Jan 6, 2024
1 parent ab87281 commit 7e26861
Show file tree
Hide file tree
Showing 8 changed files with 623 additions and 194 deletions.
165 changes: 135 additions & 30 deletions oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,13 @@
import com.google.api.client.http.HttpStatusCodes;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.util.GenericData;
import com.google.auth.Credentials;
import com.google.auth.Retryable;
import com.google.auth.ServiceAccountSigner;
import com.google.auth.http.HttpTransportFactory;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.MoreObjects.ToStringHelper;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.BufferedReader;
Expand Down Expand Up @@ -79,12 +81,12 @@ public class ComputeEngineCredentials extends GoogleCredentials
implements ServiceAccountSigner, IdTokenProvider {

// Decrease timing margins on GCE.
// This is needed because GCE VMs maintain their own OAuth cache that expires T-5mins, attempting
// This is needed because GCE VMs maintain their own OAuth cache that expires T-4 mins, attempting
// to refresh a token before then, will yield the same stale token. To enable pre-emptive
// refreshes, the margins must be shortened. This shouldn't cause problems since the clock skew
// on the VM and metadata proxy should be non-existent.
static final Duration COMPUTE_EXPIRATION_MARGIN = Duration.ofMinutes(3);
static final Duration COMPUTE_REFRESH_MARGIN = Duration.ofMinutes(4);
static final Duration COMPUTE_REFRESH_MARGIN = Duration.ofMinutes(3).plusSeconds(45);

private static final Logger LOGGER = Logger.getLogger(ComputeEngineCredentials.class.getName());

Expand Down Expand Up @@ -120,34 +122,31 @@ public class ComputeEngineCredentials extends GoogleCredentials
private transient HttpTransportFactory transportFactory;
private transient String serviceAccountEmail;

private String universeDomainFromMetadata = null;

/**
* Constructor with overridden transport.
* An internal constructor
*
* @param transportFactory HTTP transport factory, creates the transport used to get access
* tokens.
* @param scopes scope strings for the APIs to be called. May be null or an empty collection.
* @param defaultScopes default scope strings for the APIs to be called. May be null or an empty
* collection. Default scopes are ignored if scopes are provided.
* @param builder A builder for {@link ComputeEngineCredentials} See {@link
* ComputeEngineCredentials.Builder}
*/
private ComputeEngineCredentials(
HttpTransportFactory transportFactory,
Collection<String> scopes,
Collection<String> defaultScopes) {
super(/* accessToken= */ null, COMPUTE_REFRESH_MARGIN, COMPUTE_EXPIRATION_MARGIN);
private ComputeEngineCredentials(ComputeEngineCredentials.Builder builder) {
super(builder);

this.transportFactory =
firstNonNull(
transportFactory,
builder.getHttpTransportFactory(),
getFromServiceLoader(HttpTransportFactory.class, OAuth2Utils.HTTP_TRANSPORT_FACTORY));
this.transportFactoryClassName = this.transportFactory.getClass().getName();
// Use defaultScopes only when scopes don't exist.
if (scopes == null || scopes.isEmpty()) {
scopes = defaultScopes;
Collection<String> scopesToUse = builder.scopes;
if (scopesToUse == null || scopesToUse.isEmpty()) {
scopesToUse = builder.getDefaultScopes();
}
if (scopes == null) {
if (scopesToUse == null) {
this.scopes = ImmutableSet.<String>of();
} else {
List<String> scopeList = new ArrayList<String>(scopes);
List<String> scopeList = new ArrayList<String>(scopesToUse);
scopeList.removeAll(Arrays.asList("", null));
this.scopes = ImmutableSet.<String>copyOf(scopeList);
}
Expand All @@ -156,14 +155,21 @@ private ComputeEngineCredentials(
/** Clones the compute engine account with the specified scopes. */
@Override
public GoogleCredentials createScoped(Collection<String> newScopes) {
return new ComputeEngineCredentials(this.transportFactory, newScopes, null);
ComputeEngineCredentials.Builder builder =
this.toBuilder().setHttpTransportFactory(transportFactory).setScopes(newScopes);
return new ComputeEngineCredentials(builder);
}

/** Clones the compute engine account with the specified scopes. */
/** Clones the compute engine account with the specified scopes and default scopes. */
@Override
public GoogleCredentials createScoped(
Collection<String> newScopes, Collection<String> newDefaultScopes) {
return new ComputeEngineCredentials(this.transportFactory, newScopes, newDefaultScopes);
ComputeEngineCredentials.Builder builder =
ComputeEngineCredentials.newBuilder()
.setHttpTransportFactory(transportFactory)
.setScopes(newScopes)
.setDefaultScopes(newDefaultScopes);
return new ComputeEngineCredentials(builder);
}

/**
Expand All @@ -172,7 +178,7 @@ public GoogleCredentials createScoped(
* @return new ComputeEngineCredentials
*/
public static ComputeEngineCredentials create() {
return new ComputeEngineCredentials(null, null, null);
return new ComputeEngineCredentials(ComputeEngineCredentials.newBuilder());
}

public final Collection<String> getScopes() {
Expand All @@ -192,6 +198,66 @@ String createTokenUrlWithScopes() {
return tokenUrl.toString();
}

/**
* Gets the universe domain from the GCE metadata server.
*
* <p>Returns an explicit universe domain if it was provided during credential initialization.
*
* <p>Returns the {@link Credentials#GOOGLE_DEFAULT_UNIVERSE} if universe domain endpoint is not
* found (404) or returns an empty string.
*
* <p>Otherwise, returns universe domain from GCE metadata service.
*
* <p>Any above value is cached for the credential lifetime.
*
* @throws IOException if a call to GCE metadata service was unsuccessful. Check if exception
* implements the {@link Retryable} and {@code isRetryable()} will return true if the
* operation may be retried.
* @return string representing a universe domain in the format some-domain.xyz
*/
@Override
public String getUniverseDomain() throws IOException {
if (isExplicitUniverseDomain()) {
return super.getUniverseDomain();
}

synchronized (this) {
if (this.universeDomainFromMetadata != null) {
return this.universeDomainFromMetadata;
}
}

String universeDomainFromMetadata = getUniverseDomainFromMetadata();
synchronized (this) {
this.universeDomainFromMetadata = universeDomainFromMetadata;
}
return universeDomainFromMetadata;
}

private String getUniverseDomainFromMetadata() throws IOException {
HttpResponse response = getMetadataResponse(getUniverseDomainUrl());
int statusCode = response.getStatusCode();
if (statusCode == HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
return Credentials.GOOGLE_DEFAULT_UNIVERSE;
}
if (statusCode != HttpStatusCodes.STATUS_CODE_OK) {
IOException cause =
new IOException(
String.format(
"Unexpected Error code %s trying to get universe domain"
+ " from Compute Engine metadata for the default service account: %s",
statusCode, response.parseAsString()));
throw new GoogleAuthException(true, cause);
}
String responseString = response.parseAsString();

/* Earlier versions of MDS that supports universe_domain return empty string instead of GDU. */
if (responseString.isEmpty()) {
return Credentials.GOOGLE_DEFAULT_UNIVERSE;
}
return responseString;
}

/** Refresh the access token by getting it from the GCE metadata server */
@Override
public AccessToken refreshAccessToken() throws IOException {
Expand Down Expand Up @@ -420,6 +486,11 @@ public static String getTokenServerEncodedUrl() {
return getTokenServerEncodedUrl(DefaultCredentialsProvider.DEFAULT);
}

public static String getUniverseDomainUrl() {
return getMetadataServerUrl(DefaultCredentialsProvider.DEFAULT)
+ "/computeMetadata/v1/universe/universe_domain";
}

public static String getServiceAccountsUrl() {
return getMetadataServerUrl(DefaultCredentialsProvider.DEFAULT)
+ "/computeMetadata/v1/instance/service-accounts/?recursive=true";
Expand All @@ -436,20 +507,27 @@ public int hashCode() {
}

@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("transportFactoryClassName", transportFactoryClassName)
.toString();
protected ToStringHelper toStringHelper() {
synchronized (this) {
return super.toStringHelper()
.add("transportFactoryClassName", transportFactoryClassName)
.add("scopes", scopes)
.add("universeDomainFromMetadata", universeDomainFromMetadata);
}
}

@Override
public boolean equals(Object obj) {
if (!(obj instanceof ComputeEngineCredentials)) {
return false;
}
if (!super.equals(obj)) {
return false;
}
ComputeEngineCredentials other = (ComputeEngineCredentials) obj;
return Objects.equals(this.transportFactoryClassName, other.transportFactoryClassName)
&& Objects.equals(this.scopes, other.scopes);
&& Objects.equals(this.scopes, other.scopes)
&& Objects.equals(this.universeDomainFromMetadata, other.universeDomainFromMetadata);
}

private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException {
Expand Down Expand Up @@ -542,10 +620,15 @@ private String getDefaultServiceAccount() throws IOException {
public static class Builder extends GoogleCredentials.Builder {
private HttpTransportFactory transportFactory;
private Collection<String> scopes;
private Collection<String> defaultScopes;

protected Builder() {}
protected Builder() {
setRefreshMargin(COMPUTE_REFRESH_MARGIN);
setExpirationMargin(COMPUTE_EXPIRATION_MARGIN);
}

protected Builder(ComputeEngineCredentials credentials) {
super(credentials);
this.transportFactory = credentials.transportFactory;
this.scopes = credentials.scopes;
}
Expand All @@ -562,6 +645,24 @@ public Builder setScopes(Collection<String> scopes) {
return this;
}

@CanIgnoreReturnValue
public Builder setDefaultScopes(Collection<String> defaultScopes) {
this.defaultScopes = defaultScopes;
return this;
}

@CanIgnoreReturnValue
public Builder setUniverseDomain(String universeDomain) {
this.universeDomain = universeDomain;
return this;
}

@CanIgnoreReturnValue
public Builder setQuotaProjectId(String quotaProjectId) {
super.quotaProjectId = quotaProjectId;
return this;
}

public HttpTransportFactory getHttpTransportFactory() {
return transportFactory;
}
Expand All @@ -570,8 +671,12 @@ public Collection<String> getScopes() {
return scopes;
}

public Collection<String> getDefaultScopes() {
return defaultScopes;
}

public ComputeEngineCredentials build() {
return new ComputeEngineCredentials(transportFactory, scopes, null);
return new ComputeEngineCredentials(this);
}
}
}
Loading

0 comments on commit 7e26861

Please sign in to comment.