Skip to content

Commit d2ca9c6

Browse files
fix: jwt authentication on batch-bigtable.googleapis.com (#892)
* fix: jwt authentication on batch-bigtable.googleapis.com In general jwt audiences and service endpoints align. However in some cases like batch-bigtable.googleapis.com, they diverge. This PR workaround the issue by patching the JWT audience for batch-bigtable.googleapis.com * remove abandoned tst strategy * deps * fix settings * fix batch tests
1 parent 9290cd0 commit d2ca9c6

File tree

8 files changed

+321
-2
lines changed

8 files changed

+321
-2
lines changed

.kokoro/nightly/integration.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ env_vars: {
88

99
env_vars: {
1010
key: "INTEGRATION_TEST_ARGS"
11-
value: "-P bigtable-emulator-it,bigtable-prod-it -Dbigtable.project=gcloud-devel -Dbigtable.instance=google-cloud-bigtable -Dbigtable.table=integration-tests -Dbigtable.kms_key_name=projects/gcloud-devel/locations/us-east1/keyRings/cmek-test-key-ring/cryptoKeys/cmek-test-key -Dbigtable.wait-for-cmek-key-status=true"
11+
value: "-P bigtable-emulator-it,bigtable-prod-it,bigtable-prod-batch-it -Dbigtable.project=gcloud-devel -Dbigtable.instance=google-cloud-bigtable -Dbigtable.table=integration-tests -Dbigtable.kms_key_name=projects/gcloud-devel/locations/us-east1/keyRings/cmek-test-key-ring/cryptoKeys/cmek-test-key -Dbigtable.wait-for-cmek-key-status=true"
1212
}
1313

1414
env_vars: {

.kokoro/presubmit/integration.cfg

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ env_vars: {
88

99
env_vars: {
1010
key: "INTEGRATION_TEST_ARGS"
11-
value: "-P bigtable-emulator-it,bigtable-prod-it -Dbigtable.project=gcloud-devel -Dbigtable.instance=google-cloud-bigtable -Dbigtable.table=integration-tests -Dbigtable.kms_key_name=projects/gcloud-devel/locations/us-east1/keyRings/cmek-test-key-ring/cryptoKeys/cmek-test-key -Dbigtable.wait-for-cmek-key-status=true"
11+
value: "-P bigtable-emulator-it,bigtable-prod-it,bigtable-prod-batch-it -Dbigtable.project=gcloud-devel -Dbigtable.instance=google-cloud-bigtable -Dbigtable.table=integration-tests -Dbigtable.kms_key_name=projects/gcloud-devel/locations/us-east1/keyRings/cmek-test-key-ring/cryptoKeys/cmek-test-key -Dbigtable.wait-for-cmek-key-status=true"
1212
}
1313

1414
env_vars: {

google-cloud-bigtable/pom.xml

+61
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232
<bigtable.cfe-admin-endpoint/>
3333
<bigtable.directpath-data-endpoint/>
3434
<bigtable.directpath-admin-endpoint/>
35+
36+
<!-- This is used by bigtable-prod-batch-it profile to ensure that tests work on the batch endpoint.
37+
Also, this property will be augmented by `internal-bigtable-prod-batch-it-prop-helper` profile -->
38+
<bigtable.cfe-data-batch-endpoint>batch-bigtable.googleapis.com:443</bigtable.cfe-data-batch-endpoint>
3539
</properties>
3640

3741
<dependencyManagement>
@@ -114,6 +118,14 @@
114118
<groupId>com.google.guava</groupId>
115119
<artifactId>guava</artifactId>
116120
</dependency>
121+
<dependency>
122+
<groupId>com.google.http-client</groupId>
123+
<artifactId>google-http-client</artifactId>
124+
</dependency>
125+
<dependency>
126+
<groupId>com.google.http-client</groupId>
127+
<artifactId>google-http-client-gson</artifactId>
128+
</dependency>
117129
<dependency>
118130
<groupId>com.google.protobuf</groupId>
119131
<artifactId>protobuf-java</artifactId>
@@ -323,6 +335,55 @@
323335
</build>
324336
</profile>
325337

338+
<!-- internal profile to provide a sensible default for bigtable.cfe-data-batch-endpoint based on
339+
bigtable.cfe-data-endpoint -->
340+
<profile>
341+
<id>internal-bigtable-prod-batch-it-prop-helper</id>
342+
<activation>
343+
<property>
344+
<name>bigtable.cfe-data-endpoint</name>
345+
</property>
346+
</activation>
347+
<properties>
348+
<bigtable.cfe-data-batch-endpoint>batch-${bigtable.cfe-data-endpoint}</bigtable.cfe-data-batch-endpoint>
349+
</properties>
350+
</profile>
351+
352+
<profile>
353+
<id>bigtable-prod-batch-it</id>
354+
<build>
355+
<plugins>
356+
<plugin>
357+
<artifactId>maven-failsafe-plugin</artifactId>
358+
<executions>
359+
<execution>
360+
<id>prod-batch-it</id>
361+
<goals>
362+
<goal>integration-test</goal>
363+
<goal>verify</goal>
364+
</goals>
365+
<configuration>
366+
<skip>false</skip>
367+
368+
<systemPropertyVariables>
369+
<bigtable.env>cloud</bigtable.env>
370+
<bigtable.data-endpoint>${bigtable.cfe-data-batch-endpoint}</bigtable.data-endpoint>
371+
<bigtable.admin-endpoint>${bigtable.cfe-admin-endpoint}</bigtable.admin-endpoint>
372+
<bigtable.grpc-log-dir>${project.build.directory}/test-grpc-logs/prod-batch-it</bigtable.grpc-log-dir>
373+
</systemPropertyVariables>
374+
<includes>
375+
<include>com.google.cloud.bigtable.data.v2.it.*IT</include>
376+
</includes>
377+
<summaryFile>${project.build.directory}/failsafe-reports/failsafe-summary-prod-batch-it.xml</summaryFile>
378+
<reportsDirectory>${project.build.directory}/failsafe-reports/prod-batch-it</reportsDirectory>
379+
</configuration>
380+
</execution>
381+
</executions>
382+
</plugin>
383+
</plugins>
384+
</build>
385+
</profile>
386+
326387
<profile>
327388
<id>bigtable-directpath-it</id>
328389
<build>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright 2021 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.cloud.bigtable.data.v2.internal;
17+
18+
import com.google.api.core.InternalApi;
19+
import com.google.auth.Credentials;
20+
import com.google.auth.RequestMetadataCallback;
21+
import com.google.auth.oauth2.ServiceAccountJwtAccessCredentials;
22+
import java.io.IOException;
23+
import java.net.URI;
24+
import java.util.List;
25+
import java.util.Map;
26+
import java.util.concurrent.Executor;
27+
28+
/**
29+
* Internal helper to fix the mapping between JWT audiences and service endpoints.
30+
*
31+
* <p>In most cases JWT audiences correspond to service endpoints. However, in some cases they
32+
* diverge. To workaround this, this class hardcodes the audience and forces the underlying
33+
* implementation to use it.
34+
*
35+
* <p>Internal Only - public for technical reasons
36+
*/
37+
@InternalApi
38+
public class JwtCredentialsWithAudience extends Credentials {
39+
private final ServiceAccountJwtAccessCredentials delegate;
40+
41+
public JwtCredentialsWithAudience(ServiceAccountJwtAccessCredentials delegate, URI audience) {
42+
this.delegate = delegate.toBuilder().setDefaultAudience(audience).build();
43+
}
44+
45+
@Override
46+
public String getAuthenticationType() {
47+
return delegate.getAuthenticationType();
48+
}
49+
50+
@Override
51+
public Map<String, List<String>> getRequestMetadata() throws IOException {
52+
return delegate.getRequestMetadata();
53+
}
54+
55+
@Override
56+
public void getRequestMetadata(URI ignored, Executor executor, RequestMetadataCallback callback) {
57+
delegate.getRequestMetadata(null, executor, callback);
58+
}
59+
60+
@Override
61+
public Map<String, List<String>> getRequestMetadata(URI ignored) throws IOException {
62+
return delegate.getRequestMetadata(null);
63+
}
64+
65+
@Override
66+
public boolean hasRequestMetadata() {
67+
return delegate.hasRequestMetadata();
68+
}
69+
70+
@Override
71+
public boolean hasRequestMetadataOnly() {
72+
return delegate.hasRequestMetadataOnly();
73+
}
74+
75+
@Override
76+
public void refresh() throws IOException {
77+
delegate.refresh();
78+
}
79+
}

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStub.java

+43
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.google.api.gax.batching.BatcherImpl;
2222
import com.google.api.gax.batching.FlowController;
2323
import com.google.api.gax.core.BackgroundResource;
24+
import com.google.api.gax.core.CredentialsProvider;
2425
import com.google.api.gax.core.FixedCredentialsProvider;
2526
import com.google.api.gax.grpc.GaxGrpcProperties;
2627
import com.google.api.gax.grpc.GrpcCallContext;
@@ -42,6 +43,7 @@
4243
import com.google.api.gax.tracing.TracedServerStreamingCallable;
4344
import com.google.api.gax.tracing.TracedUnaryCallable;
4445
import com.google.auth.Credentials;
46+
import com.google.auth.oauth2.ServiceAccountJwtAccessCredentials;
4547
import com.google.bigtable.v2.BigtableGrpc;
4648
import com.google.bigtable.v2.CheckAndMutateRowRequest;
4749
import com.google.bigtable.v2.CheckAndMutateRowResponse;
@@ -56,6 +58,7 @@
5658
import com.google.bigtable.v2.SampleRowKeysRequest;
5759
import com.google.bigtable.v2.SampleRowKeysResponse;
5860
import com.google.cloud.bigtable.Version;
61+
import com.google.cloud.bigtable.data.v2.internal.JwtCredentialsWithAudience;
5962
import com.google.cloud.bigtable.data.v2.internal.RequestContext;
6063
import com.google.cloud.bigtable.data.v2.models.BulkMutation;
6164
import com.google.cloud.bigtable.data.v2.models.ConditionalRowMutation;
@@ -94,6 +97,8 @@
9497
import io.opencensus.tags.Tagger;
9598
import io.opencensus.tags.Tags;
9699
import java.io.IOException;
100+
import java.net.URI;
101+
import java.net.URISyntaxException;
97102
import java.util.List;
98103
import java.util.Map;
99104
import java.util.concurrent.TimeUnit;
@@ -146,6 +151,9 @@ public static EnhancedBigtableStubSettings finalizeSettings(
146151
// TODO: this implementation is on the cusp of unwieldy, if we end up adding more features
147152
// consider splitting it up by feature.
148153

154+
// workaround JWT audience issues
155+
patchCredentials(builder);
156+
149157
// Inject channel priming
150158
if (settings.isRefreshingChannel()) {
151159
// Fix the credentials so that they can be shared
@@ -218,6 +226,41 @@ public static EnhancedBigtableStubSettings finalizeSettings(
218226
return builder.build();
219227
}
220228

229+
private static void patchCredentials(EnhancedBigtableStubSettings.Builder settings)
230+
throws IOException {
231+
int i = settings.getEndpoint().lastIndexOf(":");
232+
String host = settings.getEndpoint().substring(0, i);
233+
String audience = settings.getJwtAudienceMapping().get(host);
234+
235+
if (audience == null) {
236+
return;
237+
}
238+
URI audienceUri = null;
239+
try {
240+
audienceUri = new URI(audience);
241+
} catch (URISyntaxException e) {
242+
throw new IllegalStateException("invalid JWT audience override", e);
243+
}
244+
245+
CredentialsProvider credentialsProvider = settings.getCredentialsProvider();
246+
if (credentialsProvider == null) {
247+
return;
248+
}
249+
250+
Credentials credentials = credentialsProvider.getCredentials();
251+
if (credentials == null) {
252+
return;
253+
}
254+
255+
if (!(credentials instanceof ServiceAccountJwtAccessCredentials)) {
256+
return;
257+
}
258+
259+
ServiceAccountJwtAccessCredentials jwtCreds = (ServiceAccountJwtAccessCredentials) credentials;
260+
JwtCredentialsWithAudience patchedCreds = new JwtCredentialsWithAudience(jwtCreds, audienceUri);
261+
settings.setCredentialsProvider(FixedCredentialsProvider.create(patchedCreds));
262+
}
263+
221264
public EnhancedBigtableStub(EnhancedBigtableStubSettings settings, ClientContext clientContext) {
222265
this.settings = settings;
223266
this.clientContext = clientContext;

google-cloud-bigtable/src/main/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettings.java

+30
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package com.google.cloud.bigtable.data.v2.stub;
1717

1818
import com.google.api.core.BetaApi;
19+
import com.google.api.core.InternalApi;
1920
import com.google.api.gax.batching.BatchingCallSettings;
2021
import com.google.api.gax.batching.BatchingSettings;
2122
import com.google.api.gax.batching.FlowControlSettings;
@@ -151,12 +152,20 @@ public class EnhancedBigtableStubSettings extends StubSettings<EnhancedBigtableS
151152
.add("https://www.googleapis.com/auth/cloud-platform")
152153
.build();
153154

155+
/**
156+
* In most cases, jwt audience == service name. However in some cases, this is not the case. The
157+
* following mapping is used to patch the audience in a JWT token.
158+
*/
159+
private static final Map<String, String> DEFAULT_JWT_AUDIENCE_MAPPING =
160+
ImmutableMap.of("batch-bigtable.googleapis.com", "https://bigtable.googleapis.com/");
161+
154162
private final String projectId;
155163
private final String instanceId;
156164
private final String appProfileId;
157165
private final boolean isRefreshingChannel;
158166
private ImmutableList<String> primedTableIds;
159167
private HeaderTracer headerTracer;
168+
private final Map<String, String> jwtAudienceMapping;
160169

161170
private final ServerStreamingCallSettings<Query, Row> readRowsSettings;
162171
private final UnaryCallSettings<Query, Row> readRowSettings;
@@ -191,6 +200,7 @@ private EnhancedBigtableStubSettings(Builder builder) {
191200
isRefreshingChannel = builder.isRefreshingChannel;
192201
primedTableIds = builder.primedTableIds;
193202
headerTracer = builder.headerTracer;
203+
jwtAudienceMapping = builder.jwtAudienceMapping;
194204

195205
// Per method settings.
196206
readRowsSettings = builder.readRowsSettings.build();
@@ -240,6 +250,11 @@ HeaderTracer getHeaderTracer() {
240250
return headerTracer;
241251
}
242252

253+
@InternalApi("Used for internal testing")
254+
public Map<String, String> getJwtAudienceMapping() {
255+
return jwtAudienceMapping;
256+
}
257+
243258
/** Returns a builder for the default ChannelProvider for this service. */
244259
public static InstantiatingGrpcChannelProvider.Builder defaultGrpcTransportProviderBuilder() {
245260
return BigtableStubSettings.defaultGrpcTransportProviderBuilder()
@@ -498,6 +513,7 @@ public static class Builder extends StubSettings.Builder<EnhancedBigtableStubSet
498513
private boolean isRefreshingChannel;
499514
private ImmutableList<String> primedTableIds;
500515
private HeaderTracer headerTracer;
516+
private Map<String, String> jwtAudienceMapping;
501517

502518
private final ServerStreamingCallSettings.Builder<Query, Row> readRowsSettings;
503519
private final UnaryCallSettings.Builder<Query, Row> readRowSettings;
@@ -522,6 +538,7 @@ private Builder() {
522538
this.isRefreshingChannel = false;
523539
primedTableIds = ImmutableList.of();
524540
headerTracer = HeaderTracer.newBuilder().build();
541+
jwtAudienceMapping = DEFAULT_JWT_AUDIENCE_MAPPING;
525542
setCredentialsProvider(defaultCredentialsProviderBuilder().build());
526543

527544
// Defaults provider
@@ -629,6 +646,7 @@ private Builder(EnhancedBigtableStubSettings settings) {
629646
isRefreshingChannel = settings.isRefreshingChannel;
630647
primedTableIds = settings.primedTableIds;
631648
headerTracer = settings.headerTracer;
649+
jwtAudienceMapping = settings.jwtAudienceMapping;
632650

633651
// Per method settings.
634652
readRowsSettings = settings.readRowsSettings.toBuilder();
@@ -762,6 +780,17 @@ HeaderTracer getHeaderTracer() {
762780
return headerTracer;
763781
}
764782

783+
@InternalApi("Used for internal testing")
784+
public Builder setJwtAudienceMapping(Map<String, String> jwtAudienceMapping) {
785+
this.jwtAudienceMapping = Preconditions.checkNotNull(jwtAudienceMapping);
786+
return this;
787+
}
788+
789+
@InternalApi("Used for internal testing")
790+
public Map<String, String> getJwtAudienceMapping() {
791+
return jwtAudienceMapping;
792+
}
793+
765794
/** Returns the builder for the settings used for calls to readRows. */
766795
public ServerStreamingCallSettings.Builder<Query, Row> readRowsSettings() {
767796
return readRowsSettings;
@@ -842,6 +871,7 @@ public String toString() {
842871
.add("isRefreshingChannel", isRefreshingChannel)
843872
.add("primedTableIds", primedTableIds)
844873
.add("headerTracer", headerTracer)
874+
.add("jwtAudienceMapping", jwtAudienceMapping)
845875
.add("readRowsSettings", readRowsSettings)
846876
.add("readRowSettings", readRowSettings)
847877
.add("sampleRowKeysSettings", sampleRowKeysSettings)

google-cloud-bigtable/src/test/java/com/google/cloud/bigtable/data/v2/stub/EnhancedBigtableStubSettingsTest.java

+1
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,7 @@ public void verifyDefaultHeaderTracerNotNullTest() {
716716
"isRefreshingChannel",
717717
"primedTableIds",
718718
"headerTracer",
719+
"jwtAudienceMapping",
719720
"readRowsSettings",
720721
"readRowSettings",
721722
"sampleRowKeysSettings",

0 commit comments

Comments
 (0)