Refactoring SSL Context factory. Refactoring WorkloadAPIClient and X509Source

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
This commit is contained in:
Max Lambrecht 2020-04-22 10:01:55 -03:00
parent 2cccc1c988
commit 219a2e2e71
11 changed files with 274 additions and 210 deletions

View File

@ -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;

View File

@ -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<Context.CancellableContext> 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<WorkloadApiClient, String> newClient() {
val options = ClientOptions.builder().build();
return newClient(options);
}
/**
* Creates a new Workload API Client.
* <p>
* 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<WorkloadApiClient, String> 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<JwtSvid, String> 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<JwtSvid, String> 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;
}
}
}

View File

@ -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.
* <p>
* 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.
* <p>
* 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<X509Source, String> newSource() {
X509SourceOptions x509SourceOptions = new X509SourceOptions();
X509SourceOptions x509SourceOptions = X509SourceOptions.builder().build();
return newSource(x509SourceOptions);
}
public static Result<X509Source, String> newSource(@NonNull String spiffeSocketPath) {
X509SourceOptions options = X509SourceOptions
.builder()
.spiffeSocketPath(spiffeSocketPath)
.build();
return newSource(options);
}
public static Result<X509Source, String> 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.
* <p>
* 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<X509Source, String> newSource(@NonNull WorkloadApiClient workloadApiClient) {
val x509Source = new X509Source(workloadApiClient);
public static Result<X509Source, String> 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, String> 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<Boolean, String> 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<WorkloadApiClient, String> createClient(@NonNull X509Source.@NonNull X509SourceOptions options) {
Result<WorkloadApiClient, String> 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<Boolean, String> init() {
Result<X509Context, String> 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<X509Context>() {
@Override
public void OnUpdate(X509Context update) {
log.log(Level.INFO, "Received X509Context update");
setX509Context(update);
countDownLatch.countDown();
}
@Override
public void OnError(Error<X509Context, String> 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<List<X509Svid>, X509Svid> picker;
WorkloadApiClient workloadApiClient;
@Builder
public X509SourceOptions(String spiffeSocketPath, Function<List<X509Svid>, X509Svid> picker) {
public X509SourceOptions(String spiffeSocketPath, Function<List<X509Svid>, X509Svid> picker, WorkloadApiClient workloadApiClient) {
this.spiffeSocketPath = spiffeSocketPath;
this.picker = picker;
}
public X509SourceOptions() {
this.spiffeSocketPath = null;
this.picker = null;
this.workloadApiClient = workloadApiClient;
}
}
}

View File

@ -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);

View File

@ -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> 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();
}

View File

@ -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 <code>SpiffeConstants.SOCKET_ENV_VARIABLE</code> 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<SSLContext, String> 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<Result<List<SpiffeId>, 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<Result<List<SpiffeId>, String>> acceptedSpiffeIdsSupplier) {
try {
val sslContext = SSLContext.getInstance(DEFAULT_SSL_PROTOCOL);
Result<X509Source, String> 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<Result<List<SpiffeId>, 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<Result<List<SpiffeId>, String>> acceptedSpiffeIdsSupplier;
@Builder
public SslContextOptions(
String sslProtocol,
X509Source x509Source,
Supplier<Result<List<SpiffeId>, String>> acceptedSpiffeIdsSupplier) {
this.x509Source = x509Source;
this.acceptedSpiffeIdsSupplier = acceptedSpiffeIdsSupplier;
this.sslProtocol = sslProtocol;
}
}
}

View File

@ -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() {

View File

@ -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<Result<List<SpiffeId>, String>> acceptedSpiffeIdsSupplier) {
Supplier<Result<List<SpiffeId>, String>> spiffeIdsSupplier;
if (acceptedSpiffeIdsSupplier != null) {
spiffeIdsSupplier = acceptedSpiffeIdsSupplier;
} else {
spiffeIdsSupplier = this::getAcceptedSpiffeIds;
}
val spiffeTrustManager =
new SpiffeTrustManager(
x509BundleSource,
acceptedSpiffeIdsSupplier
spiffeIdsSupplier
);
return new TrustManager[]{spiffeTrustManager};
}

View File

@ -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, String> 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<List<SpiffeId>, String> listOfSpiffeIds() {

View File

@ -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, String> 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();
}
}

View File

@ -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();