Skip to content

Commit 5972b5e

Browse files
Patrick Wrobeljackdingilian
Patrick Wrobel
authored andcommitted
feat: Test proxy support SSL backend
Change-Id: I622b1cca3b02d176beaef8a21dce8bab6b16a937
1 parent be62968 commit 5972b5e

File tree

3 files changed

+81
-85
lines changed

3 files changed

+81
-85
lines changed

test-proxy/src/main/java/com/google/cloud/bigtable/testproxy/CbtTestProxy.java

+48-72
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
import com.google.api.gax.retrying.RetrySettings;
2727
import com.google.api.gax.rpc.ApiException;
2828
import com.google.api.gax.rpc.ServerStream;
29-
import com.google.auth.oauth2.GoogleCredentials;
29+
import com.google.auth.oauth2.AccessToken;
30+
import com.google.auth.oauth2.OAuth2Credentials;
3031
import com.google.auto.value.AutoValue;
3132
import com.google.bigtable.v2.Column;
3233
import com.google.bigtable.v2.Family;
@@ -59,9 +60,6 @@
5960
import java.io.ByteArrayInputStream;
6061
import java.io.Closeable;
6162
import java.io.IOException;
62-
import java.nio.file.Files;
63-
import java.nio.file.Path;
64-
import java.nio.file.Paths;
6563
import java.util.Iterator;
6664
import java.util.LinkedHashMap;
6765
import java.util.List;
@@ -72,7 +70,6 @@
7270
import java.util.regex.Matcher;
7371
import java.util.regex.Pattern;
7472
import java.util.stream.Collectors;
75-
import javax.annotation.Nullable;
7673
import org.threeten.bp.Duration;
7774

7875
/** Java implementation of the CBT test proxy. Used to test the Java CBT client. */
@@ -95,50 +92,13 @@ static CbtClient create(BigtableDataSettings settings, BigtableDataClient dataCl
9592

9693
private static final Logger logger = Logger.getLogger(CbtTestProxy.class.getName());
9794

98-
private CbtTestProxy(
99-
boolean encrypted,
100-
@Nullable String rootCerts,
101-
@Nullable String sslTarget,
102-
@Nullable String credential) {
103-
this.encrypted = encrypted;
104-
this.rootCerts = rootCerts;
105-
this.sslTarget = sslTarget;
106-
this.credential = credential;
95+
private CbtTestProxy() {
10796
this.idClientMap = new ConcurrentHashMap<>();
10897
}
10998

110-
/**
111-
* Factory method to return a proxy instance that interacts with server unencrypted and
112-
* unauthenticated.
113-
*/
114-
public static CbtTestProxy createUnencrypted() {
115-
return new CbtTestProxy(false, null, null, null);
116-
}
117-
118-
/**
119-
* Factory method to return a proxy instance that interacts with server encrypted. Default
120-
* authority and public certificates are used if null values are passed in.
121-
*
122-
* @param rootCertsPemPath The path to a root certificate PEM file
123-
* @param sslTarget The override of SSL target name
124-
* @param credentialJsonPath The path to a credential JSON file
125-
*/
126-
public static CbtTestProxy createEncrypted(
127-
@Nullable String rootCertsPemPath,
128-
@Nullable String sslTarget,
129-
@Nullable String credentialJsonPath)
130-
throws IOException {
131-
String tmpRootCerts = null, tmpCredential = null;
132-
if (rootCertsPemPath != null) {
133-
Path file = Paths.get(rootCertsPemPath);
134-
tmpRootCerts = new String(Files.readAllBytes(file), UTF_8);
135-
}
136-
if (credentialJsonPath != null) {
137-
Path file = Paths.get(credentialJsonPath);
138-
tmpCredential = new String(Files.readAllBytes(file), UTF_8);
139-
}
140-
141-
return new CbtTestProxy(true, tmpRootCerts, sslTarget, tmpCredential);
99+
/** Factory method to return a proxy instance. */
100+
public static CbtTestProxy create() {
101+
return new CbtTestProxy();
142102
}
143103

144104
/**
@@ -196,15 +156,21 @@ public synchronized void createClient(
196156
Preconditions.checkArgument(!request.getProjectId().isEmpty(), "project id must be provided");
197157
Preconditions.checkArgument(!request.getInstanceId().isEmpty(), "instance id must be provided");
198158
Preconditions.checkArgument(!request.getDataTarget().isEmpty(), "data target must be provided");
159+
Preconditions.checkArgument(
160+
!request.getSecurityOptions().getUseSsl()
161+
|| !request.getSecurityOptions().getSslRootCertsPemBytes().isEmpty(),
162+
"security_options.ssl_root_certs_pem must be provided if security_options.use_ssl is true");
199163

200-
if (idClientMap.contains(request.getClientId())) {
164+
if (idClientMap.containsKey(request.getClientId())) {
201165
responseObserver.onError(
202166
Status.ALREADY_EXISTS
203167
.withDescription("Client " + request.getClientId() + " already exists.")
204168
.asException());
205169
return;
206170
}
207171

172+
// setRefreshingChannel is needed for now.
173+
@SuppressWarnings("deprecation")
208174
BigtableDataSettings.Builder settingsBuilder =
209175
BigtableDataSettings.newBuilder()
210176
// Disable channel refreshing when not using the real server
@@ -213,9 +179,6 @@ public synchronized void createClient(
213179
.setInstanceId(request.getInstanceId())
214180
.setAppProfileId(request.getAppProfileId());
215181

216-
settingsBuilder.stubSettings().setEnableRoutingCookie(false);
217-
settingsBuilder.stubSettings().setEnableRetryInfo(false);
218-
219182
if (request.hasPerOperationTimeout()) {
220183
Duration newTimeout = Duration.ofMillis(Durations.toMillis(request.getPerOperationTimeout()));
221184
settingsBuilder = overrideTimeoutSetting(newTimeout, settingsBuilder);
@@ -249,8 +212,13 @@ public synchronized void createClient(
249212
settingsBuilder
250213
.stubSettings()
251214
.setEndpoint(request.getDataTarget())
252-
.setTransportChannelProvider(getTransportChannel())
253-
.setCredentialsProvider(getCredentialsProvider());
215+
.setTransportChannelProvider(
216+
getTransportChannel(
217+
request.getSecurityOptions().getUseSsl(),
218+
request.getSecurityOptions().getSslRootCertsPem(),
219+
request.getSecurityOptions().getSslEndpointOverride()))
220+
.setCredentialsProvider(
221+
getCredentialsProvider(request.getSecurityOptions().getAccessToken()));
254222
}
255223
BigtableDataSettings settings = settingsBuilder.build();
256224
BigtableDataClient client = BigtableDataClient.create(settings);
@@ -780,52 +748,60 @@ private static String extractTableIdFromTableName(String fullTableName)
780748
return matcher.group(3);
781749
}
782750

783-
private InstantiatingGrpcChannelProvider getTransportChannel() throws IOException {
751+
@SuppressWarnings("rawtypes")
752+
private InstantiatingGrpcChannelProvider getTransportChannel(
753+
boolean encrypted, String rootCertsPem, String sslTarget) {
784754
if (!encrypted) {
785755
return EnhancedBigtableStubSettings.defaultGrpcTransportProviderBuilder()
786756
.setChannelConfigurator(ManagedChannelBuilder::usePlaintext)
787757
.build();
788758
}
789759

790-
if (rootCerts == null) {
791-
return EnhancedBigtableStubSettings.defaultGrpcTransportProviderBuilder().build();
760+
final SslContext sslContext;
761+
if (rootCertsPem.isEmpty()) {
762+
sslContext = null;
763+
} else {
764+
try {
765+
sslContext =
766+
GrpcSslContexts.forClient()
767+
.trustManager(new ByteArrayInputStream(rootCertsPem.getBytes(UTF_8)))
768+
.build();
769+
} catch (IOException e) {
770+
throw new IllegalArgumentException(e);
771+
}
792772
}
793773

794-
final SslContext secureContext =
795-
GrpcSslContexts.forClient()
796-
.trustManager(new ByteArrayInputStream(rootCerts.getBytes(UTF_8)))
797-
.build();
798774
return EnhancedBigtableStubSettings.defaultGrpcTransportProviderBuilder()
799775
.setChannelConfigurator(
800776
new ApiFunction<ManagedChannelBuilder, ManagedChannelBuilder>() {
801777
@Override
802778
public ManagedChannelBuilder apply(ManagedChannelBuilder input) {
803779
NettyChannelBuilder channelBuilder = (NettyChannelBuilder) input;
804-
channelBuilder.sslContext(secureContext).overrideAuthority(sslTarget);
780+
781+
if (sslContext != null) {
782+
channelBuilder.sslContext(sslContext);
783+
}
784+
785+
if (!sslTarget.isEmpty()) {
786+
channelBuilder.overrideAuthority(sslTarget);
787+
}
788+
805789
return channelBuilder;
806790
}
807791
})
808792
.build();
809793
}
810794

811-
private CredentialsProvider getCredentialsProvider() throws IOException {
812-
if (credential == null) {
795+
private CredentialsProvider getCredentialsProvider(String accessToken) {
796+
if (accessToken.isEmpty()) {
813797
return NoCredentialsProvider.create();
814798
}
815799

816-
final GoogleCredentials creds =
817-
GoogleCredentials.fromStream(new ByteArrayInputStream(credential.getBytes(UTF_8)));
818-
819-
return FixedCredentialsProvider.create(creds);
800+
return FixedCredentialsProvider.create(
801+
OAuth2Credentials.create(new AccessToken(accessToken, null)));
820802
}
821803

822804
private final ConcurrentHashMap<String, CbtClient> idClientMap;
823-
private final boolean encrypted;
824-
825-
// Parameters that may be needed when "encrypted" is true.
826-
private final String rootCerts;
827-
private final String sslTarget;
828-
private final String credential;
829805

830806
private static final Pattern tablePattern =
831807
Pattern.compile("projects/([^/]+)/instances/([^/]+)/tables/([^/]+)");

test-proxy/src/main/java/com/google/cloud/bigtable/testproxy/CbtTestProxyMain.java

+1-13
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,7 @@ public static void main(String[] args) throws InterruptedException, IOException
3232
throw new IllegalArgumentException(String.format("Port %d is not > 0.", port));
3333
}
3434

35-
CbtTestProxy cbtTestProxy;
36-
37-
// If encryption is specified
38-
boolean encrypted = Boolean.getBoolean("encrypted");
39-
if (encrypted) {
40-
String rootCertsPemPath = System.getProperty("root.certs.pem.path");
41-
String sslTarget = System.getProperty("ssl.target");
42-
String credentialJsonPath = System.getProperty("credential.json.path");
43-
cbtTestProxy = CbtTestProxy.createEncrypted(rootCertsPemPath, sslTarget, credentialJsonPath);
44-
} else {
45-
cbtTestProxy = CbtTestProxy.createUnencrypted();
46-
}
47-
35+
CbtTestProxy cbtTestProxy = CbtTestProxy.create();
4836
logger.info(String.format("Test proxy starting on %d", port));
4937
ServerBuilder.forPort(port).addService(cbtTestProxy).build().start().awaitTermination();
5038
}

test-proxy/src/main/proto/test_proxy.proto

+32
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,27 @@ enum OptionalFeatureConfig {
3838

3939
// Request to test proxy service to create a client object.
4040
message CreateClientRequest {
41+
message SecurityOptions {
42+
// Access token to use for client credentials. If empty, the client will not
43+
// use any call credentials. Certain implementations may require `use_ssl`
44+
// to be set when using this.
45+
string access_token = 1;
46+
47+
// Whether to use SSL channel credentials when connecting to the data
48+
// endpoint.
49+
bool use_ssl = 2;
50+
51+
// If using SSL channel credentials, override the SSL endpoint to match the
52+
// host that is specified in the backend's certificate. Also sets the
53+
// client's authority header value.
54+
string ssl_endpoint_override = 3;
55+
56+
// PEM encoding of the server root certificates. If not set, the default
57+
// root certs will be used instead. The default can be overridden via the
58+
// GRPC_DEFAULT_SSL_ROOTS_FILE_PATH env var.
59+
string ssl_root_certs_pem = 4;
60+
}
61+
4162
// A unique ID associated with the client object to be created.
4263
string client_id = 1;
4364

@@ -66,6 +87,17 @@ message CreateClientRequest {
6687
// Optional config that dictates how the optional features should be enabled
6788
// during the client creation. Please check the enum type's docstring above.
6889
OptionalFeatureConfig optional_feature_config = 7;
90+
91+
// Options to allow connecting to backends with channel and/or call
92+
// credentials. This is needed internally by Cloud Bigtable's own testing
93+
// frameworks.It is not necessary to support these fields for client
94+
// conformance testing.
95+
//
96+
// WARNING: this allows the proxy to connect to a real production
97+
// CBT backend with the right options, however, the proxy itself is insecure
98+
// so it is not recommended to use it with real credentials or outside testing
99+
// contexts.
100+
SecurityOptions security_options = 8;
69101
}
70102

71103
// Response from test proxy service for CreateClientRequest.

0 commit comments

Comments
 (0)