Introduce JwtSource interface and refactor JWT Source implementation.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
This commit is contained in:
parent
e9df15e44b
commit
2fcaf752d7
|
|
@ -0,0 +1,257 @@
|
||||||
|
package io.spiffe.workloadapi;
|
||||||
|
|
||||||
|
import io.spiffe.bundle.jwtbundle.JwtBundle;
|
||||||
|
import io.spiffe.bundle.jwtbundle.JwtBundleSet;
|
||||||
|
import io.spiffe.bundle.x509bundle.X509Bundle;
|
||||||
|
import io.spiffe.exception.BundleNotFoundException;
|
||||||
|
import io.spiffe.exception.JwtSourceException;
|
||||||
|
import io.spiffe.exception.JwtSvidException;
|
||||||
|
import io.spiffe.exception.SocketEndpointAddressException;
|
||||||
|
import io.spiffe.exception.WatcherException;
|
||||||
|
import io.spiffe.spiffeid.SpiffeId;
|
||||||
|
import io.spiffe.spiffeid.TrustDomain;
|
||||||
|
import io.spiffe.svid.jwtsvid.JwtSvid;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.java.Log;
|
||||||
|
import lombok.val;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import static io.spiffe.workloadapi.internal.ThreadUtils.await;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a source of SPIFFE JWT SVIDs and JWT bundles maintained via the Workload API.
|
||||||
|
*/
|
||||||
|
@Log
|
||||||
|
public class DefaultJwtSource implements JwtSource {
|
||||||
|
|
||||||
|
static final String TIMEOUT_SYSTEM_PROPERTY = "spiffe.newJwtSource.timeout";
|
||||||
|
|
||||||
|
static final Duration DEFAULT_TIMEOUT =
|
||||||
|
Duration.parse(System.getProperty(TIMEOUT_SYSTEM_PROPERTY, "PT0S"));
|
||||||
|
|
||||||
|
private JwtBundleSet bundles;
|
||||||
|
|
||||||
|
private final WorkloadApiClient workloadApiClient;
|
||||||
|
private volatile boolean closed;
|
||||||
|
|
||||||
|
// private constructor
|
||||||
|
private DefaultJwtSource(final WorkloadApiClient workloadApiClient) {
|
||||||
|
this.workloadApiClient = workloadApiClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new JWT source. It blocks until the initial update with the JWT bundles
|
||||||
|
* has been received from the Workload API or until the timeout configured
|
||||||
|
* through the system property `spiffe.newJwtSource.timeout` expires.
|
||||||
|
* If no timeout is configured, it blocks until it gets a JWT update from the Workload API.
|
||||||
|
* <p>
|
||||||
|
* It uses the default address socket endpoint from the environment variable to get the Workload API address.
|
||||||
|
*
|
||||||
|
* @return an instance of {@link DefaultJwtSource}, with the JWT bundles initialized
|
||||||
|
* @throws SocketEndpointAddressException if the address to the Workload API is not valid
|
||||||
|
* @throws JwtSourceException if the source could not be initialized
|
||||||
|
*/
|
||||||
|
public static JwtSource newSource() throws JwtSourceException, SocketEndpointAddressException {
|
||||||
|
JwtSourceOptions options = JwtSourceOptions.builder().initTimeout(DEFAULT_TIMEOUT).build();
|
||||||
|
return newSource(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new JWT source. It blocks until the initial update with the JWT bundles
|
||||||
|
* has been received from the Workload API, doing retries with a backoff exponential policy,
|
||||||
|
* or until the initTimeout has expired.
|
||||||
|
* <p>
|
||||||
|
* If the timeout is not provided in the options, the default timeout is read from the
|
||||||
|
* system property `spiffe.newJwtSource.timeout`. If none is configured, this method will
|
||||||
|
* block until the JWT bundles can be retrieved from the Workload API.
|
||||||
|
* <p>
|
||||||
|
* The {@link WorkloadApiClient} can be provided in the options, if it is not,
|
||||||
|
* a new client is created.
|
||||||
|
*
|
||||||
|
* @param options {@link JwtSourceOptions}
|
||||||
|
* @return an instance of {@link DefaultJwtSource}, with the JWT bundles initialized
|
||||||
|
* @throws SocketEndpointAddressException if the address to the Workload API is not valid
|
||||||
|
* @throws JwtSourceException if the source could not be initialized
|
||||||
|
*/
|
||||||
|
public static JwtSource newSource(@NonNull final JwtSourceOptions options)
|
||||||
|
throws SocketEndpointAddressException, JwtSourceException {
|
||||||
|
if (options.workloadApiClient == null) {
|
||||||
|
options.workloadApiClient = createClient(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.initTimeout == null) {
|
||||||
|
options.initTimeout = DEFAULT_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultJwtSource jwtSource = new DefaultJwtSource(options.workloadApiClient);
|
||||||
|
|
||||||
|
try {
|
||||||
|
jwtSource.init(options.initTimeout);
|
||||||
|
} catch (Exception e) {
|
||||||
|
jwtSource.close();
|
||||||
|
throw new JwtSourceException("Error creating JWT source", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return jwtSource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JwtSvid fetchJwtSvid(String audience, String... extraAudiences) throws JwtSvidException {
|
||||||
|
if (isClosed()) {
|
||||||
|
throw new IllegalStateException("JWT SVID source is closed");
|
||||||
|
}
|
||||||
|
return workloadApiClient.fetchJwtSvid(audience, extraAudiences);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a new JWT SVID from the Workload API for the given subject SPIFFE ID and audiences.
|
||||||
|
*
|
||||||
|
* @return a {@link JwtSvid}
|
||||||
|
* @throws IllegalStateException if the source is closed
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public JwtSvid fetchJwtSvid(final SpiffeId subject, final String audience, final String... extraAudiences)
|
||||||
|
throws JwtSvidException {
|
||||||
|
if (isClosed()) {
|
||||||
|
throw new IllegalStateException("JWT SVID source is closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return workloadApiClient.fetchJwtSvid(subject, audience, extraAudiences);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the JWT bundle for a given trust domain.
|
||||||
|
*
|
||||||
|
* @return an instance of a {@link X509Bundle}
|
||||||
|
*
|
||||||
|
* @throws BundleNotFoundException is there is no bundle for the trust domain provided
|
||||||
|
* @throws IllegalStateException if the source is closed
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public JwtBundle getBundleForTrustDomain(@NonNull final TrustDomain trustDomain) throws BundleNotFoundException {
|
||||||
|
if (isClosed()) {
|
||||||
|
throw new IllegalStateException("JWT bundle source is closed");
|
||||||
|
}
|
||||||
|
return bundles.getBundleForTrustDomain(trustDomain);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes this source, dropping the connection to the Workload API.
|
||||||
|
* Other source methods will return an error after close has been called.
|
||||||
|
* <p>
|
||||||
|
* It is marked with {@link SneakyThrows} because it is not expected to throw
|
||||||
|
* the checked exception defined on the {@link Closeable} interface.
|
||||||
|
*/
|
||||||
|
@SneakyThrows
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (!closed) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (!closed) {
|
||||||
|
workloadApiClient.close();
|
||||||
|
closed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(final Duration timeout) throws TimeoutException {
|
||||||
|
CountDownLatch done = new CountDownLatch(1);
|
||||||
|
setJwtBundlesWatcher(done);
|
||||||
|
|
||||||
|
boolean success;
|
||||||
|
if (timeout.isZero()) {
|
||||||
|
await(done);
|
||||||
|
success = true;
|
||||||
|
} else {
|
||||||
|
success = await(done, timeout.getSeconds(), TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
if (!success) {
|
||||||
|
throw new TimeoutException("Timeout waiting for JWT bundles update");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setJwtBundlesWatcher(final CountDownLatch done) {
|
||||||
|
workloadApiClient.watchJwtBundles(new Watcher<JwtBundleSet>() {
|
||||||
|
@Override
|
||||||
|
public void onUpdate(final JwtBundleSet update) {
|
||||||
|
log.log(Level.INFO, "Received JwtBundleSet update");
|
||||||
|
setJwtBundleSet(update);
|
||||||
|
done.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(final Throwable error) {
|
||||||
|
log.log(Level.SEVERE, "Error in JwtBundleSet watcher", error);
|
||||||
|
done.countDown();
|
||||||
|
throw new WatcherException("Error fetching JwtBundleSet", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setJwtBundleSet(final JwtBundleSet update) {
|
||||||
|
synchronized (this) {
|
||||||
|
this.bundles = update;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isClosed() {
|
||||||
|
synchronized (this) {
|
||||||
|
return closed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static WorkloadApiClient createClient(final JwtSourceOptions options)
|
||||||
|
throws SocketEndpointAddressException {
|
||||||
|
val clientOptions = DefaultWorkloadApiClient.ClientOptions
|
||||||
|
.builder()
|
||||||
|
.spiffeSocketPath(options.spiffeSocketPath)
|
||||||
|
.build();
|
||||||
|
return DefaultWorkloadApiClient.newClient(clientOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options to configure a {@link DefaultJwtSource}.
|
||||||
|
* <p>
|
||||||
|
* <code>spiffeSocketPath</code> Address to the Workload API, if it is not set, the default address will be used.
|
||||||
|
* <p>
|
||||||
|
* <code>initTimeout</code> Timeout for initializing the instance. If it is not defined, the timeout is read
|
||||||
|
* from the System property `spiffe.newJwtSource.timeout'. If this is also not defined, no default timeout is applied.
|
||||||
|
* <p>
|
||||||
|
* <code>workloadApiClient</code> A custom instance of a {@link WorkloadApiClient}, if it is not set,
|
||||||
|
* a new client will be created.
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public static class JwtSourceOptions {
|
||||||
|
|
||||||
|
@Setter(AccessLevel.NONE)
|
||||||
|
private String spiffeSocketPath;
|
||||||
|
|
||||||
|
@Setter(AccessLevel.NONE)
|
||||||
|
private Duration initTimeout;
|
||||||
|
|
||||||
|
@Setter(AccessLevel.NONE)
|
||||||
|
private WorkloadApiClient workloadApiClient;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
public JwtSourceOptions(
|
||||||
|
final String spiffeSocketPath,
|
||||||
|
final WorkloadApiClient workloadApiClient,
|
||||||
|
final Duration initTimeout) {
|
||||||
|
this.spiffeSocketPath = spiffeSocketPath;
|
||||||
|
this.workloadApiClient = workloadApiClient;
|
||||||
|
this.initTimeout = initTimeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,258 +2,15 @@ package io.spiffe.workloadapi;
|
||||||
|
|
||||||
import io.spiffe.bundle.BundleSource;
|
import io.spiffe.bundle.BundleSource;
|
||||||
import io.spiffe.bundle.jwtbundle.JwtBundle;
|
import io.spiffe.bundle.jwtbundle.JwtBundle;
|
||||||
import io.spiffe.bundle.jwtbundle.JwtBundleSet;
|
|
||||||
import io.spiffe.bundle.x509bundle.X509Bundle;
|
|
||||||
import io.spiffe.exception.BundleNotFoundException;
|
|
||||||
import io.spiffe.exception.JwtSourceException;
|
|
||||||
import io.spiffe.exception.JwtSvidException;
|
|
||||||
import io.spiffe.exception.SocketEndpointAddressException;
|
|
||||||
import io.spiffe.exception.WatcherException;
|
|
||||||
import io.spiffe.spiffeid.SpiffeId;
|
|
||||||
import io.spiffe.spiffeid.TrustDomain;
|
|
||||||
import io.spiffe.svid.jwtsvid.JwtSvid;
|
|
||||||
import io.spiffe.svid.jwtsvid.JwtSvidSource;
|
import io.spiffe.svid.jwtsvid.JwtSvidSource;
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NonNull;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import lombok.extern.java.Log;
|
|
||||||
import lombok.val;
|
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.TimeoutException;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
import static io.spiffe.workloadapi.internal.ThreadUtils.await;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a source of SPIFFE JWT SVIDs and JWT bundles maintained via the Workload API.
|
* Source of JWT SVIDs and Bundles.
|
||||||
|
* @see JwtSvidSource
|
||||||
|
* @see BundleSource
|
||||||
|
* @see JwtBundle
|
||||||
*/
|
*/
|
||||||
@Log
|
public interface JwtSource extends JwtSvidSource, BundleSource<JwtBundle>, Closeable {
|
||||||
public class JwtSource implements JwtSvidSource, BundleSource<JwtBundle>, Closeable {
|
|
||||||
|
|
||||||
static final String TIMEOUT_SYSTEM_PROPERTY = "spiffe.newJwtSource.timeout";
|
|
||||||
|
|
||||||
static final Duration DEFAULT_TIMEOUT =
|
|
||||||
Duration.parse(System.getProperty(TIMEOUT_SYSTEM_PROPERTY, "PT0S"));
|
|
||||||
|
|
||||||
private JwtBundleSet bundles;
|
|
||||||
|
|
||||||
private final WorkloadApiClient workloadApiClient;
|
|
||||||
private volatile boolean closed;
|
|
||||||
|
|
||||||
// private constructor
|
|
||||||
private JwtSource(final WorkloadApiClient workloadApiClient) {
|
|
||||||
this.workloadApiClient = workloadApiClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new JWT source. It blocks until the initial update with the JWT bundles
|
|
||||||
* has been received from the Workload API or until the timeout configured
|
|
||||||
* through the system property `spiffe.newJwtSource.timeout` expires.
|
|
||||||
* If no timeout is configured, it blocks until it gets a JWT update from the Workload API.
|
|
||||||
* <p>
|
|
||||||
* It uses the default address socket endpoint from the environment variable to get the Workload API address.
|
|
||||||
*
|
|
||||||
* @return an instance of {@link JwtSource}, with the JWT bundles initialized
|
|
||||||
* @throws SocketEndpointAddressException if the address to the Workload API is not valid
|
|
||||||
* @throws JwtSourceException if the source could not be initialized
|
|
||||||
*/
|
|
||||||
public static JwtSource newSource() throws JwtSourceException, SocketEndpointAddressException {
|
|
||||||
JwtSourceOptions options = JwtSourceOptions.builder().initTimeout(DEFAULT_TIMEOUT).build();
|
|
||||||
return newSource(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new JWT source. It blocks until the initial update with the JWT bundles
|
|
||||||
* has been received from the Workload API, doing retries with a backoff exponential policy,
|
|
||||||
* or until the initTimeout has expired.
|
|
||||||
* <p>
|
|
||||||
* If the timeout is not provided in the options, the default timeout is read from the
|
|
||||||
* system property `spiffe.newJwtSource.timeout`. If none is configured, this method will
|
|
||||||
* block until the JWT bundles can be retrieved from the Workload API.
|
|
||||||
* <p>
|
|
||||||
* The {@link WorkloadApiClient} can be provided in the options, if it is not,
|
|
||||||
* a new client is created.
|
|
||||||
*
|
|
||||||
* @param options {@link JwtSourceOptions}
|
|
||||||
* @return an instance of {@link JwtSource}, with the JWT bundles initialized
|
|
||||||
* @throws SocketEndpointAddressException if the address to the Workload API is not valid
|
|
||||||
* @throws JwtSourceException if the source could not be initialized
|
|
||||||
*/
|
|
||||||
public static JwtSource newSource(@NonNull final JwtSourceOptions options)
|
|
||||||
throws SocketEndpointAddressException, JwtSourceException {
|
|
||||||
if (options.workloadApiClient == null) {
|
|
||||||
options.workloadApiClient = createClient(options);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.initTimeout == null) {
|
|
||||||
options.initTimeout = DEFAULT_TIMEOUT;
|
|
||||||
}
|
|
||||||
|
|
||||||
JwtSource jwtSource = new JwtSource(options.workloadApiClient);
|
|
||||||
|
|
||||||
try {
|
|
||||||
jwtSource.init(options.initTimeout);
|
|
||||||
} catch (Exception e) {
|
|
||||||
jwtSource.close();
|
|
||||||
throw new JwtSourceException("Error creating JWT source", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return jwtSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public JwtSvid fetchJwtSvid(String audience, String... extraAudiences) throws JwtSvidException {
|
|
||||||
if (isClosed()) {
|
|
||||||
throw new IllegalStateException("JWT SVID source is closed");
|
|
||||||
}
|
|
||||||
return workloadApiClient.fetchJwtSvid(audience, extraAudiences);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a new JWT SVID from the Workload API for the given subject SPIFFE ID and audiences.
|
|
||||||
*
|
|
||||||
* @return a {@link JwtSvid}
|
|
||||||
* @throws IllegalStateException if the source is closed
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public JwtSvid fetchJwtSvid(final SpiffeId subject, final String audience, final String... extraAudiences)
|
|
||||||
throws JwtSvidException {
|
|
||||||
if (isClosed()) {
|
|
||||||
throw new IllegalStateException("JWT SVID source is closed");
|
|
||||||
}
|
|
||||||
|
|
||||||
return workloadApiClient.fetchJwtSvid(subject, audience, extraAudiences);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the JWT bundle for a given trust domain.
|
|
||||||
*
|
|
||||||
* @return an instance of a {@link X509Bundle}
|
|
||||||
*
|
|
||||||
* @throws BundleNotFoundException is there is no bundle for the trust domain provided
|
|
||||||
* @throws IllegalStateException if the source is closed
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public JwtBundle getBundleForTrustDomain(@NonNull final TrustDomain trustDomain) throws BundleNotFoundException {
|
|
||||||
if (isClosed()) {
|
|
||||||
throw new IllegalStateException("JWT bundle source is closed");
|
|
||||||
}
|
|
||||||
return bundles.getBundleForTrustDomain(trustDomain);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes this source, dropping the connection to the Workload API.
|
|
||||||
* Other source methods will return an error after close has been called.
|
|
||||||
* <p>
|
|
||||||
* It is marked with {@link SneakyThrows} because it is not expected to throw
|
|
||||||
* the checked exception defined on the {@link Closeable} interface.
|
|
||||||
*/
|
|
||||||
@SneakyThrows
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
if (!closed) {
|
|
||||||
synchronized (this) {
|
|
||||||
if (!closed) {
|
|
||||||
workloadApiClient.close();
|
|
||||||
closed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void init(final Duration timeout) throws TimeoutException {
|
|
||||||
CountDownLatch done = new CountDownLatch(1);
|
|
||||||
setJwtBundlesWatcher(done);
|
|
||||||
|
|
||||||
boolean success;
|
|
||||||
if (timeout.isZero()) {
|
|
||||||
await(done);
|
|
||||||
success = true;
|
|
||||||
} else {
|
|
||||||
success = await(done, timeout.getSeconds(), TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
if (!success) {
|
|
||||||
throw new TimeoutException("Timeout waiting for JWT bundles update");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setJwtBundlesWatcher(final CountDownLatch done) {
|
|
||||||
workloadApiClient.watchJwtBundles(new Watcher<JwtBundleSet>() {
|
|
||||||
@Override
|
|
||||||
public void onUpdate(final JwtBundleSet update) {
|
|
||||||
log.log(Level.INFO, "Received JwtBundleSet update");
|
|
||||||
setJwtBundleSet(update);
|
|
||||||
done.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(final Throwable error) {
|
|
||||||
log.log(Level.SEVERE, "Error in JwtBundleSet watcher", error);
|
|
||||||
done.countDown();
|
|
||||||
throw new WatcherException("Error fetching JwtBundleSet", error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setJwtBundleSet(final JwtBundleSet update) {
|
|
||||||
synchronized (this) {
|
|
||||||
this.bundles = update;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isClosed() {
|
|
||||||
synchronized (this) {
|
|
||||||
return closed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static WorkloadApiClient createClient(final JwtSourceOptions options)
|
|
||||||
throws SocketEndpointAddressException {
|
|
||||||
val clientOptions = DefaultWorkloadApiClient.ClientOptions
|
|
||||||
.builder()
|
|
||||||
.spiffeSocketPath(options.spiffeSocketPath)
|
|
||||||
.build();
|
|
||||||
return DefaultWorkloadApiClient.newClient(clientOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Options to configure a {@link JwtSource}.
|
|
||||||
* <p>
|
|
||||||
* <code>spiffeSocketPath</code> Address to the Workload API, if it is not set, the default address will be used.
|
|
||||||
* <p>
|
|
||||||
* <code>initTimeout</code> Timeout for initializing the instance. If it is not defined, the timeout is read
|
|
||||||
* from the System property `spiffe.newJwtSource.timeout'. If this is also not defined, no default timeout is applied.
|
|
||||||
* <p>
|
|
||||||
* <code>workloadApiClient</code> A custom instance of a {@link WorkloadApiClient}, if it is not set,
|
|
||||||
* a new client will be created.
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public static class JwtSourceOptions {
|
|
||||||
|
|
||||||
@Setter(AccessLevel.NONE)
|
|
||||||
private String spiffeSocketPath;
|
|
||||||
|
|
||||||
@Setter(AccessLevel.NONE)
|
|
||||||
private Duration initTimeout;
|
|
||||||
|
|
||||||
@Setter(AccessLevel.NONE)
|
|
||||||
private WorkloadApiClient workloadApiClient;
|
|
||||||
|
|
||||||
@Builder
|
|
||||||
public JwtSourceOptions(
|
|
||||||
final String spiffeSocketPath,
|
|
||||||
final WorkloadApiClient workloadApiClient,
|
|
||||||
final Duration initTimeout) {
|
|
||||||
this.spiffeSocketPath = spiffeSocketPath;
|
|
||||||
this.workloadApiClient = workloadApiClient;
|
|
||||||
this.initTimeout = initTimeout;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@ import java.io.Closeable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Source of X.509 SVIDs and Bundles.
|
* Source of X.509 SVIDs and Bundles.
|
||||||
|
* @see X509SvidSource
|
||||||
|
* @see BundleSource
|
||||||
|
* @see X509Bundle
|
||||||
*/
|
*/
|
||||||
public interface X509Source extends X509SvidSource, BundleSource<X509Bundle>, Closeable {
|
public interface X509Source extends X509SvidSource, BundleSource<X509Bundle>, Closeable {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ class DefaultX509SourceTest {
|
||||||
void setUp() throws X509SourceException, SocketEndpointAddressException {
|
void setUp() throws X509SourceException, SocketEndpointAddressException {
|
||||||
workloadApiClient = new WorkloadApiClientStub();
|
workloadApiClient = new WorkloadApiClientStub();
|
||||||
DefaultX509Source.X509SourceOptions options = DefaultX509Source.X509SourceOptions.builder().workloadApiClient(workloadApiClient).build();
|
DefaultX509Source.X509SourceOptions options = DefaultX509Source.X509SourceOptions.builder().workloadApiClient(workloadApiClient).build();
|
||||||
System.setProperty(JwtSource.TIMEOUT_SYSTEM_PROPERTY, "PT1S");
|
System.setProperty(DefaultJwtSource.TIMEOUT_SYSTEM_PROPERTY, "PT1S");
|
||||||
x509Source = DefaultX509Source.newSource(options);
|
x509Source = DefaultX509Source.newSource(options);
|
||||||
workloadApiClientErrorStub = new WorkloadApiClientErrorStub();
|
workloadApiClientErrorStub = new WorkloadApiClientErrorStub();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
@ -31,14 +32,14 @@ class JwtSourceTest {
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() throws JwtSourceException, SocketEndpointAddressException {
|
void setUp() throws JwtSourceException, SocketEndpointAddressException {
|
||||||
workloadApiClient = new WorkloadApiClientStub();
|
workloadApiClient = new WorkloadApiClientStub();
|
||||||
JwtSource.JwtSourceOptions options = JwtSource.JwtSourceOptions.builder().workloadApiClient(workloadApiClient).build();
|
DefaultJwtSource.JwtSourceOptions options = DefaultJwtSource.JwtSourceOptions.builder().workloadApiClient(workloadApiClient).build();
|
||||||
System.setProperty(JwtSource.TIMEOUT_SYSTEM_PROPERTY, "PT1S");
|
System.setProperty(DefaultJwtSource.TIMEOUT_SYSTEM_PROPERTY, "PT1S");
|
||||||
jwtSource = JwtSource.newSource(options);
|
jwtSource = DefaultJwtSource.newSource(options);
|
||||||
workloadApiClientErrorStub = new WorkloadApiClientErrorStub();
|
workloadApiClientErrorStub = new WorkloadApiClientErrorStub();
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
void tearDown() {
|
void tearDown() throws IOException {
|
||||||
jwtSource.close();
|
jwtSource.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -66,7 +67,7 @@ class JwtSourceTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGetBundleForTrustDomain_SourceIsClosed_ThrowsIllegalStateException() {
|
void testGetBundleForTrustDomain_SourceIsClosed_ThrowsIllegalStateException() throws IOException {
|
||||||
jwtSource.close();
|
jwtSource.close();
|
||||||
try {
|
try {
|
||||||
jwtSource.getBundleForTrustDomain(TrustDomain.of("example.org"));
|
jwtSource.getBundleForTrustDomain(TrustDomain.of("example.org"));
|
||||||
|
|
@ -104,7 +105,7 @@ class JwtSourceTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testFetchJwtSvid_SourceIsClosed_ThrowsIllegalStateException() {
|
void testFetchJwtSvid_SourceIsClosed_ThrowsIllegalStateException() throws IOException {
|
||||||
jwtSource.close();
|
jwtSource.close();
|
||||||
try {
|
try {
|
||||||
jwtSource.fetchJwtSvid("aud1", "aud2", "aud3");
|
jwtSource.fetchJwtSvid("aud1", "aud2", "aud3");
|
||||||
|
|
@ -118,7 +119,7 @@ class JwtSourceTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testFetchJwtSvidWithSubject_SourceIsClosed_ThrowsIllegalStateException() {
|
void testFetchJwtSvidWithSubject_SourceIsClosed_ThrowsIllegalStateException() throws IOException {
|
||||||
jwtSource.close();
|
jwtSource.close();
|
||||||
try {
|
try {
|
||||||
jwtSource.fetchJwtSvid(SpiffeId.parse("spiffe://example.org/workload-server"), "aud1", "aud2", "aud3");
|
jwtSource.fetchJwtSvid(SpiffeId.parse("spiffe://example.org/workload-server"), "aud1", "aud2", "aud3");
|
||||||
|
|
@ -133,13 +134,13 @@ class JwtSourceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void newSource_success() {
|
void newSource_success() {
|
||||||
val options = JwtSource.JwtSourceOptions
|
val options = DefaultJwtSource.JwtSourceOptions
|
||||||
.builder()
|
.builder()
|
||||||
.workloadApiClient(workloadApiClient)
|
.workloadApiClient(workloadApiClient)
|
||||||
.initTimeout(Duration.ofSeconds(0))
|
.initTimeout(Duration.ofSeconds(0))
|
||||||
.build();
|
.build();
|
||||||
try {
|
try {
|
||||||
JwtSource jwtSource = JwtSource.newSource(options);
|
JwtSource jwtSource = DefaultJwtSource.newSource(options);
|
||||||
assertNotNull(jwtSource);
|
assertNotNull(jwtSource);
|
||||||
} catch (SocketEndpointAddressException | JwtSourceException e) {
|
} catch (SocketEndpointAddressException | JwtSourceException e) {
|
||||||
fail(e);
|
fail(e);
|
||||||
|
|
@ -149,7 +150,7 @@ class JwtSourceTest {
|
||||||
@Test
|
@Test
|
||||||
void newSource_nullParam() {
|
void newSource_nullParam() {
|
||||||
try {
|
try {
|
||||||
JwtSource.newSource(null);
|
DefaultJwtSource.newSource(null);
|
||||||
fail();
|
fail();
|
||||||
} catch (NullPointerException e) {
|
} catch (NullPointerException e) {
|
||||||
assertEquals("options is marked non-null but is null", e.getMessage());
|
assertEquals("options is marked non-null but is null", e.getMessage());
|
||||||
|
|
@ -160,13 +161,13 @@ class JwtSourceTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void newSource_errorFetchingJwtBundles() {
|
void newSource_errorFetchingJwtBundles() {
|
||||||
val options = JwtSource.JwtSourceOptions
|
val options = DefaultJwtSource.JwtSourceOptions
|
||||||
.builder()
|
.builder()
|
||||||
.workloadApiClient(workloadApiClientErrorStub)
|
.workloadApiClient(workloadApiClientErrorStub)
|
||||||
.spiffeSocketPath("unix:/tmp/test")
|
.spiffeSocketPath("unix:/tmp/test")
|
||||||
.build();
|
.build();
|
||||||
try {
|
try {
|
||||||
JwtSource.newSource(options);
|
DefaultJwtSource.newSource(options);
|
||||||
fail();
|
fail();
|
||||||
} catch (JwtSourceException e) {
|
} catch (JwtSourceException e) {
|
||||||
assertEquals("Error creating JWT source", e.getMessage());
|
assertEquals("Error creating JWT source", e.getMessage());
|
||||||
|
|
@ -179,11 +180,11 @@ class JwtSourceTest {
|
||||||
@Test
|
@Test
|
||||||
void newSource_FailsBecauseOfTimeOut() throws Exception {
|
void newSource_FailsBecauseOfTimeOut() throws Exception {
|
||||||
try {
|
try {
|
||||||
val options = JwtSource.JwtSourceOptions
|
val options = DefaultJwtSource.JwtSourceOptions
|
||||||
.builder()
|
.builder()
|
||||||
.spiffeSocketPath("unix:/tmp/test")
|
.spiffeSocketPath("unix:/tmp/test")
|
||||||
.build();
|
.build();
|
||||||
JwtSource.newSource(options);
|
DefaultJwtSource.newSource(options);
|
||||||
fail();
|
fail();
|
||||||
} catch (JwtSourceException e) {
|
} catch (JwtSourceException e) {
|
||||||
assertEquals("Error creating JWT source", e.getMessage());
|
assertEquals("Error creating JWT source", e.getMessage());
|
||||||
|
|
@ -197,7 +198,7 @@ class JwtSourceTest {
|
||||||
void newSource_DefaultSocketAddress() throws Exception {
|
void newSource_DefaultSocketAddress() throws Exception {
|
||||||
try {
|
try {
|
||||||
TestUtils.setEnvironmentVariable(Address.SOCKET_ENV_VARIABLE, "unix:/tmp/test");
|
TestUtils.setEnvironmentVariable(Address.SOCKET_ENV_VARIABLE, "unix:/tmp/test");
|
||||||
JwtSource.newSource();
|
DefaultJwtSource.newSource();
|
||||||
fail();
|
fail();
|
||||||
} catch (JwtSourceException e) {
|
} catch (JwtSourceException e) {
|
||||||
assertEquals("Error creating JWT source", e.getMessage());
|
assertEquals("Error creating JWT source", e.getMessage());
|
||||||
|
|
@ -211,7 +212,7 @@ class JwtSourceTest {
|
||||||
try {
|
try {
|
||||||
// just in case it's defined in the environment
|
// just in case it's defined in the environment
|
||||||
TestUtils.setEnvironmentVariable(Address.SOCKET_ENV_VARIABLE, "");
|
TestUtils.setEnvironmentVariable(Address.SOCKET_ENV_VARIABLE, "");
|
||||||
JwtSource.newSource();
|
DefaultJwtSource.newSource();
|
||||||
fail();
|
fail();
|
||||||
} catch (SocketEndpointAddressException e) {
|
} catch (SocketEndpointAddressException e) {
|
||||||
fail();
|
fail();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue