Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add mtls support to GoogleNetHttpTransport and GoogleApacheHttpTransport #1619

Merged
merged 33 commits into from Nov 10, 2020
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4205102
feat: add mtls support
arithmetic1728 Oct 23, 2020
fa08020
add run cert command
arithmetic1728 Oct 27, 2020
b2d8891
javanet
arithmetic1728 Oct 30, 2020
c7a0e71
Merge branch 'master' of https://github.com/googleapis/google-api-jav…
arithmetic1728 Oct 30, 2020
79e9ac1
add apache
arithmetic1728 Oct 30, 2020
f860389
update
arithmetic1728 Oct 30, 2020
43655ab
fix test
arithmetic1728 Oct 30, 2020
2849ee7
tmp
arithmetic1728 Oct 30, 2020
46d8973
fix
arithmetic1728 Oct 30, 2020
5e063b3
added tests
arithmetic1728 Oct 31, 2020
dceaacf
add apache
arithmetic1728 Oct 31, 2020
0f4f878
finished apache
arithmetic1728 Oct 31, 2020
9e33fac
doc string
arithmetic1728 Nov 1, 2020
547a6c5
Merge branch 'master' of https://github.com/googleapis/google-api-jav…
arithmetic1728 Nov 4, 2020
0442025
update code
arithmetic1728 Nov 4, 2020
d83c115
lint
arithmetic1728 Nov 4, 2020
d92d3b3
refactor: implement MtlsUtils.MtlsProvider interface with default imp…
chingor13 Nov 5, 2020
aa72510
Merge pull request #1 from chingor13/mtls-refactor
arithmetic1728 Nov 5, 2020
05e052c
refactor mtls to a module
arithmetic1728 Nov 5, 2020
312e73a
rename
arithmetic1728 Nov 5, 2020
5209bec
fix exceptions
arithmetic1728 Nov 5, 2020
1edf060
add timeout
arithmetic1728 Nov 6, 2020
daeb967
fix util test
arithmetic1728 Nov 6, 2020
a3cea68
Merge pull request #3 from arithmetic1728/refactor
arithmetic1728 Nov 6, 2020
377f944
add tests
arithmetic1728 Nov 6, 2020
533c76e
lint
arithmetic1728 Nov 6, 2020
9ad39a5
make constructor public
arithmetic1728 Nov 6, 2020
47f8c07
lint
arithmetic1728 Nov 6, 2020
7bf8d8c
make constructor public
arithmetic1728 Nov 6, 2020
55d4eb5
update
arithmetic1728 Nov 9, 2020
46dd6b8
use bigger try catch block
arithmetic1728 Nov 9, 2020
9eee919
update code
arithmetic1728 Nov 10, 2020
5593a90
Update google-api-client/src/main/java/com/google/api/client/googleap…
arithmetic1728 Nov 10, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -15,6 +15,8 @@
package com.google.api.client.googleapis.apache.v2;

import com.google.api.client.googleapis.GoogleUtils;
import com.google.api.client.googleapis.util.MtlsUtils;
import com.google.api.client.googleapis.util.Utils;
import com.google.api.client.http.apache.v2.ApacheHttpTransport;
import com.google.api.client.util.SslUtils;
import java.io.IOException;
Expand All @@ -24,7 +26,6 @@
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import org.apache.http.client.HttpClient;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.HttpClientBuilder;
Expand All @@ -39,11 +40,26 @@
public final class GoogleApacheHttpTransport {

/**
* Returns a new instance of {@link ApacheHttpTransport} that uses
* {@link GoogleUtils#getCertificateTrustStore()} for the trusted certificates.
* Returns a new instance of {@link ApacheHttpTransport} that uses {@link
* GoogleUtils#getCertificateTrustStore()} for the trusted certificates. If
* `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true", and the default
* client certificate key store from {@link Utils#loadDefaultMtlsKeyStore()} is not null, then the
* transport uses the default client certificate and is mutual TLS.
*/
public static ApacheHttpTransport newTrustedTransport() throws GeneralSecurityException,
IOException {
public static ApacheHttpTransport newTrustedTransport()
throws GeneralSecurityException, IOException {
return newTrustedTransport(MtlsUtils.getDefaultMtlsProvider());
}

static ApacheHttpTransport newTrustedTransport(MtlsUtils.MtlsProvider mtlsProvider)
throws GeneralSecurityException, IOException {
KeyStore mtlsKeyStore = null;
String mtlsKeyStorePassword = null;
if (mtlsProvider.useMtlsClientCertificate()) {
mtlsKeyStore = mtlsProvider.loadDefaultKeyStore();
mtlsKeyStorePassword = mtlsProvider.getKeyStorePassword();
}

PoolingHttpClientConnectionManager connectionManager =
new PoolingHttpClientConnectionManager(-1, TimeUnit.MILLISECONDS);

Expand All @@ -53,22 +69,35 @@ public static ApacheHttpTransport newTrustedTransport() throws GeneralSecurityEx
// Use the included trust store
KeyStore trustStore = GoogleUtils.getCertificateTrustStore();
SSLContext sslContext = SslUtils.getTlsSslContext();
SslUtils.initSslContext(sslContext, trustStore, SslUtils.getPkixTrustManagerFactory());

boolean isMtls = false;
if (mtlsKeyStore != null && mtlsKeyStorePassword != null) {
isMtls = true;
SslUtils.initSslContext(
sslContext,
trustStore,
SslUtils.getPkixTrustManagerFactory(),
mtlsKeyStore,
mtlsKeyStorePassword,
SslUtils.getDefaultKeyManagerFactory());
} else {
SslUtils.initSslContext(sslContext, trustStore, SslUtils.getPkixTrustManagerFactory());
}
LayeredConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);

HttpClient client = HttpClientBuilder.create()
.useSystemProperties()
.setSSLSocketFactory(socketFactory)
.setMaxConnTotal(200)
.setMaxConnPerRoute(20)
.setRoutePlanner(new SystemDefaultRoutePlanner(ProxySelector.getDefault()))
.setConnectionManager(connectionManager)
.disableRedirectHandling()
.disableAutomaticRetries()
.build();
return new ApacheHttpTransport(client);
HttpClient client =
HttpClientBuilder.create()
.useSystemProperties()
.setSSLSocketFactory(socketFactory)
.setMaxConnTotal(200)
.setMaxConnPerRoute(20)
.setRoutePlanner(new SystemDefaultRoutePlanner(ProxySelector.getDefault()))
.setConnectionManager(connectionManager)
.disableRedirectHandling()
.disableAutomaticRetries()
.build();
return new ApacheHttpTransport(client, isMtls);
}

private GoogleApacheHttpTransport() {
}
private GoogleApacheHttpTransport() {}
}
Expand Up @@ -15,6 +15,8 @@
package com.google.api.client.googleapis.javanet;

import com.google.api.client.googleapis.GoogleUtils;
import com.google.api.client.googleapis.util.MtlsUtils;
import com.google.api.client.googleapis.util.Utils;
import com.google.api.client.http.javanet.NetHttpTransport;
import java.io.IOException;
import java.security.GeneralSecurityException;
Expand All @@ -29,32 +31,50 @@
public class GoogleNetHttpTransport {

/**
* Returns a new instance of {@link NetHttpTransport} that uses
* {@link GoogleUtils#getCertificateTrustStore()} for the trusted certificates using
* {@link com.google.api.client.http.javanet.NetHttpTransport.Builder#trustCertificates(KeyStore)}
* .
* Returns a new instance of {@link NetHttpTransport} that uses {@link
* GoogleUtils#getCertificateTrustStore()} for the trusted certificates using {@link
* com.google.api.client.http.javanet.NetHttpTransport.Builder#trustCertificates(KeyStore)}. If
* `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is set to "true", and the default
* client certificate key store from {@link Utils#loadDefaultMtlsKeyStore()} is not null, then the
* transport uses the default client certificate and is mutual TLS.
*
* <p>
* This helper method doesn't provide for customization of the {@link NetHttpTransport}, such as
* the ability to specify a proxy. To do use, use
* {@link com.google.api.client.http.javanet.NetHttpTransport.Builder}, for example:
* </p>
* <p>This helper method doesn't provide for customization of the {@link NetHttpTransport}, such
* as the ability to specify a proxy. To do use, use {@link
* com.google.api.client.http.javanet.NetHttpTransport.Builder}, for example:
*
* <pre>
static HttpTransport newProxyTransport() throws GeneralSecurityException, IOException {
NetHttpTransport.Builder builder = new NetHttpTransport.Builder();
builder.trustCertificates(GoogleUtils.getCertificateTrustStore());
builder.setProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 3128)));
return builder.build();
}
* static HttpTransport newProxyTransport() throws GeneralSecurityException, IOException {
* NetHttpTransport.Builder builder = new NetHttpTransport.Builder();
* builder.trustCertificates(GoogleUtils.getCertificateTrustStore());
* builder.setProxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 3128)));
* return builder.build();
* }
* </pre>
*/
public static NetHttpTransport newTrustedTransport()
throws GeneralSecurityException, IOException {
return new NetHttpTransport.Builder().trustCertificates(GoogleUtils.getCertificateTrustStore())
.build();
return newTrustedTransport(MtlsUtils.getDefaultMtlsProvider());
}

private GoogleNetHttpTransport() {
static NetHttpTransport newTrustedTransport(MtlsUtils.MtlsProvider mtlsProvider)
throws GeneralSecurityException, IOException {
KeyStore mtlsKeyStore = null;
String mtlsKeyStorePassword = null;
if (mtlsProvider.useMtlsClientCertificate()) {
mtlsKeyStore = mtlsProvider.loadDefaultKeyStore();
mtlsKeyStorePassword = mtlsProvider.getKeyStorePassword();
}

if (mtlsKeyStore != null && mtlsKeyStorePassword != null) {
return new NetHttpTransport.Builder()
.trustCertificates(
GoogleUtils.getCertificateTrustStore(), mtlsKeyStore, mtlsKeyStorePassword)
.build();
}
return new NetHttpTransport.Builder()
.trustCertificates(GoogleUtils.getCertificateTrustStore())
.build();
}

private GoogleNetHttpTransport() {}
}
@@ -0,0 +1,150 @@
/*
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/

package com.google.api.client.googleapis.util;

import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonParser;
import com.google.api.client.util.Beta;
import com.google.api.client.util.Key;
import com.google.api.client.util.SecurityUtils;
import com.google.common.annotations.VisibleForTesting;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.util.List;

public class MtlsUtils {
arithmetic1728 marked this conversation as resolved.
Show resolved Hide resolved
public interface MtlsProvider {
boolean useMtlsClientCertificate();

String getKeyStorePassword();

KeyStore loadDefaultKeyStore() throws IOException, GeneralSecurityException;
arithmetic1728 marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* {@link Beta} <br>
* Data class representing context_aware_metadata.json file.
*
* @since 1.31
*/
@Beta
public static class ContextAwareMetadataJson extends GenericJson {
/** Cert provider command */
@Key("cert_provider_command")
private List<String> commands;

/**
* Returns the cert provider command.
*
* @since 1.31
*/
public final List<String> getCommands() {
return commands;
}
}

@VisibleForTesting
static class DefaultMtlsProvider implements MtlsProvider {
private static final String DEFAULT_CONTEXT_AWARE_METADATA_PATH =
System.getProperty("user.home") + "/.secureConnect/context_aware_metadata.json";

/** GOOGLE_API_USE_CLIENT_CERTIFICATE environment variable */
public static final String GOOGLE_API_USE_CLIENT_CERTIFICATE =
"GOOGLE_API_USE_CLIENT_CERTIFICATE";

interface EnvironmentProvider {
String getenv(String name);
}

static class SystemEnvironmentProvider implements EnvironmentProvider {
@Override
public String getenv(String name) {
return System.getenv(name);
}
}

DefaultMtlsProvider() {
this(new SystemEnvironmentProvider(), DEFAULT_CONTEXT_AWARE_METADATA_PATH);
}

private EnvironmentProvider envProvider;
private String metadataPath;

@VisibleForTesting
DefaultMtlsProvider(EnvironmentProvider envProvider, String metadataPath) {
this.envProvider = envProvider;
this.metadataPath = metadataPath;
}

@Override
public boolean useMtlsClientCertificate() {
String useClientCertificate = envProvider.getenv(GOOGLE_API_USE_CLIENT_CERTIFICATE);
return "true".equals(useClientCertificate);
}

@Override
public String getKeyStorePassword() {
return "";
}

@Override
public KeyStore loadDefaultKeyStore() throws IOException, GeneralSecurityException {
// Load the cert provider command from the json file.
InputStream stream;
try {
stream = new FileInputStream(metadataPath);
} catch (FileNotFoundException ignored) {
// file doesn't exist
return null;
}

List<String> command = extractCertificateProviderCommand(stream);

// Call the command.
Process process = new ProcessBuilder(command).start();
int exitCode = -1;
try {
exitCode = process.waitFor();
} catch (InterruptedException e) {
throw new IOException("Interrupted executing certificate provider command", e);
}
if (exitCode != 0) {
throw new IOException(
String.format("Failed to execute cert provider command with exit code: %d", exitCode));
}

// Parse input certificates from shell command
return SecurityUtils.createMtlsKeyStore(process.getInputStream());
}

@VisibleForTesting
static List<String> extractCertificateProviderCommand(InputStream contextAwareMetadata)
throws IOException {
JsonParser parser = Utils.getDefaultJsonFactory().createJsonParser(contextAwareMetadata);
ContextAwareMetadataJson json = parser.parse(ContextAwareMetadataJson.class);
return json.getCommands();
}
}

private static final MtlsProvider MTLS_PROVIDER = new DefaultMtlsProvider();

public static MtlsProvider getDefaultMtlsProvider() {
return MTLS_PROVIDER;
}
}
Expand Up @@ -17,8 +17,18 @@
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.JsonParser;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.Beta;
import com.google.api.client.util.SecurityUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.AccessControlException;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.util.List;

/**
* {@link Beta} <br/>
Expand All @@ -29,9 +39,7 @@
@Beta
public final class Utils {

/**
* Returns a cached default implementation of the JsonFactory interface.
*/
/** Returns a cached default implementation of the JsonFactory interface. */
public static JsonFactory getDefaultJsonFactory() {
return JsonFactoryInstanceHolder.INSTANCE;
}
Expand All @@ -44,9 +52,7 @@ private static class JsonFactoryInstanceHolder {
static final JsonFactory INSTANCE = new JacksonFactory();
}

/**
* Returns a cached default implementation of the HttpTransport interface.
*/
/** Returns a cached default implementation of the HttpTransport interface. */
public static HttpTransport getDefaultTransport() {
return TransportInstanceHolder.INSTANCE;
}
Expand All @@ -55,6 +61,5 @@ private static class TransportInstanceHolder {
static final HttpTransport INSTANCE = new NetHttpTransport();
}

private Utils() {
}
private Utils() {}
}