Skip to content

Commit 7e26861

Browse files
Timur Sadykovgcf-owl-bot[bot]
andauthored
feat: adds universe domain support for compute credentials (#1346)
* 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>
1 parent ab87281 commit 7e26861

File tree

8 files changed

+623
-194
lines changed

8 files changed

+623
-194
lines changed

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

Lines changed: 135 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,13 @@
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.auth.Credentials;
45+
import com.google.auth.Retryable;
4446
import com.google.auth.ServiceAccountSigner;
4547
import com.google.auth.http.HttpTransportFactory;
4648
import com.google.common.annotations.VisibleForTesting;
4749
import com.google.common.base.Joiner;
48-
import com.google.common.base.MoreObjects;
50+
import com.google.common.base.MoreObjects.ToStringHelper;
4951
import com.google.common.collect.ImmutableSet;
5052
import com.google.errorprone.annotations.CanIgnoreReturnValue;
5153
import java.io.BufferedReader;
@@ -79,12 +81,12 @@ public class ComputeEngineCredentials extends GoogleCredentials
7981
implements ServiceAccountSigner, IdTokenProvider {
8082

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

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

@@ -120,34 +122,31 @@ public class ComputeEngineCredentials extends GoogleCredentials
120122
private transient HttpTransportFactory transportFactory;
121123
private transient String serviceAccountEmail;
122124

125+
private String universeDomainFromMetadata = null;
126+
123127
/**
124-
* Constructor with overridden transport.
128+
* An internal constructor
125129
*
126-
* @param transportFactory HTTP transport factory, creates the transport used to get access
127-
* tokens.
128-
* @param scopes scope strings for the APIs to be called. May be null or an empty collection.
129-
* @param defaultScopes default scope strings for the APIs to be called. May be null or an empty
130-
* collection. Default scopes are ignored if scopes are provided.
130+
* @param builder A builder for {@link ComputeEngineCredentials} See {@link
131+
* ComputeEngineCredentials.Builder}
131132
*/
132-
private ComputeEngineCredentials(
133-
HttpTransportFactory transportFactory,
134-
Collection<String> scopes,
135-
Collection<String> defaultScopes) {
136-
super(/* accessToken= */ null, COMPUTE_REFRESH_MARGIN, COMPUTE_EXPIRATION_MARGIN);
133+
private ComputeEngineCredentials(ComputeEngineCredentials.Builder builder) {
134+
super(builder);
137135

138136
this.transportFactory =
139137
firstNonNull(
140-
transportFactory,
138+
builder.getHttpTransportFactory(),
141139
getFromServiceLoader(HttpTransportFactory.class, OAuth2Utils.HTTP_TRANSPORT_FACTORY));
142140
this.transportFactoryClassName = this.transportFactory.getClass().getName();
143141
// Use defaultScopes only when scopes don't exist.
144-
if (scopes == null || scopes.isEmpty()) {
145-
scopes = defaultScopes;
142+
Collection<String> scopesToUse = builder.scopes;
143+
if (scopesToUse == null || scopesToUse.isEmpty()) {
144+
scopesToUse = builder.getDefaultScopes();
146145
}
147-
if (scopes == null) {
146+
if (scopesToUse == null) {
148147
this.scopes = ImmutableSet.<String>of();
149148
} else {
150-
List<String> scopeList = new ArrayList<String>(scopes);
149+
List<String> scopeList = new ArrayList<String>(scopesToUse);
151150
scopeList.removeAll(Arrays.asList("", null));
152151
this.scopes = ImmutableSet.<String>copyOf(scopeList);
153152
}
@@ -156,14 +155,21 @@ private ComputeEngineCredentials(
156155
/** Clones the compute engine account with the specified scopes. */
157156
@Override
158157
public GoogleCredentials createScoped(Collection<String> newScopes) {
159-
return new ComputeEngineCredentials(this.transportFactory, newScopes, null);
158+
ComputeEngineCredentials.Builder builder =
159+
this.toBuilder().setHttpTransportFactory(transportFactory).setScopes(newScopes);
160+
return new ComputeEngineCredentials(builder);
160161
}
161162

162-
/** Clones the compute engine account with the specified scopes. */
163+
/** Clones the compute engine account with the specified scopes and default scopes. */
163164
@Override
164165
public GoogleCredentials createScoped(
165166
Collection<String> newScopes, Collection<String> newDefaultScopes) {
166-
return new ComputeEngineCredentials(this.transportFactory, newScopes, newDefaultScopes);
167+
ComputeEngineCredentials.Builder builder =
168+
ComputeEngineCredentials.newBuilder()
169+
.setHttpTransportFactory(transportFactory)
170+
.setScopes(newScopes)
171+
.setDefaultScopes(newDefaultScopes);
172+
return new ComputeEngineCredentials(builder);
167173
}
168174

169175
/**
@@ -172,7 +178,7 @@ public GoogleCredentials createScoped(
172178
* @return new ComputeEngineCredentials
173179
*/
174180
public static ComputeEngineCredentials create() {
175-
return new ComputeEngineCredentials(null, null, null);
181+
return new ComputeEngineCredentials(ComputeEngineCredentials.newBuilder());
176182
}
177183

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

201+
/**
202+
* Gets the universe domain from the GCE metadata server.
203+
*
204+
* <p>Returns an explicit universe domain if it was provided during credential initialization.
205+
*
206+
* <p>Returns the {@link Credentials#GOOGLE_DEFAULT_UNIVERSE} if universe domain endpoint is not
207+
* found (404) or returns an empty string.
208+
*
209+
* <p>Otherwise, returns universe domain from GCE metadata service.
210+
*
211+
* <p>Any above value is cached for the credential lifetime.
212+
*
213+
* @throws IOException if a call to GCE metadata service was unsuccessful. Check if exception
214+
* implements the {@link Retryable} and {@code isRetryable()} will return true if the
215+
* operation may be retried.
216+
* @return string representing a universe domain in the format some-domain.xyz
217+
*/
218+
@Override
219+
public String getUniverseDomain() throws IOException {
220+
if (isExplicitUniverseDomain()) {
221+
return super.getUniverseDomain();
222+
}
223+
224+
synchronized (this) {
225+
if (this.universeDomainFromMetadata != null) {
226+
return this.universeDomainFromMetadata;
227+
}
228+
}
229+
230+
String universeDomainFromMetadata = getUniverseDomainFromMetadata();
231+
synchronized (this) {
232+
this.universeDomainFromMetadata = universeDomainFromMetadata;
233+
}
234+
return universeDomainFromMetadata;
235+
}
236+
237+
private String getUniverseDomainFromMetadata() throws IOException {
238+
HttpResponse response = getMetadataResponse(getUniverseDomainUrl());
239+
int statusCode = response.getStatusCode();
240+
if (statusCode == HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
241+
return Credentials.GOOGLE_DEFAULT_UNIVERSE;
242+
}
243+
if (statusCode != HttpStatusCodes.STATUS_CODE_OK) {
244+
IOException cause =
245+
new IOException(
246+
String.format(
247+
"Unexpected Error code %s trying to get universe domain"
248+
+ " from Compute Engine metadata for the default service account: %s",
249+
statusCode, response.parseAsString()));
250+
throw new GoogleAuthException(true, cause);
251+
}
252+
String responseString = response.parseAsString();
253+
254+
/* Earlier versions of MDS that supports universe_domain return empty string instead of GDU. */
255+
if (responseString.isEmpty()) {
256+
return Credentials.GOOGLE_DEFAULT_UNIVERSE;
257+
}
258+
return responseString;
259+
}
260+
195261
/** Refresh the access token by getting it from the GCE metadata server */
196262
@Override
197263
public AccessToken refreshAccessToken() throws IOException {
@@ -420,6 +486,11 @@ public static String getTokenServerEncodedUrl() {
420486
return getTokenServerEncodedUrl(DefaultCredentialsProvider.DEFAULT);
421487
}
422488

489+
public static String getUniverseDomainUrl() {
490+
return getMetadataServerUrl(DefaultCredentialsProvider.DEFAULT)
491+
+ "/computeMetadata/v1/universe/universe_domain";
492+
}
493+
423494
public static String getServiceAccountsUrl() {
424495
return getMetadataServerUrl(DefaultCredentialsProvider.DEFAULT)
425496
+ "/computeMetadata/v1/instance/service-accounts/?recursive=true";
@@ -436,20 +507,27 @@ public int hashCode() {
436507
}
437508

438509
@Override
439-
public String toString() {
440-
return MoreObjects.toStringHelper(this)
441-
.add("transportFactoryClassName", transportFactoryClassName)
442-
.toString();
510+
protected ToStringHelper toStringHelper() {
511+
synchronized (this) {
512+
return super.toStringHelper()
513+
.add("transportFactoryClassName", transportFactoryClassName)
514+
.add("scopes", scopes)
515+
.add("universeDomainFromMetadata", universeDomainFromMetadata);
516+
}
443517
}
444518

445519
@Override
446520
public boolean equals(Object obj) {
447521
if (!(obj instanceof ComputeEngineCredentials)) {
448522
return false;
449523
}
524+
if (!super.equals(obj)) {
525+
return false;
526+
}
450527
ComputeEngineCredentials other = (ComputeEngineCredentials) obj;
451528
return Objects.equals(this.transportFactoryClassName, other.transportFactoryClassName)
452-
&& Objects.equals(this.scopes, other.scopes);
529+
&& Objects.equals(this.scopes, other.scopes)
530+
&& Objects.equals(this.universeDomainFromMetadata, other.universeDomainFromMetadata);
453531
}
454532

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

546-
protected Builder() {}
625+
protected Builder() {
626+
setRefreshMargin(COMPUTE_REFRESH_MARGIN);
627+
setExpirationMargin(COMPUTE_EXPIRATION_MARGIN);
628+
}
547629

548630
protected Builder(ComputeEngineCredentials credentials) {
631+
super(credentials);
549632
this.transportFactory = credentials.transportFactory;
550633
this.scopes = credentials.scopes;
551634
}
@@ -562,6 +645,24 @@ public Builder setScopes(Collection<String> scopes) {
562645
return this;
563646
}
564647

648+
@CanIgnoreReturnValue
649+
public Builder setDefaultScopes(Collection<String> defaultScopes) {
650+
this.defaultScopes = defaultScopes;
651+
return this;
652+
}
653+
654+
@CanIgnoreReturnValue
655+
public Builder setUniverseDomain(String universeDomain) {
656+
this.universeDomain = universeDomain;
657+
return this;
658+
}
659+
660+
@CanIgnoreReturnValue
661+
public Builder setQuotaProjectId(String quotaProjectId) {
662+
super.quotaProjectId = quotaProjectId;
663+
return this;
664+
}
665+
565666
public HttpTransportFactory getHttpTransportFactory() {
566667
return transportFactory;
567668
}
@@ -570,8 +671,12 @@ public Collection<String> getScopes() {
570671
return scopes;
571672
}
572673

674+
public Collection<String> getDefaultScopes() {
675+
return defaultScopes;
676+
}
677+
573678
public ComputeEngineCredentials build() {
574-
return new ComputeEngineCredentials(transportFactory, scopes, null);
679+
return new ComputeEngineCredentials(this);
575680
}
576681
}
577682
}

0 commit comments

Comments
 (0)