Merge pull request #11 from maxlambrecht/federation-support
Adding Federation support
This commit is contained in:
commit
adbda7b957
20
README.md
20
README.md
|
|
@ -10,6 +10,9 @@ The JAVA-SPIFFE library provides two components:
|
|||
and receives the updates asynchronously from the Workload API. Using the terminology of the Java Security API,
|
||||
this library provides a custom Security Provider that can be installed in the JVM.
|
||||
|
||||
It supports Federation. The TrustStore validates the peer's SVID using a set of Trusted CAs that includes the
|
||||
Federated TrustDomains CAs bundles. These Federated CAs bundles come from the Workload API in the X509SVIDResponse.
|
||||
|
||||
## SPIFFE Workload API Client Example
|
||||
|
||||
The `X509SVIDFetcher` provides the `registerListener` method that allows a consumer to register a listener
|
||||
|
|
@ -20,16 +23,18 @@ The gRPC channel is configured based on the Address (tcp or unix socket) and the
|
|||
### Use
|
||||
|
||||
```
|
||||
Fetcher<List<X509SVID>> svidFetcher = new X509SVIDFetcher("/tmp/agent.sock");
|
||||
Consumer<List<X509SVID>> certificateUpdater;
|
||||
certificateUpdater = certs -> {
|
||||
certs.forEach(svid -> {
|
||||
Fetcher<X509SVIDResponse> svidFetcher = new X509SVIDFetcher("/tmp/agent.sock");
|
||||
|
||||
Consumer<X509SVIDResponse> xvidConsumer = x509SVIDResponse -> {
|
||||
x509SVIDResponse.getSvidsList().forEach(svid -> {
|
||||
System.out.println("Spiffe ID fetched: " + svid.getSpiffeId());
|
||||
System.out.println("Federated with: " + svid.getFederatesWithList());
|
||||
});
|
||||
};
|
||||
System.out.println(x509SVIDResponse.getFederatedBundlesMap());
|
||||
};
|
||||
|
||||
//Registering the callback to receive the SVIDs from the Workload API
|
||||
svidFetcher.registerListener(certificateUpdater);
|
||||
svidFetcher.registerListener(xvidConsumer);
|
||||
```
|
||||
|
||||
The `X509SVIDFetcher` can be configured with a custom `RetryPolicy`.
|
||||
|
|
@ -84,7 +89,7 @@ ssl.KeyManagerFactory.algorithm=Spiffe
|
|||
ssl.TrustManagerFactory.algorithm=Spiffe
|
||||
|
||||
# The list of spiffeIDs that will be authorized
|
||||
ssl.spiffe.accept=spiffe://example.org/workload, spiffe://example.org/workload2
|
||||
ssl.spiffe.accept=spiffe://example.org/workload, spiffe://example.org/workload2, spiffe://example2.org/workload
|
||||
```
|
||||
|
||||
In your `java.security` file:
|
||||
|
|
@ -92,6 +97,7 @@ In your `java.security` file:
|
|||
* replace `<n>` following the order of the `# List of Providers` in the master file.
|
||||
|
||||
* replace the value of the custom property `ssl.spiffe.accept` with the Spiffe IDs of the workloads that are allowed to connect.
|
||||
If the property is not present or if it's empty, any spiffe id will be authorized.
|
||||
|
||||
To pass your custom security properties file through the command line via system property when starting the JVM:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
group 'spiffe'
|
||||
version '0.1.0'
|
||||
version '0.2.0'
|
||||
|
||||
|
||||
buildscript {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import java.util.logging.Logger;
|
|||
* Provides functionality to interact with a Workload API
|
||||
*
|
||||
*/
|
||||
public final class X509SVIDFetcher implements Fetcher<List<X509SVID>> {
|
||||
public final class X509SVIDFetcher implements Fetcher<X509SVIDResponse> {
|
||||
|
||||
private static final Logger LOGGER = Logger.getLogger(X509SVIDFetcher.class.getName());
|
||||
|
||||
|
|
@ -58,19 +58,19 @@ public final class X509SVIDFetcher implements Fetcher<List<X509SVID>> {
|
|||
}
|
||||
|
||||
/**
|
||||
* Register the listener to receive the X509 SVIDS from the Workload API
|
||||
* Register the listener to receive the X509SVIDResponse from the Workload API
|
||||
* In case there's an error in the connection with the Workload API,
|
||||
* it retries using a RetryHandler that implements a backoff policy
|
||||
*
|
||||
*/
|
||||
@Override
|
||||
public void registerListener(Consumer<List<X509SVID>> listener) {
|
||||
public void registerListener(Consumer<X509SVIDResponse> listener) {
|
||||
|
||||
StreamObserver<X509SVIDResponse> observer = new StreamObserver<X509SVIDResponse>() {
|
||||
@Override
|
||||
public void onNext(X509SVIDResponse value) {
|
||||
LOGGER.log(Level.FINE, "New SVID received ");
|
||||
listener.accept(value.getSvidsList());
|
||||
listener.accept(value);
|
||||
retryHandler.reset();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import java.util.logging.Level;
|
|||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.Collections.EMPTY_LIST;
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.startsWith;
|
||||
|
||||
/**
|
||||
|
|
@ -90,6 +92,8 @@ class CertificateUtils {
|
|||
* Validates that the SPIFFE ID is present and matches the SPIFFE ID configured in
|
||||
* the java.security property ssl.spiffe.accept
|
||||
*
|
||||
* If the authorized spiffe ids list is empty any spiffe id is authorized
|
||||
*
|
||||
* @param chain an array of X509Certificate that contains the Peer's SVID to be validated
|
||||
* @throws CertificateException when either the certificates doesn't have a SPIFFE ID or the SPIFFE ID is not authorized
|
||||
*/
|
||||
|
|
@ -99,9 +103,9 @@ class CertificateUtils {
|
|||
throw new CertificateException("SPIFFE ID not found in the certificate");
|
||||
}
|
||||
|
||||
List<String> acceptedSpiffeIds = getAcceptedSpiffeIds();
|
||||
List<String> acceptedSpiffeIds = getAuthorizedSpiffeIDs();
|
||||
|
||||
if (!acceptedSpiffeIds.contains(spiffeId.get())) {
|
||||
if (!acceptedSpiffeIds.isEmpty() && !acceptedSpiffeIds.contains(spiffeId.get())) {
|
||||
String errorMessage = String.format("SPIFFE ID %s is not authorized", spiffeId.get());
|
||||
LOGGER.log(Level.WARNING, errorMessage);
|
||||
throw new CertificateException(errorMessage);
|
||||
|
|
@ -109,12 +113,15 @@ class CertificateUtils {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the list of accepted spiffe id configured in the SSL_SPIFFE_ACCEPT_PROPERTY property in
|
||||
* Returns the list of authorized spiffe ids configured in the SSL_SPIFFE_ACCEPT_PROPERTY property in
|
||||
* the java security properties file
|
||||
*
|
||||
*/
|
||||
private static List<String> getAcceptedSpiffeIds() {
|
||||
private static List<String> getAuthorizedSpiffeIDs() {
|
||||
String commaSeparatedSpiffeIds = Security.getProperty(SSL_SPIFFE_ACCEPT_PROPERTY);
|
||||
if (isBlank(commaSeparatedSpiffeIds)) {
|
||||
return EMPTY_LIST;
|
||||
}
|
||||
String [] array = commaSeparatedSpiffeIds.split(",");
|
||||
return normalize(Arrays.asList(array));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
package spiffe.provider;
|
||||
|
||||
import spiffe.api.svid.Fetcher;
|
||||
import spiffe.api.svid.Workload.X509SVID;
|
||||
import spiffe.api.svid.Workload;
|
||||
import spiffe.api.svid.X509SVIDFetcher;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static java.util.Collections.EMPTY_SET;
|
||||
|
||||
|
|
@ -20,6 +22,7 @@ import static java.util.Collections.EMPTY_SET;
|
|||
public class SpiffeIdManager {
|
||||
|
||||
private static final SpiffeIdManager INSTANCE = new SpiffeIdManager();
|
||||
private static final Logger LOGGER = Logger.getLogger(SpiffeIdManager.class.getName());
|
||||
|
||||
static SpiffeIdManager getInstance() {
|
||||
return INSTANCE;
|
||||
|
|
@ -35,6 +38,11 @@ public class SpiffeIdManager {
|
|||
*/
|
||||
private final FunctionalReadWriteLock guard;
|
||||
|
||||
/**
|
||||
* Used to make the getters wait until there's a spiffeSVID initialized
|
||||
*/
|
||||
private final CountDownLatch completedSpiffeSVIDUpdate = new CountDownLatch(1);
|
||||
|
||||
/**
|
||||
* Private Constructor
|
||||
*
|
||||
|
|
@ -43,11 +51,12 @@ public class SpiffeIdManager {
|
|||
*/
|
||||
private SpiffeIdManager() {
|
||||
guard = new FunctionalReadWriteLock();
|
||||
Fetcher<List<X509SVID>> svidFetcher = new X509SVIDFetcher();
|
||||
Fetcher<Workload.X509SVIDResponse> svidFetcher = new X509SVIDFetcher();
|
||||
svidFetcher.registerListener(this::updateSVID);
|
||||
}
|
||||
|
||||
public SpiffeSVID getSpiffeSVID() {
|
||||
awaitSpiffeSVID();
|
||||
return guard.read(() -> spiffeSVID);
|
||||
}
|
||||
|
||||
|
|
@ -55,21 +64,34 @@ public class SpiffeIdManager {
|
|||
* Method used as callback that gets executed whenever an SVID update is pushed by the Workload API
|
||||
* Uses a write lock to synchronize access to spiffeSVID
|
||||
*/
|
||||
private void updateSVID(List<X509SVID> certs) {
|
||||
X509SVID svid = certs.get(0);
|
||||
guard.write(() -> spiffeSVID = new SpiffeSVID(svid));
|
||||
private void updateSVID(Workload.X509SVIDResponse x509SVIDResponse) {
|
||||
guard.write(() -> spiffeSVID = new SpiffeSVID(x509SVIDResponse));
|
||||
completedSpiffeSVIDUpdate.countDown();
|
||||
LOGGER.log(Level.FINE, "Spiffe SVID has been updated ");
|
||||
}
|
||||
|
||||
X509Certificate getCertificate() {
|
||||
public X509Certificate getCertificate() {
|
||||
awaitSpiffeSVID();
|
||||
return guard.read(() -> spiffeSVID != null ? spiffeSVID.getCertificate() : null);
|
||||
}
|
||||
|
||||
PrivateKey getPrivateKey() {
|
||||
public PrivateKey getPrivateKey() {
|
||||
awaitSpiffeSVID();
|
||||
return guard.read(() -> spiffeSVID != null ? spiffeSVID.getPrivateKey() : null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<X509Certificate> getTrustedCerts() {
|
||||
return guard.read(() -> spiffeSVID != null ? spiffeSVID.getBundle() : EMPTY_SET);
|
||||
public Set<X509Certificate> getTrustedCerts() {
|
||||
awaitSpiffeSVID();
|
||||
return guard.read(() -> spiffeSVID != null ? spiffeSVID.getTrustedCerts() : EMPTY_SET);
|
||||
}
|
||||
|
||||
private void awaitSpiffeSVID() {
|
||||
try {
|
||||
completedSpiffeSVIDUpdate.await();
|
||||
} catch (InterruptedException e) {
|
||||
LOGGER.info("Interrupted " + e.getMessage());
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
package spiffe.provider;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import spiffe.api.svid.Workload;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
|
@ -20,37 +25,71 @@ public class SpiffeSVID {
|
|||
* The SPIFFE Identity String
|
||||
*/
|
||||
private String spiffeID;
|
||||
|
||||
/**
|
||||
* The SPIFFE Verifiable Identity Document
|
||||
*/
|
||||
private X509Certificate certificate;
|
||||
|
||||
/**
|
||||
* The Private Key associated to the Public Key of the certificate
|
||||
*/
|
||||
private PrivateKey privateKey;
|
||||
|
||||
/**
|
||||
* The trust chain used as the set of CAs trusted certificates
|
||||
*/
|
||||
private Set<X509Certificate> bundle;
|
||||
|
||||
/**
|
||||
* The map of Federated Trust Domains and their bundles
|
||||
*/
|
||||
private Map<String, Set<X509Certificate>> federatedBundles;
|
||||
|
||||
/**
|
||||
* All trusted CAs certs, including the Federated CAs
|
||||
*/
|
||||
private Set<X509Certificate> trustedCerts;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param x509SVID: Workload.X509SVID
|
||||
* @param x509SVIDResponse: Workload.X509SVIDResponse
|
||||
*
|
||||
*/
|
||||
SpiffeSVID(Workload.X509SVID x509SVID) {
|
||||
SpiffeSVID(Workload.X509SVIDResponse x509SVIDResponse) {
|
||||
try {
|
||||
certificate = CertificateUtils.generateCertificate(x509SVID.getX509Svid().toByteArray());
|
||||
bundle = CertificateUtils.generateCertificates(x509SVID.getBundle().toByteArray());
|
||||
privateKey = CertificateUtils.generatePrivateKey(x509SVID.getX509SvidKey().toByteArray());
|
||||
spiffeID = x509SVID.getSpiffeId();
|
||||
|
||||
Workload.X509SVID svid = x509SVIDResponse.getSvidsList().get(0);
|
||||
|
||||
certificate = CertificateUtils.generateCertificate(svid.getX509Svid().toByteArray());
|
||||
bundle = CertificateUtils.generateCertificates(svid.getBundle().toByteArray());
|
||||
privateKey = CertificateUtils.generatePrivateKey(svid.getX509SvidKey().toByteArray());
|
||||
spiffeID = svid.getSpiffeId();
|
||||
federatedBundles = buildFederatedX509CertificatesMap(x509SVIDResponse.getFederatedBundlesMap());
|
||||
|
||||
trustedCerts = new HashSet<>();
|
||||
trustedCerts.addAll(bundle);
|
||||
federatedBundles.values().forEach(set -> trustedCerts.addAll(set));
|
||||
} catch (Exception e) {
|
||||
LOGGER.log(Level.SEVERE, "SVID message could not be processed", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Set<X509Certificate>> buildFederatedX509CertificatesMap(Map<String, ByteString> federatedBundlesMap) {
|
||||
Map<String, Set<X509Certificate>> federatedCertificates = new HashMap<>();
|
||||
federatedBundlesMap.forEach((trustDomain, cert) -> {
|
||||
try {
|
||||
federatedCertificates.put(trustDomain, CertificateUtils.generateCertificates(cert.toByteArray()));
|
||||
} catch (CertificateException e) {
|
||||
LOGGER.log(Level.SEVERE, "Federated Bundles couldn't be processed ", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
return federatedCertificates;
|
||||
}
|
||||
|
||||
public String getSpiffeID() {
|
||||
return spiffeID;
|
||||
}
|
||||
|
|
@ -66,4 +105,12 @@ public class SpiffeSVID {
|
|||
public Set<X509Certificate> getBundle() {
|
||||
return bundle;
|
||||
}
|
||||
|
||||
public Map<String, Set<X509Certificate>> getFederatedBundles() {
|
||||
return federatedBundles;
|
||||
}
|
||||
|
||||
public Set<X509Certificate> getTrustedCerts() {
|
||||
return trustedCerts;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ message X509SVIDRequest { }
|
|||
|
||||
// The X509SVIDResponse message carries a set of X.509 SVIDs and their
|
||||
// associated information. It also carries a set of global CRLs, and a
|
||||
// TTL to inform the svid when it should check back next.
|
||||
// TTL to inform the workload when it should check back next.
|
||||
message X509SVIDResponse {
|
||||
// A list of X509SVID messages, each of which includes a single
|
||||
// SPIFFE Verifiable Identity Document, along with its private key
|
||||
|
|
@ -18,19 +18,19 @@ message X509SVIDResponse {
|
|||
repeated bytes crl = 2;
|
||||
|
||||
// CA certificate bundles belonging to foreign Trust Domains that the
|
||||
// svid should trust, keyed by the SPIFFE ID of the foreign
|
||||
// workload should trust, keyed by the SPIFFE ID of the foreign
|
||||
// domain. Bundles are ASN.1 DER encoded.
|
||||
map<string, bytes> federated_bundles = 3;
|
||||
}
|
||||
|
||||
// The X509SVID message carries a single WorkloadAPIClient and all associated
|
||||
// The X509SVID message carries a single SVID and all associated
|
||||
// information, including CA bundles.
|
||||
message X509SVID {
|
||||
// The SPIFFE ID of the WorkloadAPIClient in this entry
|
||||
// The SPIFFE ID of the SVID in this entry
|
||||
string spiffe_id = 1;
|
||||
|
||||
// ASN.1 DER encoded certificate chain. MAY include intermediates,
|
||||
// the leaf certificate (or WorkloadAPIClient itself) MUST come first.
|
||||
// the leaf certificate (or SVID itself) MUST come first.
|
||||
bytes x509_svid = 2;
|
||||
|
||||
// ASN.1 DER encoded PKCS#8 private key. MUST be unencrypted.
|
||||
|
|
@ -40,11 +40,14 @@ message X509SVID {
|
|||
// ASN.1 DER encoded
|
||||
bytes bundle = 4;
|
||||
|
||||
// List of trust domains the SVID federates with, which corresponds to
|
||||
// keys in the federated_bundles map in the X509SVIDResponse message.
|
||||
repeated string federates_with = 5;
|
||||
}
|
||||
|
||||
service SpiffeWorkloadAPI {
|
||||
// X.509-WorkloadAPIClient Profile
|
||||
// Fetch all SPIFFE identities the svid is entitled to, as
|
||||
// X.509-SVID Profile
|
||||
// Fetch all SPIFFE identities the workload is entitled to, as
|
||||
// well as related information like trust bundles and CRLs. As
|
||||
// this information changes, subsequent messages will be sent.
|
||||
rpc FetchX509SVID(X509SVIDRequest) returns (stream X509SVIDResponse);
|
||||
|
|
|
|||
Loading…
Reference in New Issue