Addressing PR comments.

Adding documentation.
Amendments in READMEs and javadoc comments.
Some refactors to improve code and clarity.

Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
This commit is contained in:
Max Lambrecht 2020-06-11 17:09:55 -03:00
parent 6d42e48861
commit 00eb86949f
12 changed files with 72 additions and 45 deletions

View File

@ -4,7 +4,7 @@
The JAVA-SPIFFE library provides functionality to interact with the Workload API to fetch X.509 and JWT SVIDs and Bundles,
and a Java Security Provider implementation to be plugged into the Java Security architecture. This is essentially
a X.509-SVID based KeyStore and TrustStore implementation that handles the certificates in memory and receives the updates
an X.509-SVID based KeyStore and TrustStore implementation that handles the certificates in memory and receives the updates
asynchronously from the Workload API. The KeyStore handles the Certificate chain and Private Key to prove identity
in a TLS connection, and the TrustStore handles the trusted bundles (supporting federated bundles) and performs
peer's certificate and SPIFFE ID verification.

View File

@ -6,7 +6,7 @@ Core functionality to fetch, process and validate X.509 and JWT SVIDs and Bundle
A `spiffe.workloadapi.X509Source` represents a source of X.509 SVIDs and X.509 bundles maintained via the Workload API.
To create a new X509 Source:
To create a new X.509 Source:
```
X509Source x509Source;
@ -20,13 +20,13 @@ To create a new X509 Source:
X509Bundle bundle = x509Source.getBundleForTrustDomain(TrustDomain.of("example.org"));
```
The `newSource()` blocks until the X.509 materials can be retrieved from the Workload API and the X509Source is
initialized with the X.509 SVIDs and Bundles. A `X509 context watcher` is configured on the X509Source to get automatically
The `newSource()`method blocks until the X.509 materials can be retrieved from the Workload API and the `X509Source` is
initialized with the X.509 SVIDs and Bundles. A `X.509 context watcher` is configured on the `X509Source` to automatically get
the updates from the Workload API. This watcher performs retries if at any time the connection to the Workload API
reports an error.
The socket endpoint address is configured through the environment variable `SPIFFE_ENDPOINT_SOCKET`. Another way to
configure it is by providing a `X509SourceOptions` instance to the `newSource` method:
configure it is by providing an `X509SourceOptions` instance to the `newSource` method:
```
X509Source.X509SourceOptions x509SourceOptions = X509Source.X509SourceOptions
@ -42,16 +42,16 @@ It allows to configure another SVID picker. By default, the first SVID is used.
### Configure a timeout for X509Source initialization
The method `X509Source newSource()` blocks waiting until a X509 context is fetched. The X509 context fetch is retried
The method `X509Source newSource()` blocks waiting until a X.509 context is fetched. The X.509 context fetch is retried
using an exponential backoff policy with this progression of delays between retries: 1 second, 2 seconds, 4, 8, 16, 32, 60, 60, 60...
It retries indefinitely unless a timeout is configured.
This timeout can be configured either providing it through the `newSource(Duration timeout)` method or
using a System property:
`spiffe.newX509Source.timeout=30`
`spiffe.newX509Source.timeout=PT30S`
The Time Unit is seconds.
The `timout` duration is expressed in `ISO-8601` format.
## JWT Source
@ -73,15 +73,34 @@ To create a new JWT Source:
JwtBundle bundle = jwtSource.getBundleForTrustDomain(TrustDomain.of("example.org"));
```
The `newSource()` blocks until the JWT materials can be retrieved from the Workload API and the JwtSource is
initialized with the JWT Bundles. A `JWT context watcher` is configured on the JwtSource to get automatically
The `newSource()` method blocks until the JWT materials can be retrieved from the Workload API and the `JwtSource` is
initialized with the JWT Bundles. A `JWT context watcher` is configured on the JwtSource to automatically get
the updates from the Workload API. This watcher performs retries if at any time the connection to the Workload API
reports an error.
The socket endpoint address is configured through the environment variable `SPIFFE_ENDPOINT_SOCKET`.
## Netty Event Loop thread number configuration
Another way to configure it is by providing an `JwtSourceOptions` instance to the `newSource` method:
Use the variable `io.netty.eventLoopThreads` to configure the number of threads for the Netty Event Loop Group.
```
JwtSource.JwtSourceOptions jwtSourceOptions = JwtSource.JwtSourceOptions
.builder()
.spiffeSocketPath("unix:/tmp/agent-other.sock")
.build();
JwtSource jwtSource = JwtSource.newSource(jwtSourceOptions);
```
### Configure a timeout for JwtSource initialization
The method `JwtSource newSource()` blocks until the JWT materials are fetched. The fetching process is retried
using an exponential backoff policy with this progression of delays between retries: 1 second, 2 seconds, 4, 8, 16, 32, 60, 60, 60...
It retries indefinitely unless a timeout is configured.
This timeout can be configured either providing it through the `newSource(Duration timeout)` method or
using a System property:
`spiffe.newJwtSource.timeout=PT30S`
The `timout` duration is expressed in `ISO-8601` format.
By default, it is `availableProcessors * 2`.

View File

@ -21,6 +21,7 @@ import java.nio.file.Path;
import java.security.KeyException;
import java.security.PublicKey;
import java.text.ParseException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -57,7 +58,7 @@ public class JwtBundle implements BundleSource<JwtBundle> {
}
/**
* Loads a bundle from a file on disk. The file must contain a standard RFC 7517 JWKS document.
* Loads a JWT bundle from a file on disk. The file must contain a standard RFC 7517 JWKS document.
* <p>
* Key Types supported are EC and RSA.
*
@ -77,7 +78,7 @@ public class JwtBundle implements BundleSource<JwtBundle> {
}
/**
* Parses a bundle from a byte array.
* Parses a JWT bundle from a byte array.
*
* @param trustDomain a {@link TrustDomain}
* @param bundleBytes an array of bytes representing the JWT bundle.
@ -114,7 +115,7 @@ public class JwtBundle implements BundleSource<JwtBundle> {
* Returns the JWT authorities in the bundle, keyed by key ID.
*/
public Map<String, PublicKey> getJwtAuthorities() {
return new HashMap<>(jwtAuthorities);
return Collections.unmodifiableMap(jwtAuthorities);
}
/**
@ -146,7 +147,7 @@ public class JwtBundle implements BundleSource<JwtBundle> {
* @param keyId Key ID to associate to the jwtAuthority
* @param jwtAuthority a PublicKey
*/
public void addJwtAuthority(@NonNull String keyId, @NonNull PublicKey jwtAuthority) {
public void putJwtAuthority(@NonNull String keyId, @NonNull PublicKey jwtAuthority) {
if (StringUtils.isBlank(keyId)) {
throw new IllegalArgumentException("KeyId cannot be empty");
}

View File

@ -14,6 +14,7 @@ import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@ -38,7 +39,7 @@ public class X509Bundle implements BundleSource<X509Bundle> {
}
/**
* Creates a new JWT bundle for a trust domain with X.509 Authorities.
* Creates a new X.509 bundle for a trust domain with X.509 Authorities.
*
* @param trustDomain a {@link TrustDomain} to associate to the JwtBundle
* @param x509Authorities a Map of X.509 Certificates
@ -50,7 +51,7 @@ public class X509Bundle implements BundleSource<X509Bundle> {
}
/**
* Loads a X.509 bundle from a file on disk.
* Loads an X.509 bundle from a file on disk.
*
* @param trustDomain a {@link TrustDomain} to associate to the bundle
* @param bundlePath a path to the file that has the X.509 authorities
@ -73,7 +74,7 @@ public class X509Bundle implements BundleSource<X509Bundle> {
}
/**
* Parses a X095 bundle from an array of bytes.
* Parses a X.509 bundle from an array of bytes.
*
* @param trustDomain a {@link TrustDomain} to associate to the X.509 bundle
* @param bundleBytes an array of bytes that represents the X.509 authorities
@ -109,7 +110,7 @@ public class X509Bundle implements BundleSource<X509Bundle> {
* Returns the X.509 x509Authorities in the bundle.
*/
public Set<X509Certificate> getX509Authorities() {
return new HashSet<>(x509Authorities);
return Collections.unmodifiableSet(x509Authorities);
}
/**

View File

@ -7,6 +7,7 @@ import spiffe.bundle.BundleSource;
import spiffe.exception.BundleNotFoundException;
import spiffe.spiffeid.TrustDomain;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -39,12 +40,12 @@ public class X509BundleSet implements BundleSource<X509Bundle> {
}
/**
* Adds a bundle to this Set, if the trust domain already exists,
* Adds an X.509 bundle to this Set, if the trust domain already exists,
* replaces the bundle.
*
* @param x509Bundle a {@link X509Bundle}
*/
public void add(@NonNull X509Bundle x509Bundle){
public void put(@NonNull X509Bundle x509Bundle){
bundles.put(x509Bundle.getTrustDomain(), x509Bundle);
}
@ -64,7 +65,10 @@ public class X509BundleSet implements BundleSource<X509Bundle> {
return bundle;
}
/**
* Returns the X.509 bundles of this X.509 Bundle Set.
*/
public Map<TrustDomain, X509Bundle> getBundles() {
return new HashMap<>(bundles);
return Collections.unmodifiableMap(bundles);
}
}

View File

@ -29,7 +29,7 @@ import java.util.logging.Level;
import static spiffe.workloadapi.internal.ThreadUtils.await;
/**
* A <code>JwtSource</code> represents a source of SPIFFE JWT SVID and JWT bundles
* A <code>JwtSource</code> represents a source of SPIFFE JWT SVIDs and JWT bundles
* maintained via the Workload API.
*/
@Log
@ -38,7 +38,7 @@ public class JwtSource implements JwtSvidSource, BundleSource<JwtBundle>, Closea
private static final Duration DEFAULT_TIMEOUT;
static {
DEFAULT_TIMEOUT = Duration.ofSeconds(Long.getLong("spiffe.newJwtSource.timeout", 0));
DEFAULT_TIMEOUT = Duration.parse(System.getProperty("spiffe.newJwtSource.timeout", "PT0S"));
}
private JwtBundleSet bundles;
@ -131,7 +131,7 @@ public class JwtSource implements JwtSvidSource, BundleSource<JwtBundle>, Closea
}
/**
* Returns the JWT SVID handled by this source.
* 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

View File

@ -41,12 +41,12 @@ import static spiffe.workloadapi.internal.ThreadUtils.await;
* after close has been called.
*/
@Log
public class X509Source implements X509SvidSource, BundleSource, Closeable {
public class X509Source implements X509SvidSource, BundleSource<X509Bundle>, Closeable {
private static final Duration DEFAULT_TIMEOUT;
static {
DEFAULT_TIMEOUT = Duration.ofSeconds(Long.getLong("spiffe.newX509Source.timeout", 0));
DEFAULT_TIMEOUT = Duration.parse(System.getProperty("spiffe.newX509Source.timeout", "PT0S"));
}
private X509Svid svid;
@ -125,6 +125,8 @@ public class X509Source implements X509SvidSource, BundleSource, Closeable {
* @throws X509SourceException if the source could not be initialized
*/
public static X509Source newSource(@NonNull X509SourceOptions options, @NonNull Duration timeout) throws SocketEndpointAddressException, X509SourceException {
System.out.println("TIMEOUT: ***** " + timeout);
if (options.workloadApiClient == null) {
options.workloadApiClient = createClient(options);
}

View File

@ -114,7 +114,7 @@ class JwtBundleSetTest {
JwtBundleSet bundleSet = JwtBundleSet.of(bundleList);
JwtBundle jwtBundle2 = new JwtBundle(TrustDomain.of("example.org"));
jwtBundle2.addJwtAuthority("key1", new DummyPublicKey());
jwtBundle2.putJwtAuthority("key1", new DummyPublicKey());
bundleSet.add(jwtBundle2);
assertTrue(bundleSet.getBundles().containsValue(jwtBundle2));

View File

@ -287,8 +287,8 @@ class JwtBundleTest {
// Test addJWTAuthority
DummyPublicKey jwtAuthority1 = new DummyPublicKey();
DummyPublicKey jwtAuthority2 = new DummyPublicKey();
jwtBundle.addJwtAuthority("key1", jwtAuthority1);
jwtBundle.addJwtAuthority("key2", jwtAuthority2);
jwtBundle.putJwtAuthority("key1", jwtAuthority1);
jwtBundle.putJwtAuthority("key2", jwtAuthority2);
assertEquals(2, jwtBundle.getJwtAuthorities().size());
@ -310,7 +310,7 @@ class JwtBundleTest {
assertTrue(jwtBundle.hasJwtAuthority("key2"));
// Test update
jwtBundle.addJwtAuthority("key2", jwtAuthority1);
jwtBundle.putJwtAuthority("key2", jwtAuthority1);
assertEquals(jwtAuthority1, jwtBundle.getJwtAuthorities().get("key2"));
assertEquals(1, jwtBundle.getJwtAuthorities().size());
}
@ -319,7 +319,7 @@ class JwtBundleTest {
void testAddJwtAuthority_emtpyKeyId_throwsIllegalArgumentException() {
JwtBundle jwtBundle = new JwtBundle(TrustDomain.of("example.org"));
try {
jwtBundle.addJwtAuthority("", new DummyPublicKey());
jwtBundle.putJwtAuthority("", new DummyPublicKey());
} catch (IllegalArgumentException e) {
assertEquals("KeyId cannot be empty", e.getMessage());
}
@ -329,7 +329,7 @@ class JwtBundleTest {
void testAddJwtAuthority_nullKeyId_throwsNullPointerException() {
JwtBundle jwtBundle = new JwtBundle(TrustDomain.of("example.org"));
try {
jwtBundle.addJwtAuthority(null, new DummyPublicKey());
jwtBundle.putJwtAuthority(null, new DummyPublicKey());
} catch (NullPointerException e) {
assertEquals("keyId is marked non-null but is null", e.getMessage());
}
@ -339,7 +339,7 @@ class JwtBundleTest {
void testAddJwtAuthority_nullJwtAuthority_throwsNullPointerException() {
JwtBundle jwtBundle = new JwtBundle(TrustDomain.of("example.org"));
try {
jwtBundle.addJwtAuthority("key1", null);
jwtBundle.putJwtAuthority("key1", null);
} catch (NullPointerException e) {
assertEquals("jwtAuthority is marked non-null but is null", e.getMessage());
}

View File

@ -41,7 +41,7 @@ class X509BundleSetTest {
X509BundleSet bundleSet = X509BundleSet.of(bundleList);
X509Bundle x509Bundle2 = new X509Bundle(TrustDomain.of("other.org"));
bundleSet.add(x509Bundle2);
bundleSet.put(x509Bundle2);
assertTrue(bundleSet.getBundles().containsValue(x509Bundle1));
assertTrue(bundleSet.getBundles().containsValue(x509Bundle2));
@ -53,7 +53,7 @@ class X509BundleSetTest {
List<X509Bundle> bundleList = Collections.singletonList(x509Bundle1);
X509BundleSet bundleSet = X509BundleSet.of(bundleList);
bundleSet.add(x509Bundle1);
bundleSet.put(x509Bundle1);
assertTrue(bundleSet.getBundles().containsValue(x509Bundle1));
assertEquals(1, bundleSet.getBundles().size());
@ -67,7 +67,7 @@ class X509BundleSetTest {
X509Bundle x509Bundle2 = new X509Bundle(TrustDomain.of("example.org"));
x509Bundle2.addX509Authority(new DummyX509Certificate());
bundleSet.add(x509Bundle2);
bundleSet.put(x509Bundle2);
assertTrue(bundleSet.getBundles().containsValue(x509Bundle2));
assertFalse(bundleSet.getBundles().containsValue(x509Bundle1));
@ -81,7 +81,7 @@ class X509BundleSetTest {
X509BundleSet bundleSet = X509BundleSet.of(bundleList);
try {
bundleSet.add(null);
bundleSet.put(null);
fail("should have thrown exception");
} catch (NullPointerException e) {
assertEquals("x509Bundle is marked non-null but is null", e.getMessage());

View File

@ -106,9 +106,9 @@ class JwtSvidParseAndValidateTest {
TrustDomain trustDomain = TrustDomain.of("test.domain");
JwtBundle jwtBundle = new JwtBundle(trustDomain);
jwtBundle.addJwtAuthority("authority1", key1.getPublic());
jwtBundle.addJwtAuthority("authority2", key2.getPublic());
jwtBundle.addJwtAuthority("authority3", key3.getPublic());
jwtBundle.putJwtAuthority("authority1", key1.getPublic());
jwtBundle.putJwtAuthority("authority2", key2.getPublic());
jwtBundle.putJwtAuthority("authority3", key3.getPublic());
SpiffeId spiffeId = trustDomain.newSpiffeId("host");
Date expiration = new Date(System.currentTimeMillis() + 3600000);

View File

@ -88,8 +88,8 @@ class JwtSvidParseInsecureTest {
TrustDomain trustDomain = TrustDomain.of("test.domain");
JwtBundle jwtBundle = new JwtBundle(trustDomain);
jwtBundle.addJwtAuthority("authority1", key1.getPublic());
jwtBundle.addJwtAuthority("authority2", key2.getPublic());
jwtBundle.putJwtAuthority("authority1", key1.getPublic());
jwtBundle.putJwtAuthority("authority2", key2.getPublic());
SpiffeId spiffeId = trustDomain.newSpiffeId("host");
Date expiration = new Date(System.currentTimeMillis() + 3600000);