From 219a2e2e71b799b4558e8516b232bfc8b361d39e Mon Sep 17 00:00:00 2001 From: Max Lambrecht Date: Wed, 22 Apr 2020 10:01:55 -0300 Subject: [PATCH] Refactoring SSL Context factory. Refactoring WorkloadAPIClient and X509Source Signed-off-by: Max Lambrecht --- .../main/java/spiffe/workloadapi/Address.java | 1 - .../spiffe/workloadapi/WorkloadApiClient.java | 96 ++++++++++--- .../java/spiffe/workloadapi/X509Source.java | 129 ++++++++---------- .../internal/GrpcManagedChannelFactory.java | 2 +- .../java/spiffe/helper/KeyStoreHelper.java | 31 ++--- .../provider/SpiffeSslContextFactory.java | 120 ++++++---------- .../provider/SpiffeSslSocketFactory.java | 13 +- .../provider/SpiffeTrustManagerFactory.java | 32 ++++- .../spiffe/provider/examples/HttpsClient.java | 32 ++++- .../spiffe/provider/examples/HttpsServer.java | 21 ++- .../provider/examples/WorkloadThread.java | 7 +- 11 files changed, 274 insertions(+), 210 deletions(-) diff --git a/java-spiffe-core/src/main/java/spiffe/workloadapi/Address.java b/java-spiffe-core/src/main/java/spiffe/workloadapi/Address.java index fe27d09..09d6aec 100644 --- a/java-spiffe-core/src/main/java/spiffe/workloadapi/Address.java +++ b/java-spiffe-core/src/main/java/spiffe/workloadapi/Address.java @@ -105,7 +105,6 @@ public class Address { private static String parseIp(String host) { try { InetAddress ip = InetAddress.getByName(host); - System.out.println(ip); return ip.getHostAddress(); } catch (UnknownHostException e) { return null; diff --git a/java-spiffe-core/src/main/java/spiffe/workloadapi/WorkloadApiClient.java b/java-spiffe-core/src/main/java/spiffe/workloadapi/WorkloadApiClient.java index 78d7e82..6301995 100644 --- a/java-spiffe-core/src/main/java/spiffe/workloadapi/WorkloadApiClient.java +++ b/java-spiffe-core/src/main/java/spiffe/workloadapi/WorkloadApiClient.java @@ -3,7 +3,10 @@ package spiffe.workloadapi; import io.grpc.Context; import io.grpc.ManagedChannel; import io.grpc.stub.StreamObserver; +import lombok.*; +import lombok.extern.java.Log; import org.apache.commons.lang3.NotImplementedException; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import spiffe.bundle.jwtbundle.JwtBundleSet; import spiffe.result.Result; @@ -18,7 +21,10 @@ import spiffe.workloadapi.internal.SpiffeWorkloadAPIGrpc.SpiffeWorkloadAPIStub; import java.io.Closeable; import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; +import java.util.List; import static spiffe.workloadapi.internal.Workload.X509SVIDRequest; import static spiffe.workloadapi.internal.Workload.X509SVIDResponse; @@ -30,35 +36,67 @@ import static spiffe.workloadapi.internal.Workload.X509SVIDResponse; * Multiple WorkloadApiClients can be created for the same SPIFFE Socket Path, * they will share a common ManagedChannel. */ +@Log public class WorkloadApiClient implements Closeable { private final SpiffeWorkloadAPIStub workloadApiAsyncStub; private final SpiffeWorkloadAPIBlockingStub workloadApiBlockingStub; private final ManagedChannel managedChannel; + private final List cancellableContexts; private WorkloadApiClient(SpiffeWorkloadAPIStub workloadApiAsyncStub, SpiffeWorkloadAPIBlockingStub workloadApiBlockingStub, ManagedChannel managedChannel) { this.workloadApiAsyncStub = workloadApiAsyncStub; this.workloadApiBlockingStub = workloadApiBlockingStub; this.managedChannel = managedChannel; + this.cancellableContexts = Collections.synchronizedList(new ArrayList<>()); + } + + /** + * Creates a new WorkloadAPI client using the Default Address from the Environment Variable + * + * @return a Result containing a WorklaodAPI client + */ + public static Result newClient() { + val options = ClientOptions.builder().build(); + return newClient(options); } /** * Creates a new Workload API Client. + *

+ * If the SPIFFE Socket Path is not provided, it uses the Default Address from + * the Environment variable for creating the client. * - * @param spiffeSocketPath where the WorkloadAPI is listening. + * @param options {@link ClientOptions} + * @return a Result containing a WorklaodAPI client */ - public static WorkloadApiClient newClient(URI spiffeSocketPath) { - ManagedChannel managedChannel = GrpcManagedChannelFactory.newChannel(spiffeSocketPath); + public static Result newClient(@NonNull ClientOptions options) { - SpiffeWorkloadAPIStub workloadAPIAsyncStub = SpiffeWorkloadAPIGrpc - .newStub(managedChannel) - .withInterceptors(new SecurityHeaderInterceptor()); + String spiffeSocketPath; + if (StringUtils.isNotBlank(options.spiffeSocketPath)) { + spiffeSocketPath = options.spiffeSocketPath; + } else { + spiffeSocketPath = Address.getDefaultAddress(); + } - SpiffeWorkloadAPIBlockingStub workloadAPIBlockingStub = SpiffeWorkloadAPIGrpc + val parseResult = Address.parseAddress(spiffeSocketPath); + if (parseResult.isError()) { + return Result.error(parseResult.getError()); + } + + URI parsedAddress = parseResult.getValue(); + val managedChannel = GrpcManagedChannelFactory.newChannel(parsedAddress); + + val workloadAPIAsyncStub = SpiffeWorkloadAPIGrpc + .newStub(managedChannel) + .withInterceptors(new SecurityHeaderInterceptor()); + + val workloadAPIBlockingStub = SpiffeWorkloadAPIGrpc .newBlockingStub(managedChannel) .withInterceptors(new SecurityHeaderInterceptor()); - return new WorkloadApiClient(workloadAPIAsyncStub, workloadAPIBlockingStub, managedChannel); + val workloadApiClient = new WorkloadApiClient(workloadAPIAsyncStub, workloadAPIBlockingStub, managedChannel); + return Result.ok(workloadApiClient); } /** @@ -71,7 +109,7 @@ public class WorkloadApiClient implements Closeable { try { result = cancellableContext.call(this::processX509Context); } catch (Exception e) { - return Result.error("Error fetching X509Context: %s", e.getMessage()); + return Result.error("Error fetching X509Context: %s %n %s", e.getMessage(), ExceptionUtils.getStackTrace(e)); } // close connection cancellableContext.close(); @@ -85,7 +123,7 @@ public class WorkloadApiClient implements Closeable { return GrpcConversionUtils.toX509Context(x509SVIDResponse.next()); } } catch (Exception e) { - return Result.error("Error processing X509Context: %s", e.getMessage()); + return Result.error("Error processing X509Context: %s %n %s", e.getMessage(), ExceptionUtils.getStackTrace(e)); } return Result.error("Could not get X509Context"); } @@ -108,7 +146,7 @@ public class WorkloadApiClient implements Closeable { @Override public void onError(Throwable t) { - String error = String.format("Error getting X509Context update: %s", ExceptionUtils.getStackTrace(t)); + String error = String.format("Error getting X509Context update: %s %n %s", t.getMessage(), ExceptionUtils.getStackTrace(t)); watcher.OnError(Result.error(error)); } @@ -117,20 +155,23 @@ public class WorkloadApiClient implements Closeable { watcher.OnError(Result.error("Unexpected completed stream.")); } }; - workloadApiAsyncStub.fetchX509SVID(newX509SvidRequest(), streamObserver); + Context.CancellableContext cancellableContext; + cancellableContext = Context.current().withCancellation(); + cancellableContext.run(() -> workloadApiAsyncStub.fetchX509SVID(newX509SvidRequest(), streamObserver)); + this.cancellableContexts.add(cancellableContext); } /** * One-shot fetch call to get a SPIFFE JWT-SVID. * - * @param subject a SPIFFE ID - * @param audience the audience of the JWT-SVID + * @param subject a SPIFFE ID + * @param audience the audience of the JWT-SVID * @param extraAudience the extra audience for the JWT_SVID * @return an {@link spiffe.result.Ok} containing the JWT SVID, or an {@link spiffe.result.Error} * if the JwtSvid could not be fetched. */ public Result fetchJwtSvid(SpiffeId subject, String audience, String... extraAudience) { - throw new NotImplementedException("Not implemented"); + throw new NotImplementedException("Not implemented"); } /** @@ -147,9 +188,8 @@ public class WorkloadApiClient implements Closeable { * Validates the JWT-SVID token. The parsed and validated * JWT-SVID is returned. * - * @param token JWT token + * @param token JWT token * @param audience audience of the JWT - * * @return the JwtSvid if the token and audience could be validated. */ public Result validateJwtSvid(String token, String audience) { @@ -171,6 +211,26 @@ public class WorkloadApiClient implements Closeable { @Override public void close() { - managedChannel.shutdown(); + log.info("Closing WorkloadAPI client"); + synchronized (this) { + for (val context : cancellableContexts) { + context.close(); + } + log.info("Shutting down Managed Channel"); + managedChannel.shutdown(); + } + } + + /** + * Options for creating a new {@link WorkloadApiClient}. + */ + @Data + public static class ClientOptions { + String spiffeSocketPath; + + @Builder + public ClientOptions(String spiffeSocketPath) { + this.spiffeSocketPath = spiffeSocketPath; + } } } diff --git a/java-spiffe-core/src/main/java/spiffe/workloadapi/X509Source.java b/java-spiffe-core/src/main/java/spiffe/workloadapi/X509Source.java index 661f2e8..41823d0 100644 --- a/java-spiffe-core/src/main/java/spiffe/workloadapi/X509Source.java +++ b/java-spiffe-core/src/main/java/spiffe/workloadapi/X509Source.java @@ -1,8 +1,10 @@ package spiffe.workloadapi; -import lombok.*; +import lombok.Builder; +import lombok.Data; +import lombok.NonNull; import lombok.extern.java.Log; -import org.apache.commons.lang3.exception.ExceptionUtils; +import lombok.val; import spiffe.bundle.x509bundle.X509Bundle; import spiffe.bundle.x509bundle.X509BundleSet; import spiffe.bundle.x509bundle.X509BundleSource; @@ -13,9 +15,7 @@ import spiffe.svid.x509svid.X509Svid; import spiffe.svid.x509svid.X509SvidSource; import java.io.Closeable; -import java.net.URI; import java.util.List; -import java.util.concurrent.CountDownLatch; import java.util.function.Function; import java.util.logging.Level; @@ -44,83 +44,63 @@ public class X509Source implements X509SvidSource, X509BundleSource, Closeable { * Creates a new X509Source. It blocks until the initial update * has been received from the Workload API. *

- * It uses the default Address Env variable to get the Workload API endpoint address. + * It uses the Default Address from the Environment variable to get the Workload API endpoint address. + *

+ * It uses the default X509-SVID. * * @return an initialized an {@link spiffe.result.Ok} with X509Source, or an {@link Error} in * case the X509Source could not be initialized. */ public static Result newSource() { - X509SourceOptions x509SourceOptions = new X509SourceOptions(); + X509SourceOptions x509SourceOptions = X509SourceOptions.builder().build(); return newSource(x509SourceOptions); } - public static Result newSource(@NonNull String spiffeSocketPath) { - X509SourceOptions options = X509SourceOptions - .builder() - .spiffeSocketPath(spiffeSocketPath) - .build(); - return newSource(options); - } - - public static Result newSource(@NonNull X509SourceOptions options) { - - WorkloadApiClient workloadApiClient; - String address; - URI parsedAddress; - - if (options.spiffeSocketPath != null) { - address = options.spiffeSocketPath; - } else { - address = Address.getDefaultAddress(); - } - - val parseResult = Address.parseAddress(address); - if (parseResult.isError()) { - return Result.error(parseResult.getError()); - } - - parsedAddress = parseResult.getValue(); - workloadApiClient = WorkloadApiClient.newClient(parsedAddress); - - val x509Source = new X509Source(); - x509Source.picker = options.picker; - x509Source.workloadApiClient = workloadApiClient; - - try { - x509Source.init(); - } catch (RuntimeException e) { - return Result.error("Error creating X509 Source: %s %n %s", e.getMessage(), ExceptionUtils.getStackTrace(e)); - } - - return Result.ok(x509Source); - } - /** - * Creates a new X509Source using the {@link WorkloadApiClient} provided. It blocks until the initial update + * Creates a new X509Source. It blocks until the initial update * has been received from the Workload API. + *

+ * The {@link WorkloadApiClient} can be provided in the options, if it is not, + * a new client is created. * - * @param workloadApiClient a {@link WorkloadApiClient} + * @param options {@link X509SourceOptions} * @return an initialized an {@link spiffe.result.Ok} with X509Source, or an {@link Error} in * case the X509Source could not be initialized. */ - public static Result newSource(@NonNull WorkloadApiClient workloadApiClient) { - val x509Source = new X509Source(workloadApiClient); + public static Result newSource(@NonNull X509SourceOptions options) { - try { - x509Source.init(); - } catch (RuntimeException e) { - return Result.error("Error creating X509 Source: %s %n %s", e.getMessage(), ExceptionUtils.getStackTrace(e)); + if (options.workloadApiClient == null) { + Result workloadApiClient = createClient(options); + if (workloadApiClient.isError()) { + return Result.error(workloadApiClient.getError()); + } + options.workloadApiClient = workloadApiClient.getValue(); + } + + val x509Source = new X509Source(); + x509Source.picker = options.picker; + x509Source.workloadApiClient = options.workloadApiClient; + + Result init = x509Source.init(); + if (init.isError()) { + x509Source.close(); + return Result.error("Error creating X509 Source: %s", init.getError()); } return Result.ok(x509Source); } - private X509Source(@NonNull WorkloadApiClient workloadApiClient) { - this.workloadApiClient = workloadApiClient; + private static Result createClient(@NonNull X509Source.@NonNull X509SourceOptions options) { + Result workloadApiClient; + val clientOptions= WorkloadApiClient.ClientOptions + .builder() + .spiffeSocketPath(options.spiffeSocketPath) + .build(); + workloadApiClient = WorkloadApiClient.newClient(clientOptions); + return workloadApiClient; } private X509Source() { - } /** @@ -169,25 +149,28 @@ public class X509Source implements X509SvidSource, X509BundleSource, Closeable { } } - @SneakyThrows - private void init() { - CountDownLatch countDownLatch = new CountDownLatch(1); - setX509ContextWatcher(countDownLatch); - countDownLatch.await(); + + private Result init() { + Result x509Context = workloadApiClient.fetchX509Context(); + if (x509Context.isError()) { + return Result.error(x509Context.getError()); + } + setX509Context(x509Context.getValue()); + setX509ContextWatcher(); + return Result.ok(true); } - private void setX509ContextWatcher(CountDownLatch countDownLatch) { + private void setX509ContextWatcher() { workloadApiClient.watchX509Context(new Watcher() { @Override public void OnUpdate(X509Context update) { log.log(Level.INFO, "Received X509Context update"); setX509Context(update); - countDownLatch.countDown(); } @Override public void OnError(Error error) { - throw new RuntimeException(error.getError()); + log.log(Level.SEVERE, String.format("Error in X509Context watcher: %s", error.getError())); } }); } @@ -214,20 +197,20 @@ public class X509Source implements X509SvidSource, X509BundleSource, Closeable { } } - @Value + /** + * Options for creating a new {@link X509Source} + */ + @Data public static class X509SourceOptions { String spiffeSocketPath; Function, X509Svid> picker; + WorkloadApiClient workloadApiClient; @Builder - public X509SourceOptions(String spiffeSocketPath, Function, X509Svid> picker) { + public X509SourceOptions(String spiffeSocketPath, Function, X509Svid> picker, WorkloadApiClient workloadApiClient) { this.spiffeSocketPath = spiffeSocketPath; this.picker = picker; - } - - public X509SourceOptions() { - this.spiffeSocketPath = null; - this.picker = null; + this.workloadApiClient = workloadApiClient; } } } diff --git a/java-spiffe-core/src/main/java/spiffe/workloadapi/internal/GrpcManagedChannelFactory.java b/java-spiffe-core/src/main/java/spiffe/workloadapi/internal/GrpcManagedChannelFactory.java index e713d12..3045991 100644 --- a/java-spiffe-core/src/main/java/spiffe/workloadapi/internal/GrpcManagedChannelFactory.java +++ b/java-spiffe-core/src/main/java/spiffe/workloadapi/internal/GrpcManagedChannelFactory.java @@ -26,7 +26,7 @@ public class GrpcManagedChannelFactory { * @return a instance of a ManagedChannel. */ public static ManagedChannel newChannel(@NonNull URI address) { - if (address.getScheme().equals("unix")) { + if ("unix".equals(address.getScheme())) { return createNativeSocketChannel(address); } else { return createTcpChannel(address); diff --git a/java-spiffe-helper/src/main/java/spiffe/helper/KeyStoreHelper.java b/java-spiffe-helper/src/main/java/spiffe/helper/KeyStoreHelper.java index 93f4d7e..56b6ac4 100644 --- a/java-spiffe-helper/src/main/java/spiffe/helper/KeyStoreHelper.java +++ b/java-spiffe-helper/src/main/java/spiffe/helper/KeyStoreHelper.java @@ -8,9 +8,10 @@ import lombok.val; import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.StringUtils; import spiffe.result.Error; -import spiffe.workloadapi.Address; +import spiffe.result.Result; import spiffe.workloadapi.Watcher; import spiffe.workloadapi.WorkloadApiClient; +import spiffe.workloadapi.WorkloadApiClient.ClientOptions; import spiffe.workloadapi.X509Context; import java.nio.file.Path; @@ -75,35 +76,19 @@ public class KeyStoreHelper { setupX509ContextFetcher(); } - // Use spiffeSocketPath from Env Variable and KeyStoreType.PKCS12 as default - public KeyStoreHelper( - @NonNull final Path keyStoreFilePath, - @NonNull final char[] keyStorePassword, - @NonNull final char[] privateKeyPassword, - @NonNull final String privateKeyAlias) { - - this(keyStoreFilePath, KeyStoreType.PKCS12, keyStorePassword, privateKeyPassword, privateKeyAlias, null); - } - @SneakyThrows private void setupX509ContextFetcher() { + Result workloadApiClient; - String address; if (StringUtils.isNotBlank(spiffeSocketPath)) { - address = spiffeSocketPath; - } else { - address = Address.getDefaultAddress(); + ClientOptions clientOptions = ClientOptions.builder().spiffeSocketPath(spiffeSocketPath).build(); + workloadApiClient = WorkloadApiClient.newClient(clientOptions); + } else { + workloadApiClient = WorkloadApiClient.newClient(); } - val parseResult = Address.parseAddress(address); - if (parseResult.isError()) { - // panic - throw new RuntimeException(parseResult.getError()); - } - - val workloadApiClient = WorkloadApiClient.newClient(parseResult.getValue()); CountDownLatch countDownLatch = new CountDownLatch(1); - setX509ContextWatcher(workloadApiClient, countDownLatch); + setX509ContextWatcher(workloadApiClient.getValue(), countDownLatch); countDownLatch.await(); } diff --git a/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeSslContextFactory.java b/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeSslContextFactory.java index cd68312..06131ba 100644 --- a/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeSslContextFactory.java +++ b/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeSslContextFactory.java @@ -1,11 +1,12 @@ package spiffe.provider; -import lombok.val; -import spiffe.bundle.x509bundle.X509BundleSource; +import lombok.Builder; +import lombok.Data; +import lombok.NonNull; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; import spiffe.result.Result; import spiffe.spiffeid.SpiffeId; -import spiffe.svid.x509svid.X509SvidSource; -import spiffe.workloadapi.Address; import spiffe.workloadapi.X509Source; import javax.net.ssl.SSLContext; @@ -27,97 +28,54 @@ public final class SpiffeSslContextFactory { * Creates an SSLContext initialized with a SPIFFE KeyManager and TrustManager that are backed by * the Workload API via a X509Source. * - * The TrustManager uses {@link spiffe.svid.x509svid.X509SvidValidator} to validate chain and check the SPIFFE ID, - * and {@link spiffe.spiffeid.SpiffeIdUtils} to get the list of accepted SPIFFE IDs from a System variable. + * @param options {@link SslContextOptions}. The option {@link X509Source} must be not null. + * If the option acceptedSpiffeIdsSupplier is not provided, the list of accepted SPIFFE IDs + * is read from the Security Property ssl.spiffe.accept. + * If the sslProcotol is not provided, the default TLSv1.2 is used. * - * @implNote the environment variable SpiffeConstants.SOCKET_ENV_VARIABLE should be set with - * the path to the Workload API endpoint. - * - * @return a SSLContext + * @return a Result containing a SSLContext */ - public static SSLContext getSslContext() { + public static Result getSslContext(@NonNull SslContextOptions options) { try { - val sslContext = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL); - sslContext.init( - new SpiffeKeyManagerFactory().engineGetKeyManagers(), - new SpiffeTrustManagerFactory().engineGetTrustManagers(), - null); - return sslContext; - } catch (NoSuchAlgorithmException | KeyManagementException e) { - throw new IllegalStateException(e); - } - } + SSLContext sslContext; + if (StringUtils.isNotBlank(options.sslProtocol)) { + sslContext = SSLContext.getInstance(options.sslProtocol); + } else { + sslContext = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL); + } - /** - * Creates an SSLContext initialized with a SPIFFE KeyManager and TrustManager, - * providing a supplier of the SPIFFE IDs that will be accepted during peer's SVID validation, - * and using an environment variable to get the Path to the SPIFFE Socket endpoint. - * - * @param acceptedSpiffeIdsSupplier a supplier of a list of accepted SPIFFE IDs - * @return an SSLContext initialized with a SpiffeKeyManager and a SpiffeTrustManager. - */ - public static SSLContext getSslContext(Supplier, String>> acceptedSpiffeIdsSupplier) { - val spiffeSocketPath = System.getenv(Address.SOCKET_ENV_VARIABLE); - return getSslContext(spiffeSocketPath, acceptedSpiffeIdsSupplier); - - } - - /** - * Creates an SSLContext initialized with a SPIFFE KeyManager and TrustManager, - * specifying the Path where the Workload API is listening, and a supplier of - * the SPIFFE IDs that will be accepted during peer's SVID validation. - * - * @param spiffeSocketPath a Path to the Workload API endpoint - * @param acceptedSpiffeIdsSupplier a supplier of a list of accepted SPIFFE IDs - * @return an SSLContext initialized with a SpiffeKeyManager and a SpiffeTrustManager. - */ - public static SSLContext getSslContext( - String spiffeSocketPath, - Supplier, String>> acceptedSpiffeIdsSupplier) { - try { - val sslContext = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL); - Result x509Source = X509Source.newSource(spiffeSocketPath); - if (x509Source.isError()) { - throw new RuntimeException(x509Source.getError()); + if (options.x509Source == null) { + return Result.error("x509Source option cannot be null, a X509 Source must be provided"); } sslContext.init( - new SpiffeKeyManagerFactory().engineGetKeyManagers(x509Source.getValue()), - new SpiffeTrustManagerFactory() - .engineGetTrustManagers( - x509Source.getValue(), - acceptedSpiffeIdsSupplier), + new SpiffeKeyManagerFactory().engineGetKeyManagers(options.x509Source), + new SpiffeTrustManagerFactory().engineGetTrustManagers(options.x509Source, options.acceptedSpiffeIdsSupplier), null); - return sslContext; + + return Result.ok(sslContext); } catch (NoSuchAlgorithmException | KeyManagementException e) { - throw new IllegalStateException(e); + return Result.error("Error creating SSL Context: %s %n %s", e.getMessage(), ExceptionUtils.getStackTrace(e)); } } /** - * Creates an SSLContext initialized with a SPIFFE KeyManager and TrustManager. - * - * @param x509SvidSource a {@link X509SvidSource} to provide the X509-SVIDs - * @param x509BundleSource a {@link X509BundleSource} to provide Bundles to validate SVIDs - * @param acceptedSpiffeIdsSupplier a supplier of a list of accepted SPIFFE IDs - * @return an SSLContext + * Options for creating a new SslContext. */ - public static SSLContext getSslContext( - X509SvidSource x509SvidSource, - X509BundleSource x509BundleSource, - Supplier, String>> acceptedSpiffeIdsSupplier) { - try { - val sslContext = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL); - sslContext.init( - new SpiffeKeyManagerFactory().engineGetKeyManagers(x509SvidSource), - new SpiffeTrustManagerFactory() - .engineGetTrustManagers( - x509BundleSource, - acceptedSpiffeIdsSupplier), - null); - return sslContext; - } catch (NoSuchAlgorithmException | KeyManagementException e) { - throw new IllegalStateException(e); + @Data + public static class SslContextOptions { + String sslProtocol; + X509Source x509Source; + Supplier, String>> acceptedSpiffeIdsSupplier; + + @Builder + public SslContextOptions( + String sslProtocol, + X509Source x509Source, + Supplier, String>> acceptedSpiffeIdsSupplier) { + this.x509Source = x509Source; + this.acceptedSpiffeIdsSupplier = acceptedSpiffeIdsSupplier; + this.sslProtocol = sslProtocol; } } } diff --git a/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeSslSocketFactory.java b/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeSslSocketFactory.java index d65338d..75c1d45 100644 --- a/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeSslSocketFactory.java +++ b/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeSslSocketFactory.java @@ -1,5 +1,8 @@ package spiffe.provider; +import lombok.val; +import spiffe.provider.SpiffeSslContextFactory.SslContextOptions; + import javax.net.ssl.SSLSocketFactory; import java.io.IOException; import java.net.InetAddress; @@ -12,7 +15,15 @@ import java.net.Socket; */ public class SpiffeSslSocketFactory extends SSLSocketFactory { - private final SSLSocketFactory delegate = SpiffeSslContextFactory.getSslContext().getSocketFactory(); + private final SSLSocketFactory delegate; + + public SpiffeSslSocketFactory(SslContextOptions contextOptions) { + val sslContext = SpiffeSslContextFactory.getSslContext(contextOptions); + if (sslContext.isError()) { + throw new RuntimeException(sslContext.getError()); + } + delegate = sslContext.getValue().getSocketFactory(); + } @Override public String[] getDefaultCipherSuites() { diff --git a/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeTrustManagerFactory.java b/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeTrustManagerFactory.java index 62481e1..ce905ab 100644 --- a/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeTrustManagerFactory.java +++ b/java-spiffe-provider/src/main/java/spiffe/provider/SpiffeTrustManagerFactory.java @@ -32,9 +32,11 @@ public class SpiffeTrustManagerFactory extends TrustManagerFactorySpi { private static final String SSL_SPIFFE_ACCEPT_PROPERTY = "ssl.spiffe.accept"; /** - * Default method for creating a TrustManager initializing it with the X509BundleSource instance - * and with and a supplier of accepted SPIFFE IDs. that reads the list - * from the System Property defined in {@link spiffe.SpiffeConstants}.SSL_SPIFFE_ACCEPT_PROPERTY. + * Default method for creating a TrustManager initializing it with + * the {@link spiffe.workloadapi.X509Source} instance + * that is handled by the {@link X509SourceManager}, and + * with and a supplier of accepted SPIFFE IDs. that reads the list + * from the System Property defined in SSL_SPIFFE_ACCEPT_PROPERTY. * * @return a TrustManager array with an initialized TrustManager. */ @@ -48,6 +50,22 @@ public class SpiffeTrustManagerFactory extends TrustManagerFactorySpi { return new TrustManager[]{spiffeTrustManager}; } + /** + * Creates a TrustManager initializing it with the X509BundleSource instance + * and with and a supplier of accepted SPIFFE IDs. that reads the list + * from the System Property defined in SSL_SPIFFE_ACCEPT_PROPERTY. + * + * @return a TrustManager array with an initialized TrustManager. + */ + public TrustManager[] engineGetTrustManagers(X509BundleSource x509BundleSource) { + val spiffeTrustManager = + new SpiffeTrustManager( + x509BundleSource, + this::getAcceptedSpiffeIds + ); + return new TrustManager[]{spiffeTrustManager}; + } + /** * Creates a TrustManager initializing it with a X509BundleSource to get the bundles, * with a function verify a chain of certificates using a to validate the SPIFFE IDs @@ -61,10 +79,16 @@ public class SpiffeTrustManagerFactory extends TrustManagerFactorySpi { X509BundleSource x509BundleSource, Supplier, String>> acceptedSpiffeIdsSupplier) { + Supplier, String>> spiffeIdsSupplier; + if (acceptedSpiffeIdsSupplier != null) { + spiffeIdsSupplier = acceptedSpiffeIdsSupplier; + } else { + spiffeIdsSupplier = this::getAcceptedSpiffeIds; + } val spiffeTrustManager = new SpiffeTrustManager( x509BundleSource, - acceptedSpiffeIdsSupplier + spiffeIdsSupplier ); return new TrustManager[]{spiffeTrustManager}; } diff --git a/java-spiffe-provider/src/main/java/spiffe/provider/examples/HttpsClient.java b/java-spiffe-provider/src/main/java/spiffe/provider/examples/HttpsClient.java index f9f67c6..56bdddf 100644 --- a/java-spiffe-provider/src/main/java/spiffe/provider/examples/HttpsClient.java +++ b/java-spiffe-provider/src/main/java/spiffe/provider/examples/HttpsClient.java @@ -1,8 +1,12 @@ package spiffe.provider.examples; +import lombok.val; import spiffe.provider.SpiffeSslContextFactory; +import spiffe.provider.SpiffeSslContextFactory.SslContextOptions; import spiffe.result.Result; import spiffe.spiffeid.SpiffeId; +import spiffe.workloadapi.X509Source; +import spiffe.workloadapi.X509Source.X509SourceOptions; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocket; @@ -41,12 +45,32 @@ public class HttpsClient { } void run() throws IOException { - SSLContext sslContext = SpiffeSslContextFactory - .getSslContext(spiffeSocket, acceptedSpiffeIdsListSupplier); - SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory(); + val sourceOptions = X509SourceOptions + .builder() + .spiffeSocketPath(spiffeSocket) + .build(); + val x509Source = X509Source.newSource(sourceOptions); + if (x509Source.isError()) { + throw new RuntimeException(x509Source.getError()); + } + + SslContextOptions sslContextOptions = SslContextOptions + .builder() + .acceptedSpiffeIdsSupplier(acceptedSpiffeIdsListSupplier) + .x509Source(x509Source.getValue()) + .build(); + Result sslContext = SpiffeSslContextFactory + .getSslContext(sslContextOptions); + + if (sslContext.isError()) { + throw new RuntimeException(sslContext.getError()); + } + + SSLSocketFactory sslSocketFactory = sslContext.getValue().getSocketFactory(); SSLSocket sslSocket = (SSLSocket) sslSocketFactory.createSocket("localhost", serverPort); - new WorkloadThread(sslSocket).start(); + + new WorkloadThread(sslSocket, x509Source.getValue()).start(); } static Result, String> listOfSpiffeIds() { diff --git a/java-spiffe-provider/src/main/java/spiffe/provider/examples/HttpsServer.java b/java-spiffe-provider/src/main/java/spiffe/provider/examples/HttpsServer.java index 1bd3f37..208fbf7 100644 --- a/java-spiffe-provider/src/main/java/spiffe/provider/examples/HttpsServer.java +++ b/java-spiffe-provider/src/main/java/spiffe/provider/examples/HttpsServer.java @@ -1,6 +1,10 @@ package spiffe.provider.examples; +import lombok.val; import spiffe.provider.SpiffeSslContextFactory; +import spiffe.provider.SpiffeSslContextFactory.SslContextOptions; +import spiffe.result.Result; +import spiffe.workloadapi.X509Source; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; @@ -34,17 +38,28 @@ public class HttpsServer { } void run() throws IOException { + val x509Source = X509Source.newSource(); + if (x509Source.isError()) { + throw new RuntimeException(x509Source.getError()); + } - SSLContext sslContext = SpiffeSslContextFactory.getSslContext(); + val sslContextOptions = SslContextOptions + .builder() + .x509Source(x509Source.getValue()) + .build(); + Result sslContext = SpiffeSslContextFactory.getSslContext(sslContextOptions); + if (sslContext.isError()) { + throw new RuntimeException(sslContext.getError()); + } - SSLServerSocketFactory sslServerSocketFactory = sslContext.getServerSocketFactory(); + SSLServerSocketFactory sslServerSocketFactory = sslContext.getValue().getServerSocketFactory(); SSLServerSocket sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(port); // Server will validate Client chain and SPIFFE ID sslServerSocket.setNeedClientAuth(true); SSLSocket sslSocket = (SSLSocket) sslServerSocket.accept(); - new WorkloadThread(sslSocket).start(); + new WorkloadThread(sslSocket, x509Source.getValue()).start(); } } diff --git a/java-spiffe-provider/src/main/java/spiffe/provider/examples/WorkloadThread.java b/java-spiffe-provider/src/main/java/spiffe/provider/examples/WorkloadThread.java index b392bc7..d856749 100644 --- a/java-spiffe-provider/src/main/java/spiffe/provider/examples/WorkloadThread.java +++ b/java-spiffe-provider/src/main/java/spiffe/provider/examples/WorkloadThread.java @@ -2,6 +2,7 @@ package spiffe.provider.examples; import spiffe.internal.CertificateUtils; import spiffe.spiffeid.SpiffeId; +import spiffe.workloadapi.X509Source; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; @@ -10,10 +11,12 @@ import java.security.cert.X509Certificate; class WorkloadThread extends Thread { + private final X509Source x509Source; private SSLSocket sslSocket; - WorkloadThread(SSLSocket sslSocket) { + WorkloadThread(SSLSocket sslSocket, X509Source x509Source) { this.sslSocket = sslSocket; + this.x509Source = x509Source; } public void run() { @@ -48,8 +51,10 @@ class WorkloadThread extends Thread { String line; while ((line = bufferedReader.readLine()) != null) { System.out.println("Message received: " + line); + break; } + x509Source.close(); sslSocket.close(); } catch (Exception ex) { ex.printStackTrace();