Skip to content

Commit ace11d5

Browse files
sagnghosolavloite
andauthored
feat: default authentication support for external hosts (#3656)
* feat: default authentication support for external hosts * unit test added * unit test typo * skip auth_token in emulator host for jdbc * skip auth_token for emulator * env variable prefix change * code refactor to handle edge cases * chore: create util method for reading token from file * chore: fix clirr failure * test: reset unimplemented flag after test --------- Co-authored-by: Knut Olav Løite <koloite@gmail.com>
1 parent b86c0bb commit ace11d5

File tree

4 files changed

+67
-0
lines changed

4 files changed

+67
-0
lines changed

google-cloud-spanner/clirr-ignored-differences.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,4 +822,10 @@
822822
<method>java.lang.Object runTransaction(com.google.cloud.spanner.connection.Connection$TransactionCallable)</method>
823823
</difference>
824824

825+
<!-- Added external host option -->
826+
<difference>
827+
<differenceType>7012</differenceType>
828+
<className>com/google/cloud/spanner/SpannerOptions$SpannerEnvironment</className>
829+
<method>com.google.auth.oauth2.GoogleCredentials getDefaultExternalHostCredentials()</method>
830+
</difference>
825831
</differences>

google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
import com.google.api.gax.tracing.ApiTracerFactory;
3535
import com.google.api.gax.tracing.BaseApiTracerFactory;
3636
import com.google.api.gax.tracing.OpencensusTracerFactory;
37+
import com.google.auth.oauth2.AccessToken;
38+
import com.google.auth.oauth2.GoogleCredentials;
3739
import com.google.cloud.NoCredentials;
3840
import com.google.cloud.ServiceDefaults;
3941
import com.google.cloud.ServiceOptions;
@@ -56,6 +58,7 @@
5658
import com.google.common.annotations.VisibleForTesting;
5759
import com.google.common.base.MoreObjects;
5860
import com.google.common.base.Preconditions;
61+
import com.google.common.base.Strings;
5962
import com.google.common.collect.ImmutableMap;
6063
import com.google.common.collect.ImmutableSet;
6164
import com.google.common.util.concurrent.ThreadFactoryBuilder;
@@ -79,8 +82,11 @@
7982
import java.io.IOException;
8083
import java.net.MalformedURLException;
8184
import java.net.URL;
85+
import java.nio.file.Files;
86+
import java.nio.file.Paths;
8287
import java.time.Duration;
8388
import java.util.ArrayList;
89+
import java.util.Base64;
8490
import java.util.HashMap;
8591
import java.util.List;
8692
import java.util.Map;
@@ -92,6 +98,7 @@
9298
import java.util.concurrent.ThreadFactory;
9399
import java.util.concurrent.TimeUnit;
94100
import java.util.concurrent.atomic.AtomicInteger;
101+
import java.util.regex.Pattern;
95102
import javax.annotation.Nonnull;
96103
import javax.annotation.Nullable;
97104
import javax.annotation.concurrent.GuardedBy;
@@ -110,6 +117,11 @@ public class SpannerOptions extends ServiceOptions<Spanner, SpannerOptions> {
110117

111118
private static final String API_SHORT_NAME = "Spanner";
112119
private static final String DEFAULT_HOST = "https://spanner.googleapis.com";
120+
private static final String CLOUD_SPANNER_HOST_FORMAT = ".*\\.googleapis\\.com.*";
121+
122+
@VisibleForTesting
123+
static final Pattern CLOUD_SPANNER_HOST_PATTERN = Pattern.compile(CLOUD_SPANNER_HOST_FORMAT);
124+
113125
private static final ImmutableSet<String> SCOPES =
114126
ImmutableSet.of(
115127
"https://www.googleapis.com/auth/spanner.admin",
@@ -843,8 +855,15 @@ default boolean isEnableEndToEndTracing() {
843855
default String getMonitoringHost() {
844856
return null;
845857
}
858+
859+
default GoogleCredentials getDefaultExternalHostCredentials() {
860+
return null;
861+
}
846862
}
847863

864+
static final String DEFAULT_SPANNER_EXTERNAL_HOST_CREDENTIALS =
865+
"SPANNER_EXTERNAL_HOST_AUTH_TOKEN";
866+
848867
/**
849868
* Default implementation of {@link SpannerEnvironment}. Reads all configuration from environment
850869
* variables.
@@ -900,6 +919,11 @@ public boolean isEnableEndToEndTracing() {
900919
public String getMonitoringHost() {
901920
return System.getenv(SPANNER_MONITORING_HOST);
902921
}
922+
923+
@Override
924+
public GoogleCredentials getDefaultExternalHostCredentials() {
925+
return getOAuthTokenFromFile(System.getenv(DEFAULT_SPANNER_EXTERNAL_HOST_CREDENTIALS));
926+
}
903927
}
904928

905929
/** Builder for {@link SpannerOptions} instances. */
@@ -967,6 +991,7 @@ public static class Builder
967991
private boolean enableBuiltInMetrics = SpannerOptions.environment.isEnableBuiltInMetrics();
968992
private String monitoringHost = SpannerOptions.environment.getMonitoringHost();
969993
private SslContext mTLSContext = null;
994+
private boolean isExternalHost = false;
970995

971996
private static String createCustomClientLibToken(String token) {
972997
return token + " " + ServiceOptions.getGoogApiClientLibName();
@@ -1459,6 +1484,9 @@ public Builder setDecodeMode(DecodeMode decodeMode) {
14591484
@Override
14601485
public Builder setHost(String host) {
14611486
super.setHost(host);
1487+
if (!CLOUD_SPANNER_HOST_PATTERN.matcher(host).matches()) {
1488+
this.isExternalHost = true;
1489+
}
14621490
// Setting a host should override any SPANNER_EMULATOR_HOST setting.
14631491
setEmulatorHost(null);
14641492
return this;
@@ -1629,6 +1657,8 @@ public SpannerOptions build() {
16291657
this.setChannelConfigurator(ManagedChannelBuilder::usePlaintext);
16301658
// As we are using plain text, we should never send any credentials.
16311659
this.setCredentials(NoCredentials.getInstance());
1660+
} else if (isExternalHost && credentials == null) {
1661+
credentials = environment.getDefaultExternalHostCredentials();
16321662
}
16331663
if (this.numChannels == null) {
16341664
this.numChannels =
@@ -1669,6 +1699,24 @@ public static void useDefaultEnvironment() {
16691699
SpannerOptions.environment = SpannerEnvironmentImpl.INSTANCE;
16701700
}
16711701

1702+
@InternalApi
1703+
public static GoogleCredentials getDefaultExternalHostCredentialsFromSysEnv() {
1704+
return getOAuthTokenFromFile(System.getenv(DEFAULT_SPANNER_EXTERNAL_HOST_CREDENTIALS));
1705+
}
1706+
1707+
private static @Nullable GoogleCredentials getOAuthTokenFromFile(@Nullable String file) {
1708+
if (!Strings.isNullOrEmpty(file)) {
1709+
String token;
1710+
try {
1711+
token = Base64.getEncoder().encodeToString(Files.readAllBytes(Paths.get(file)));
1712+
} catch (IOException e) {
1713+
throw SpannerExceptionFactory.newSpannerException(e);
1714+
}
1715+
return GoogleCredentials.create(new AccessToken(token, null));
1716+
}
1717+
return null;
1718+
}
1719+
16721720
/**
16731721
* Enables OpenTelemetry traces. Enabling OpenTelemetry traces will disable OpenCensus traces. By
16741722
* default, OpenCensus traces are enabled.

google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/ConnectionOptions.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -921,6 +921,8 @@ private ConnectionOptions(Builder builder) {
921921
getInitialConnectionPropertyValue(AUTO_CONFIG_EMULATOR),
922922
usePlainText,
923923
System.getenv());
924+
GoogleCredentials defaultExternalHostCredentials =
925+
SpannerOptions.getDefaultExternalHostCredentialsFromSysEnv();
924926
// Using credentials on a plain text connection is not allowed, so if the user has not specified
925927
// any credentials and is using a plain text connection, we should not try to get the
926928
// credentials from the environment, but default to NoCredentials.
@@ -935,6 +937,8 @@ && getInitialConnectionPropertyValue(OAUTH_TOKEN) == null
935937
this.credentials =
936938
new GoogleCredentials(
937939
new AccessToken(getInitialConnectionPropertyValue(OAUTH_TOKEN), null));
940+
} else if (isExternalHost && defaultExternalHostCredentials != null) {
941+
this.credentials = defaultExternalHostCredentials;
938942
} else if (getInitialConnectionPropertyValue(CREDENTIALS_PROVIDER) != null) {
939943
try {
940944
this.credentials = getInitialConnectionPropertyValue(CREDENTIALS_PROVIDER).getCredentials();

google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.google.cloud.spanner;
1818

19+
import static com.google.cloud.spanner.SpannerOptions.CLOUD_SPANNER_HOST_PATTERN;
1920
import static com.google.common.truth.Truth.assertThat;
2021
import static org.hamcrest.CoreMatchers.is;
2122
import static org.hamcrest.MatcherAssert.assertThat;
@@ -1164,4 +1165,12 @@ public void checkGlobalOpenTelemetryWhenNotInjected() {
11641165
.build();
11651166
assertEquals(GlobalOpenTelemetry.get(), options.getOpenTelemetry());
11661167
}
1168+
1169+
@Test
1170+
public void testCloudSpannerHostPattern() {
1171+
assertTrue(CLOUD_SPANNER_HOST_PATTERN.matcher("https://spanner.googleapis.com").matches());
1172+
assertTrue(
1173+
CLOUD_SPANNER_HOST_PATTERN.matcher("https://product-area.googleapis.com:443").matches());
1174+
assertFalse(CLOUD_SPANNER_HOST_PATTERN.matcher("https://some-company.com:443").matches());
1175+
}
11671176
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy