java-spiffe/java-spiffe-provider/README.md

227 lines
8.6 KiB
Markdown

# Java SPIFFE Provider
This module provides a Java Security Provider implementation supporting X.509-SVIDs and methods for
creating SSLContexts that are backed by the Workload API.
## Create an SSL Context backed by the Workload API
To create an SSL Context that uses a `X509Source` backed by the Workload API, having the environment variable
` SPIFFE_ENDPOINT_SOCKET` defined with the Workload API endpoint address, and the `ssl.spiffe.accept`
Security property defined in the `java.security` file containing the list of SPIFFE IDs that the current workload
will trust for TLS connections:
```
X509Source source = X509Source.newSource();
SslContextOptions options = SslContextOptions
.builder()
.x509Source(source)
.build();
SSLContext sslContext = SpiffeSslContextFactory.getSslContext(options);
```
See [HttpsServer example](src/test/java/io/spiffe/provider/examples/mtls/HttpsServer.java).
Alternatively, a different Workload API address can be used by passing it to the X509Source creation method, and a
`Supplier` of a Set of accepted SPIFFE IDs can be provided as part of the `SslContextOptions`:
```
X509SourceOptions sourceOptions = X509SourceOptions
.builder()
.spiffeSocketPath("unix:/tmp/agent.sock")
.build();
X509Source x509Source = X509Source.newSource(sourceOptions);
Supplier<Set<SpiffeId>> spiffeIdSetSupplier = () -> Collections.singleton(SpiffeId.parse("spiffe://example.org/test"));
SslContextOptions sslContextOptions = SslContextOptions
.builder()
.acceptedSpiffeIdsSupplier(spiffeIdSetSupplier)
.x509Source(x509Source)
.build();
SSLContext sslContext = SpiffeSslContextFactory.getSslContext(sslContextOptions);
```
See [HttpsClient example](src/test/java/io/spiffe/provider/examples/mtls/HttpsClient.java) that defines a Supplier for providing
the list of SPIFFE IDs from a file.
## Plug Java SPIFFE Provider into Java Security architecture
Java Security Providers are configured in the master security properties file `<java-home>/jre/lib/security/java.security`.
The way to register a java security provider is by specifying the custom `Provider` subclass name and the priority in the
following format:
```
security.provider.<n>=<className>
```
This declares a provider, and specifies its preference order `n`.
### Copy the JAR to the JVM extensions
For installing the JAR file containing the provider classes as a bundled extension in the java platform,
copy `build/libs/java-spiffe-provider-<version>-all.jar` to `<java-home>/jre/lib/ext`.
#### Register the SPIFFE Provider
The master security properties file can be extended. Create a file `java.security` with the following content:
```
# Add the spiffe provider, change the <n> for the correct consecutive number
security.provider.<n>=io.spiffe.provider.SpiffeProvider
# Configure the default KeyManager and TrustManager factory algorithms
ssl.KeyManagerFactory.algorithm=Spiffe
ssl.TrustManagerFactory.algorithm=Spiffe
# The list of spiffeIDs that will be authorized, separated by a pipe character
ssl.spiffe.accept=spiffe://example.org/workload | spiffe://example.org/workload2 | spiffe://example2.org/workload
```
In this `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, separated by the pipe character.
***If the property is not present or if it's empty, no SPIFFE ID will be authorized.***
To pass your custom security properties file through the command line via system property when starting the JVM:
```
-Djava.security.properties=<path to java.security>
```
The properties defined in your custom properties file will override the properties in the master file.
The property `ssl.spiffe.accept` can also be defined through a System property passed as `-Dssl.spiffe.accept=`;
#### Accept all SPIFFE IDs
By default, only the SPIFFE IDs defined in the property `ssl.spiffe.accept` are accepted for a TLS connection. Thus,
if the property is empty or not defined, no SPIFFE ID will be accepted. To accept all SPIFFE IDs it should be used
the property `ssl.spiffe.acceptAll` and set as `true` in the Security properties file:
```
ssl.spiffe.acceptAll=true
```
or through a System property: `-Dssl.spiffe.acceptAll=true`.
It can also be configured when the SSL Context is created programmatically setting as `true` the option `acceptAnySpiffeId`
in the `SslContextOptions`:
```
SslContextOptions sslContextOptions = SslContextOptions
.builder()
.x509Source(x509Source)
.acceptAnySpiffeId(true)
.build();
SSLContext sslContext = SpiffeSslContextFactory.getSslContext(sslContextOptions);
```
#### Configure Workload API Socket Endpoint
The socket endpoint can be configured defining an environment variable named `SPIFFE_ENDPOINT_SOCKET`:
```
export SPIFFE_ENDPOINT_SOCKET=/tmp/agent.sock
```
## Use Cases
### Configure a Tomcat connector
***Prerequisite***: Having the SPIFFE Provider configured through the `java.security`.
A Tomcat TLS connector that uses the `Spiffe` KeyStore can be configured as follows:
```
<Connector
protocol="org.apache.coyote.http11.Http11NioProtocol"
port="8443" maxThreads="200"
scheme="https" secure="true" SSLEnabled="true"
keystoreFile="" keystorePass=""
keystoreType="Spiffe"
clientAuth="true" sslProtocol="TLS"/>
```
### Create mTLS GRPC server and client
Prerequisite: Having the SPIFFE Provided configured through the `java.security`.
A `GRPC Server` using an SSL context backed by the Workload API:
```
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(SpiffeProviderConstants.ALGORITHM, SpiffeProviderConstants.PROVIDER_NAME);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(SpiffeProviderConstants.ALGORITHM, SpiffeProviderConstants.PROVIDER_NAME);
SslContextBuilder sslContextBuilder =
SslContextBuilder
.forServer(keyManagerFactory)
.trustManager(trustManagerFactory);
Server server = NettyServerBuilder.forPort(9000)
.sslContext(GrpcSslContexts.configure(sslContextBuilder)
.clientAuth(ClientAuth.REQUIRE)
.build())
.build();
server.start();
```
#### Configure it programmatically:
The `SpiffeKeyManager` and `SpiffeTrustManager` can be created without resorting to factories, providing the constructors
with a [X509Source instance](../java-spiffe-core/README.md#x509-source).
```
// create a new X.509 source using the default socket endpoint address
X509Source x509Source = X509Source.newSource();
KeyManager keyManager = new SpiffeKeyManager(x509Source);
// TrustManager gets the X509Source and the supplier of the Set of accepted SPIFFE IDs.
TrustManager trustManager = new SpiffeTrustManager(x509Source, () -> SpiffeIdUtils.toSetOfSpiffeIds("spiffe://example.org/workload-client", ','));
SslContextBuilder sslContextBuilder =
SslContextBuilder
.forServer(keyManager)
.trustManager(trustManager);
Server server = NettyServerBuilder.forPort(9000)
.addService(new GreetingServiceImpl())
.sslContext(GrpcSslContexts.configure(sslContextBuilder)
.clientAuth(ClientAuth.REQUIRE)
.build())
.build();
```
For the client, a `ManagedChannel` would be created using the `SpiffeKeyManager` and `SpiffeTrustManager` for configuring
the GRPC SSL context, analogous to the config for the Server:
```
X509Source x509Source = X509Source.newSource();
KeyManager keyManager = new SpiffeKeyManager(x509Source);
TrustManager trustManager = new SpiffeTrustManager(x509Source, () -> SpiffeIdUtils.toSetOfSpiffeIds("spiffe://example.org/workload-server", ','));
SslContextBuilder sslContextBuilder = SslContextBuilder
.forClient()
.trustManager(trustManager)
.keyManager(keyManager)
.clientAuth(ClientAuth.REQUIRE);
ManagedChannel channel = NettyChannelBuilder.forAddress("localhost", 9000)
.sslContext(GrpcSslContexts.configure(sslContextBuilder).build())
.build();
```
## References
[How to Implement a Provider in the Java Cryptography Architecture](https://docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/HowToImplAProvider.html)
[Java PKI Programmer's Guide](https://docs.oracle.com/javase/8/docs/technotes/guides/security/certpath/CertPathProgGuide.html)