java-spiffe/java-spiffe-provider
Max Lambrecht 3e81bee7ff Fixing checkstyle issues.
Signed-off-by: Max Lambrecht <maxlambrecht@gmail.com>
2020-06-26 13:23:53 -03:00
..
src Fixing checkstyle issues. 2020-06-26 13:23:53 -03:00
README.md Fixing links in README. 2020-06-24 15:40:52 -03:00
build.gradle Move modules versions to a single parent version to simplify versioning. 2020-06-10 14:43:13 -03:00

README.md

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.

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 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
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. 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.

    // 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

Java PKI Programmer's Guide