Key loaders

What is a key loader?

Key loaders are a mechanism to load private keys and certificates from outside the repository.

This is only available in versions 1.3.2-1276 and later.

A key loader is a Java class that implements the KeyLoader interface:

public interface KeyLoader {

Map<String, Object> loadKeys(Map<String, Object> params);

}

If a key loader is provided, Gallium Data will call it on startup. The key loader will be expected to return (possibly empty) arrays of KeyManager and/or TrustManager objects (see example below).

The KeyManager(s) and TrustManager(s) returned by the key loader will be added to all projects, in addition to any keys and/or certificates they may contain in the repository.

The key loader will also be invoked every time the repository is published.


Compiling

A key loader can be compiled by using the Gallium Data Java library -- the same as the one used for Java filters.

The Java class will need to use the following libraries (here in Maven form):

<dependency>

<groupId>galliumdata</groupId>

<artifactId>galliumdata-filter-library</artifactId>

<version>1.3.2-1276</version>

</dependency>

Note that the exact version will depend on which version of Gallium Data you are using.

In addition, you'll need to add the following:

<repositories>

<repository>

<id>gallium-repo</id>

<url>https://maven.galliumdata.com/</url>

</repository>

</repositories>

This will allow Maven to find the galliumdata:galliumdata-filter-library jar.


Installation

A key loader is installed by making its Java class(es) available to Gallium Data, usually in the form of a jar file in the /galliumdata/jars/ directory of the Docker image, or by making the jar available from a Maven repository and adding it to the repository.

Once the classes are available to Gallium Data, the key loader is specified by setting the environment variable GALLIUM_KEY_LOADER to the full name of the key loader class. So when starting Gallium Data from the command line, it might look something like:

docker run -d -p 5432:5432 -e GALLIUM_KEY_LOADER=com.acme.security.MyKeyLoader galliumdata/gallium-data-engine:<version>

If the specified class is not found, or if there is any problem during the invocation of the key loader, Gallium Data will continue the startup sequence but will log an error message describing the nature of the problem.

Example

Here is a simple key loader that reads a private key and its certificate, and a CA certificate from PEM files.

Your implementation might differ quite a bit. You might for instance retrieve a private key from Kubernetes, or AWS Secrets Manager, or any other solution.


package com.galliumdata.server.security;


import org.apache.commons.codec.binary.Base64;


import javax.net.ssl.KeyManager;

import javax.net.ssl.KeyManagerFactory;

import javax.net.ssl.TrustManager;

import javax.net.ssl.TrustManagerFactory;

import java.io.ByteArrayInputStream;

import java.nio.file.Files;

import java.nio.file.Paths;

import java.security.KeyFactory;

import java.security.KeyStore;

import java.security.PrivateKey;

import java.security.cert.Certificate;

import java.security.cert.CertificateFactory;

import java.security.spec.PKCS8EncodedKeySpec;

import java.util.HashMap;

import java.util.Map;


/**

* An example KeyLoader that reads a private key and certificates from a set of PEM files

*/

public class SampleKeyLoader implements KeyLoader {


private static final String BASE_DIR = System.getenv("SAMPLE_KEY_LOADER_BASE_DIR");


@Override

public Map<String, Object> loadKeys(Map<String, String> params) {

System.out.println("### SampleKeyLoader is loading keys");

Map<String, Object> res = new HashMap<>();


try {

// Read the private key and certificate

PrivateKey privateKey = readPrivateKey();

Certificate serverCert = readCertificate("server-cert.pem");


// Create the KeyManagers

KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());

keystore.load(null, null);

keystore.setCertificateEntry("server-cert", serverCert);


Certificate[] keyCerts = new Certificate[]{serverCert};

KeyStore.PrivateKeyEntry entry = new KeyStore.PrivateKeyEntry(privateKey, keyCerts);

keystore.setEntry("server-key", entry, new KeyStore.PasswordProtection("GalliumData".toCharArray()));


KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");

kmf.init(keystore, "GalliumData".toCharArray());

res.put(KeyLoader.KEY_LOADER_RETURN_KEY_MANAGERS, kmf.getKeyManagers());


// Create the TrustManagers

Certificate caCert = readCertificate("ca.pem");

KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());

trustStore.load(null, null);

trustStore.setCertificateEntry("ca-cert", caCert);

TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());

tmf.init(trustStore);

res.put(KeyLoader.KEY_LOADER_RETURN_TRUST_MANAGERS, tmf.getTrustManagers());

}

catch(Exception ex) {

ex.printStackTrace();

throw new RuntimeException(ex);

}


return res;

}


private PrivateKey readPrivateKey() {

try {

String pemStr = new String(Files.readAllBytes(Paths.get(BASE_DIR + "/server-key.pem")));

pemStr = pemStr.replace("-----BEGIN PRIVATE KEY-----", "");

pemStr = pemStr.replace("-----END PRIVATE KEY-----", "");

byte[] decoded = Base64.decodeBase64(pemStr.trim());


PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decoded);

KeyFactory keyFactory = KeyFactory.getInstance("RSA");

return keyFactory.generatePrivate(keySpec);

}

catch(Exception ex) {

ex.printStackTrace();

throw new RuntimeException(ex);

}

}


private Certificate readCertificate(String fileName) {

try {

String pemStr = new String(Files.readAllBytes(Paths.get(BASE_DIR + "/" + fileName)));

pemStr = pemStr.replace("-----BEGIN CERTIFICATE-----", "");

pemStr = pemStr.replace("-----END CERTIFICATE-----", "");

byte[] decoded = Base64.decodeBase64(pemStr.trim());


CertificateFactory cf = CertificateFactory.getInstance("X.509");

return cf.generateCertificate(new ByteArrayInputStream(decoded));

}

catch(Exception ex) {

ex.printStackTrace();

throw new RuntimeException(ex);

}

}

}