Skip to content

Commit

Permalink
Remote: Support client TLS authentication for remote caches over HTTP
Browse files Browse the repository at this point in the history
Fixes #14397.

This change augments the `SslContext` created for TLS HTTP remote caches to include the client certificate, client key, and optional root CA as specified on the command line with `--tls_client_certificate`, `--tls_client_key`, and `--tls_certificate`, respectively.

The implementation proposes propagating `AuthAndTLSOptions` through `RemoteCacheClientFactory` and `HttpCacheClient` in order to expose the relevant file paths to the logic that creates the SSL context for use by the HTTP remote cache client.

Closes #14398.

PiperOrigin-RevId: 416354592
  • Loading branch information
LINKIWI authored and Copybara-Service committed Dec 14, 2021
1 parent 7074bcf commit aaf65b9
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 40 deletions.
Expand Up @@ -18,6 +18,7 @@
import com.google.common.base.Ascii;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions;
import com.google.devtools.build.lib.remote.common.RemoteCacheClient;
import com.google.devtools.build.lib.remote.disk.DiskAndRemoteCacheClient;
import com.google.devtools.build.lib.remote.disk.DiskCacheClient;
Expand Down Expand Up @@ -55,16 +56,17 @@ public static RemoteCacheClient createDiskAndRemoteClient(
public static RemoteCacheClient create(
RemoteOptions options,
@Nullable Credentials creds,
AuthAndTLSOptions authAndTlsOptions,
Path workingDirectory,
DigestUtil digestUtil)
throws IOException {
Preconditions.checkNotNull(workingDirectory, "workingDirectory");
if (isHttpCache(options) && isDiskCache(options)) {
return createDiskAndHttpCache(
workingDirectory, options.diskCache, options, creds, digestUtil);
workingDirectory, options.diskCache, options, creds, authAndTlsOptions, digestUtil);
}
if (isHttpCache(options)) {
return createHttp(options, creds, digestUtil);
return createHttp(options, creds, authAndTlsOptions, digestUtil);
}
if (isDiskCache(options)) {
return createDiskCache(
Expand All @@ -80,7 +82,10 @@ public static boolean isRemoteCacheOptions(RemoteOptions options) {
}

private static RemoteCacheClient createHttp(
RemoteOptions options, Credentials creds, DigestUtil digestUtil) {
RemoteOptions options,
Credentials creds,
AuthAndTLSOptions authAndTlsOptions,
DigestUtil digestUtil) {
Preconditions.checkNotNull(options.remoteCache, "remoteCache");

try {
Expand All @@ -99,7 +104,8 @@ private static RemoteCacheClient createHttp(
options.remoteVerifyDownloads,
ImmutableList.copyOf(options.remoteHeaders),
digestUtil,
creds);
creds,
authAndTlsOptions);
} else {
throw new Exception("Remote cache proxy unsupported: " + options.remoteProxy);
}
Expand All @@ -111,7 +117,8 @@ private static RemoteCacheClient createHttp(
options.remoteVerifyDownloads,
ImmutableList.copyOf(options.remoteHeaders),
digestUtil,
creds);
creds,
authAndTlsOptions);
}
} catch (Exception e) {
throw new RuntimeException(e);
Expand All @@ -137,6 +144,7 @@ private static RemoteCacheClient createDiskAndHttpCache(
PathFragment diskCachePath,
RemoteOptions options,
Credentials cred,
AuthAndTLSOptions authAndTlsOptions,
DigestUtil digestUtil)
throws IOException {
Path cacheDir =
Expand All @@ -145,7 +153,7 @@ private static RemoteCacheClient createDiskAndHttpCache(
cacheDir.createDirectoryAndParents();
}

RemoteCacheClient httpCache = createHttp(options, cred, digestUtil);
RemoteCacheClient httpCache = createHttp(options, cred, authAndTlsOptions, digestUtil);
return createDiskAndRemoteClient(
workingDirectory,
diskCachePath,
Expand Down
Expand Up @@ -231,6 +231,7 @@ private void initHttpAndDiskCache(
RemoteCacheClientFactory.create(
remoteOptions,
creds,
authAndTlsOptions,
Preconditions.checkNotNull(env.getWorkingDirectory(), "workingDirectory"),
digestUtil);
} catch (IOException e) {
Expand Down
Expand Up @@ -19,6 +19,7 @@ java_library(
],
deps = [
"//src/main/java/com/google/devtools/build/lib/analysis:blaze_version_info",
"//src/main/java/com/google/devtools/build/lib/authandtls",
"//src/main/java/com/google/devtools/build/lib/remote/common",
"//src/main/java/com/google/devtools/build/lib/remote/util",
"//src/main/java/com/google/devtools/build/lib/vfs",
Expand Down
Expand Up @@ -24,6 +24,7 @@
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions;
import com.google.devtools.build.lib.remote.common.CacheNotFoundException;
import com.google.devtools.build.lib.remote.common.RemoteActionExecutionContext;
import com.google.devtools.build.lib.remote.common.RemoteCacheClient;
Expand Down Expand Up @@ -68,6 +69,7 @@
import io.netty.handler.timeout.WriteTimeoutException;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
Expand Down Expand Up @@ -151,7 +153,8 @@ public static HttpCacheClient create(
boolean verifyDownloads,
ImmutableList<Entry<String, String>> extraHttpHeaders,
DigestUtil digestUtil,
@Nullable final Credentials creds)
@Nullable final Credentials creds,
AuthAndTLSOptions authAndTlsOptions)
throws Exception {
return new HttpCacheClient(
NioEventLoopGroup::new,
Expand All @@ -163,6 +166,7 @@ public static HttpCacheClient create(
extraHttpHeaders,
digestUtil,
creds,
authAndTlsOptions,
null);
}

Expand All @@ -174,7 +178,8 @@ public static HttpCacheClient create(
boolean verifyDownloads,
ImmutableList<Entry<String, String>> extraHttpHeaders,
DigestUtil digestUtil,
@Nullable final Credentials creds)
@Nullable final Credentials creds,
AuthAndTLSOptions authAndTlsOptions)
throws Exception {

if (KQueue.isAvailable()) {
Expand All @@ -188,6 +193,7 @@ public static HttpCacheClient create(
extraHttpHeaders,
digestUtil,
creds,
authAndTlsOptions,
domainSocketAddress);
} else if (Epoll.isAvailable()) {
return new HttpCacheClient(
Expand All @@ -200,6 +206,7 @@ public static HttpCacheClient create(
extraHttpHeaders,
digestUtil,
creds,
authAndTlsOptions,
domainSocketAddress);
} else {
throw new Exception("Unix domain sockets are unsupported on this platform");
Expand All @@ -216,6 +223,7 @@ private HttpCacheClient(
ImmutableList<Entry<String, String>> extraHttpHeaders,
DigestUtil digestUtil,
@Nullable final Credentials creds,
AuthAndTLSOptions authAndTlsOptions,
@Nullable SocketAddress socketAddress)
throws Exception {
useTls = uri.getScheme().equals("https");
Expand All @@ -236,15 +244,7 @@ private HttpCacheClient(
socketAddress = new InetSocketAddress(uri.getHost(), uri.getPort());
}

final SslContext sslCtx;
if (useTls) {
// OpenSsl gives us a > 2x speed improvement on fast networks, but requires netty tcnative
// to be there which is not available on all platforms and environments.
SslProvider sslProvider = OpenSsl.isAvailable() ? SslProvider.OPENSSL : SslProvider.JDK;
sslCtx = SslContextBuilder.forClient().sslProvider(sslProvider).build();
} else {
sslCtx = null;
}
final SslContext sslCtx = useTls ? createSSLContext(authAndTlsOptions) : null;
final int port = uri.getPort();
final String hostname = uri.getHost();
this.eventLoop = newEventLoopGroup.apply(2);
Expand All @@ -269,6 +269,10 @@ public void channelCreated(Channel ch) {
if (sslCtx != null) {
SSLEngine engine = sslCtx.newEngine(ch.alloc(), hostname, port);
engine.setUseClientMode(true);
if (authAndTlsOptions.tlsClientCertificate != null
&& authAndTlsOptions.tlsClientKey != null) {
engine.setNeedClientAuth(true);
}
p.addFirst("ssl-handler", new SslHandler(engine));
}
}
Expand Down Expand Up @@ -773,4 +777,28 @@ private void refreshCredentials() throws IOException {
}
}
}

private static SslContext createSSLContext(AuthAndTLSOptions authAndTlsOptions)
throws IOException {
// OpenSsl gives us a > 2x speed improvement on fast networks, but requires netty tcnative
// to be there which is not available on all platforms and environments.
SslProvider sslProvider = OpenSsl.isAvailable() ? SslProvider.OPENSSL : SslProvider.JDK;
SslContextBuilder sslContextBuilder = SslContextBuilder.forClient().sslProvider(sslProvider);

// Root CA certificate
if (authAndTlsOptions.tlsCertificate != null) {
sslContextBuilder =
sslContextBuilder.trustManager(new File(authAndTlsOptions.tlsCertificate));
}

// Optional client TLS authentication
if (authAndTlsOptions.tlsClientCertificate != null && authAndTlsOptions.tlsClientKey != null) {
sslContextBuilder =
sslContextBuilder.keyManager(
new File(authAndTlsOptions.tlsClientCertificate),
new File(authAndTlsOptions.tlsClientKey));
}

return sslContextBuilder.build();
}
}
Expand Up @@ -17,6 +17,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;

import com.google.devtools.build.lib.authandtls.AuthAndTLSOptions;
import com.google.devtools.build.lib.clock.JavaClock;
import com.google.devtools.build.lib.remote.common.RemoteCacheClient;
import com.google.devtools.build.lib.remote.disk.DiskAndRemoteCacheClient;
Expand All @@ -42,6 +43,7 @@ public class RemoteCacheClientFactoryTest {
private final DigestUtil digestUtil = new DigestUtil(DigestHashFunction.SHA256);

private RemoteOptions remoteOptions;
private final AuthAndTLSOptions authAndTlsOptions = Options.getDefaults(AuthAndTLSOptions.class);
private Path workingDirectory;
private InMemoryFileSystem fs;

Expand All @@ -60,7 +62,7 @@ public void createCombinedCacheWithExistingWorkingDirectory() throws IOException

RemoteCacheClient blobStore =
RemoteCacheClientFactory.create(
remoteOptions, /* creds= */ null, workingDirectory, digestUtil);
remoteOptions, /* creds= */ null, authAndTlsOptions, workingDirectory, digestUtil);

assertThat(blobStore).isInstanceOf(DiskAndRemoteCacheClient.class);
}
Expand All @@ -73,7 +75,7 @@ public void createCombinedCacheWithNotExistingWorkingDirectory() throws IOExcept

RemoteCacheClient blobStore =
RemoteCacheClientFactory.create(
remoteOptions, /* creds= */ null, workingDirectory, digestUtil);
remoteOptions, /* creds= */ null, authAndTlsOptions, workingDirectory, digestUtil);

assertThat(blobStore).isInstanceOf(DiskAndRemoteCacheClient.class);
assertThat(workingDirectory.exists()).isTrue();
Expand All @@ -89,7 +91,11 @@ public void createCombinedCacheWithMissingWorkingDirectoryShouldThrowException()
NullPointerException.class,
() ->
RemoteCacheClientFactory.create(
remoteOptions, /* creds= */ null, /* workingDirectory= */ null, digestUtil));
remoteOptions,
/* creds= */ null,
authAndTlsOptions,
/* workingDirectory= */ null,
digestUtil));
}

@Test
Expand All @@ -99,7 +105,7 @@ public void createHttpCacheWithProxy() throws IOException {

RemoteCacheClient blobStore =
RemoteCacheClientFactory.create(
remoteOptions, /* creds= */ null, workingDirectory, digestUtil);
remoteOptions, /* creds= */ null, authAndTlsOptions, workingDirectory, digestUtil);

assertThat(blobStore).isInstanceOf(HttpCacheClient.class);
}
Expand All @@ -114,7 +120,11 @@ public void createHttpCacheFailsWithUnsupportedProxyProtocol() {
RuntimeException.class,
() ->
RemoteCacheClientFactory.create(
remoteOptions, /* creds= */ null, workingDirectory, digestUtil)))
remoteOptions,
/* creds= */ null,
authAndTlsOptions,
workingDirectory,
digestUtil)))
.hasMessageThat()
.contains("Remote cache proxy unsupported: bad-proxy");
}
Expand All @@ -125,7 +135,7 @@ public void createHttpCacheWithoutProxy() throws IOException {

RemoteCacheClient blobStore =
RemoteCacheClientFactory.create(
remoteOptions, /* creds= */ null, workingDirectory, digestUtil);
remoteOptions, /* creds= */ null, authAndTlsOptions, workingDirectory, digestUtil);

assertThat(blobStore).isInstanceOf(HttpCacheClient.class);
}
Expand All @@ -136,7 +146,7 @@ public void createDiskCache() throws IOException {

RemoteCacheClient blobStore =
RemoteCacheClientFactory.create(
remoteOptions, /* creds= */ null, workingDirectory, digestUtil);
remoteOptions, /* creds= */ null, authAndTlsOptions, workingDirectory, digestUtil);

assertThat(blobStore).isInstanceOf(DiskCacheClient.class);
}
Expand Down
2 changes: 2 additions & 0 deletions src/test/java/com/google/devtools/build/lib/remote/http/BUILD
Expand Up @@ -22,10 +22,12 @@ java_test(
],
test_class = "com.google.devtools.build.lib.AllTests",
deps = [
"//src/main/java/com/google/devtools/build/lib/authandtls",
"//src/main/java/com/google/devtools/build/lib/remote/common",
"//src/main/java/com/google/devtools/build/lib/remote/http",
"//src/main/java/com/google/devtools/build/lib/remote/util",
"//src/main/java/com/google/devtools/build/lib/vfs",
"//src/main/java/com/google/devtools/common/options",
"//src/test/java/com/google/devtools/build/lib:test_runner",
"//src/tools/remote/src/main/java/com/google/devtools/build/remote/worker/http",
"//third_party:auth",
Expand Down

0 comments on commit aaf65b9

Please sign in to comment.