Refactoring SSL Context factory. Refactoring WorkloadAPIClient and X509Source
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
This commit is contained in:
parent
2cccc1c988
commit
219a2e2e71
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue