Skip to content

Commit 110d2b7

Browse files
authored
feat: enable self-signed JWTs by default in ServiceOptions (#13338)
This should improve efficiency/reliability by avoiding OAuth token exchange - see https://google.aip.dev/auth/4111. BigQueryOptions (b/523372553) and StorageOptions (b/523372960) are disabled by default for now.
1 parent 79e26b8 commit 110d2b7

6 files changed

Lines changed: 92 additions & 2 deletions

File tree

java-bigquery/google-cloud-bigquery/src/main/java/com/google/cloud/bigquery/BigQueryOptions.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ public static class Builder extends ServiceOptions.Builder<BigQuery, BigQueryOpt
7777
private Tracer openTelemetryTracer;
7878
private ResultRetryAlgorithm<?> resultRetryAlgorithm;
7979

80-
private Builder() {}
80+
private Builder() {
81+
setUseJwtAccessWithScope(false);
82+
}
8183

8284
private Builder(BigQueryOptions options) {
8385
super(options);

java-bigquery/google-cloud-bigquery/src/test/java/com/google/cloud/bigquery/BigQueryOptionsTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,11 @@ void dataFormatOptionsSetterHasPrecedence() {
9292

9393
assertTrue(options.getDataFormatOptions().useInt64Timestamp());
9494
}
95+
96+
@Test
97+
void testUseJwtAccessWithScope_defaultsToFalse() {
98+
BigQueryOptions options = BigQueryOptions.newBuilder().setProjectId("project-id").build();
99+
100+
assertFalse(options.getUseJwtAccessWithScope());
101+
}
95102
}

java-storage/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageOptions.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,9 @@ public DefaultStorageRpcFactory() {
112112
public abstract static class Builder
113113
extends ServiceOptions.Builder<Storage, StorageOptions, Builder> {
114114

115-
Builder() {}
115+
Builder() {
116+
setUseJwtAccessWithScope(false);
117+
}
116118

117119
Builder(StorageOptions options) {
118120
super(options);

java-storage/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageOptionsBuilderTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,15 @@ public void grpc() throws Exception {
6969
() -> assertThat(rebuilt.hashCode()).isEqualTo(base.hashCode()));
7070
}
7171

72+
@Test
73+
public void useJwtAccessWithScope_defaultsToFalse() {
74+
HttpStorageOptions httpOptions = HttpStorageOptions.http().build();
75+
GrpcStorageOptions grpcOptions = GrpcStorageOptions.grpc().build();
76+
77+
assertThat(httpOptions.getUseJwtAccessWithScope()).isFalse();
78+
assertThat(grpcOptions.getUseJwtAccessWithScope()).isFalse();
79+
}
80+
7281
private static class MyStorageRetryStrategy implements StorageRetryStrategy {
7382

7483
@Override

sdk-platform-java/java-core/google-cloud-core/src/main/java/com/google/cloud/ServiceOptions.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ public abstract class ServiceOptions<
106106
private final TransportOptions transportOptions;
107107
private final HeaderProvider headerProvider;
108108
private final String quotaProjectId;
109+
private final boolean useJwtAccessWithScope;
109110

110111
private transient ServiceRpcFactory<OptionsT> serviceRpcFactory;
111112
private transient ServiceFactory<ServiceT, OptionsT> serviceFactory;
@@ -140,6 +141,7 @@ public abstract static class Builder<
140141
private HeaderProvider headerProvider;
141142
private String clientLibToken = ServiceOptions.getGoogApiClientLibName();
142143
private String quotaProjectId;
144+
private boolean useJwtAccessWithScope = true;
143145

144146
private ApiTracerFactory apiTracerFactory;
145147

@@ -159,6 +161,7 @@ protected Builder(ServiceOptions<ServiceT, OptionsT> options) {
159161
transportOptions = options.transportOptions;
160162
clientLibToken = options.clientLibToken;
161163
quotaProjectId = options.quotaProjectId;
164+
useJwtAccessWithScope = options.useJwtAccessWithScope;
162165
apiTracerFactory = options.apiTracerFactory;
163166
}
164167

@@ -313,6 +316,18 @@ public B setQuotaProjectId(String quotaProjectId) {
313316
return self();
314317
}
315318

319+
/**
320+
* Sets the configuration determining whether self-signed JWT with scopes are used for service
321+
* account credentials.
322+
*
323+
* @param useJwtAccessWithScope whether to use self-signed JWT with scopes
324+
* @return the builder
325+
*/
326+
public B setUseJwtAccessWithScope(final boolean useJwtAccessWithScope) {
327+
this.useJwtAccessWithScope = useJwtAccessWithScope;
328+
return self();
329+
}
330+
316331
/**
317332
* Sets the {@link ApiTracerFactory}. It will be used to create an {@link ApiTracer} that is
318333
* annotated throughout the lifecycle of an RPC operation.
@@ -365,6 +380,7 @@ protected ServiceOptions(
365380
builder.quotaProjectId != null
366381
? builder.quotaProjectId
367382
: getValueFromCredentialsFile(getCredentialsPath(), "quota_project_id");
383+
useJwtAccessWithScope = builder.useJwtAccessWithScope;
368384
apiTracerFactory = builder.apiTracerFactory;
369385
}
370386

@@ -650,6 +666,11 @@ public Credentials getScopedCredentials() {
650666
&& ((GoogleCredentials) credentials).createScopedRequired()) {
651667
credentialsToReturn = ((GoogleCredentials) credentials).createScoped(getScopes());
652668
}
669+
if (credentialsToReturn instanceof ServiceAccountCredentials) {
670+
credentialsToReturn =
671+
((ServiceAccountCredentials) credentialsToReturn)
672+
.createWithUseJwtAccessWithScope(getUseJwtAccessWithScope());
673+
}
653674
return credentialsToReturn;
654675
}
655676

@@ -823,6 +844,15 @@ public String getQuotaProjectId() {
823844
return quotaProjectId;
824845
}
825846

847+
/**
848+
* Returns true when self-signed JWT with scopes are used for service account credentials.
849+
*
850+
* @return true when self-signed JWT with scopes are used
851+
*/
852+
public boolean getUseJwtAccessWithScope() {
853+
return useJwtAccessWithScope;
854+
}
855+
826856
/**
827857
* Returns the resolved host for the Service to connect to Google Cloud
828858
*

sdk-platform-java/java-core/google-cloud-core/src/test/java/com/google/cloud/ServiceOptionsTest.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,46 @@ void testIsValidUniverseDomain_userUniverseDomainConfig_nonGDUCredentials() thro
630630
assertThat(options.hasValidUniverseDomain()).isTrue();
631631
}
632632

633+
@Test
634+
void testGetScopedCredentials_useJwtAccessWithScope_enablesByDefault() {
635+
TestServiceOptions options =
636+
TestServiceOptions.newBuilder()
637+
.setProjectId("project-id")
638+
.setCredentials(credentials)
639+
.build();
640+
com.google.auth.Credentials scoped = options.getScopedCredentials();
641+
assertThat(scoped).isInstanceOf(ServiceAccountCredentials.class);
642+
assertThat(((ServiceAccountCredentials) scoped).getUseJwtAccessWithScope()).isTrue();
643+
}
644+
645+
@Test
646+
void testGetScopedCredentials_useJwtAccessWithScope_canBeDisabled() {
647+
TestServiceOptions options =
648+
new TestServiceOptions.Builder()
649+
.setProjectId("project-id")
650+
.setCredentials(credentials)
651+
.setUseJwtAccessWithScope(false)
652+
.build();
653+
com.google.auth.Credentials scoped = options.getScopedCredentials();
654+
assertThat(scoped).isInstanceOf(ServiceAccountCredentials.class);
655+
assertThat(((ServiceAccountCredentials) scoped).getUseJwtAccessWithScope()).isFalse();
656+
}
657+
658+
@Test
659+
void testGetScopedCredentials_useJwtAccessWithScope_overridesCredentials() {
660+
ServiceAccountCredentials trueCredentials =
661+
((ServiceAccountCredentials) credentials).createWithUseJwtAccessWithScope(true);
662+
TestServiceOptions options =
663+
new TestServiceOptions.Builder()
664+
.setProjectId("project-id")
665+
.setCredentials(trueCredentials)
666+
.setUseJwtAccessWithScope(false)
667+
.build();
668+
com.google.auth.Credentials scoped = options.getScopedCredentials();
669+
assertThat(scoped).isInstanceOf(ServiceAccountCredentials.class);
670+
assertThat(((ServiceAccountCredentials) scoped).getUseJwtAccessWithScope()).isFalse();
671+
}
672+
633673
private HttpResponse createHttpResponseWithHeader(final Multimap<String, String> headers)
634674
throws Exception {
635675
HttpTransport mockHttpTransport =

0 commit comments

Comments
 (0)