Recently I have been working on building an app using Spring Boot with Java 11 and was prototyping a solution for external configuration using Spring Cloud Config Server. As a part of this, I wanted to make sure that the values I store for sensitive configuration such as passwords were encrypted. I also wanted to make sure that the security key for encrypting and decrypting these sensitive values was not stored in the config server application itself but instead was externalized. This post is about how I’ve come to a solution to do this. I’m not going to get into the details of how to set up a config server, rather this post will focus on the setup for ensuring that sensitive values are, in fact, secure.

Overview

My environment is Linux Pop_OS 20.04 with OpenJDK 11 using Spring Boot version 2.3.1. It is important to note from the start that the solution for encrypted config is not primarily a Spring Boot solution or even a Spring Cloud Config Server solution, rather it is a Java solution that utilizes a library called the Java Cryptography Extension or JCE for short. Before Java 9, this library was not offered as a part of the core Java implementation and you had to manually add it to your classpath. As of Java 9, this is no longer the case and it is now enabled by default in both Open JDK and Oracle JDK.

This means that the work you have to do to encrypt your sensitive config values is primarily done in the application config for your configuration server and in annotations that you apply to the values you want to encrypt. You will also need to take advantage of the /encrypt /decrypt endpoints offered by the Spring Cloud Config Server. The basic setup is that you have a Spring Cloud Config Server that manages the provisioning of the config values to your microservices. I have set up a GitHub repository used by the Configuration Server to version control the configuration. I also have a Microservice in place that uses the configuration values whether the value is encrypted or not. Here is a diagram that shows the Architecture Overview:

Pre-requisites for this

  • Java 11 or later installed and correctly configured
  • A simple Spring Boot Microservice to use for testing the encrypted configuration values
  • A Git or GitHub repo that stores the configuration
  • A Spring Cloud Config Server configured to use the repo

Checking JCE installation

In case you are not sure whether or not your installation and setup of Java have the JCE library included you can check by executing the following java script in a terminal:

jrunscript -e
'exit (
    println(javax.crypto.Cipher.getMaxAllowedKeyLength("AES") >= 256)
);'

The output of this command will print true if JCE policy files have been applied and false if not. If it returns false you are either running a version of java older than 9 or your JDK is not installed correctly. The first time I ran this, it returned false and it was because my JDK was not installed correctly. After a quick re-install, everything worked just fine.

Steps to Implement Encrypted Values

First – you will need to set up a key store that contains the security key to be used by the encryption server in converting plain text values to encrypted values. It is possible to avoid using a key store external to your configuration server and instead set a security key in your config server’s application config but this is less secure in my opinion. It is much better to externalize this security key from the config server so that if the config server is somehow compromised the encryption key you are using is not also compromised. To do this you can execute a command in your terminal that uses the following syntax which is further explained by Oracle in their keytool documentation.

keytool
    -genkeypair \
    -alias <encryption key alias> \
    -keyalg <encryption algorithm to use> \
    -dname "CN=<name>, \
            OU=<organizational unit>,\
            O=<organization>,\
            L=<city>,\
            S=<state>,\
            C=<country>"\
    -keypass <your key> \
    -keystore <path to keystore file> \
    -storepass <your key>

It is important to note that the values you assign to the following parameters of this command will be used as properties in the java command you run, in your terminal, to start the config server. Therefore it is important to remember the values of these parameters when using keytool to create your external config server key store. They are; -alias, -keypass, and -keystore. Also, you will need to use the same value for both the -keystore and -storepass parameters when you run keytool otherwise you will get an error.

Second – Once you have created the keystore you are now ready to start your configuration server with the parameters. You will need to have built and packaged the Config Server so that you can start it from the command line using the following command:

java -jar <path to config server jar> \
    --encrypt.key-store.location=<path to keystore> \
    --encrypt.key-store.password=< -keypass value> \
    --encrypt.key-store.alias=<-alias value> \
    --encrypt.key-store.secret=< -keypass value>

You could configure all of this in the config server application config file (.properties / .yml) but by externalizing these properties as parameters passed to the java command, you are taking a more secure approach. It is important to note that you must prefix the value of <path to keystone> with “file:” so that java knows you are referencing a physical file on the server. This may look something like file:/path-to-keystore-file/filename.

When you run this command, if you’ve done everything correctly Spring Boot will start your configuration server.

Third – You will need to use the /encrypt endpoint exposed by the Configuration Servers Encryption API to retrieve an encrypted value for the configuration values you intend to securely store in your repository, in my case the GitHub repository. You can do this using a tool such as Postman. In the body of your request, you should type out the config value you want to encrypt. The response you will get should contain the encrypted value you will put in your repo, here is an example of what that looks like in Postman:

Fourth – you should add this value to your config file that is in your repository and prefix it with {cipher}. Here is an example of a .properties file using the value above:

db.password={cipher}AQAfm21zwJjaOud1EWhkWPMilCG4mS4qUXiCaXcQRJqLBubE4JV0K4TwGQHnNBUuq48jXZmfrt2JTDAVQv1GMSo07xdheFb/wfCdtWD8CXB5BFYTSL5tZamX0zijNhZY7bx0pIK1KC55izzFoevMAQItBDp0pm2wBiuXQ85gXRdFREGxBHcInb39CD+dzXoEUGPhyLpvL1dvfnil+nCE23M+DRlWB7KNICR2df5e67XVGAGl80ob38k7Ifq/Vf4QX9A40A+85aqhE4NTgErsv5kRkPvOr6O1NGNF82RJjIl/iVpVkGRwSgbk1Bax9qDwvoT2B0MqvQlAvf5O1mN2rUvQdw9BABlrOpoy/CQmgxX65ez6Do8VHBGaB3J6ezERpwCmfUDxgFEYIbzsVU4Nl2Zo

Here is an example of a .yml file. For YAML based configurations, it is important that you wrap your value in single quotes, otherwise, YAML will think that you are specifying a list of values rather than treat it as an encrypted value and you will not get back the decrypted value as expected.

db.password: ‘{cipher}AQAfm21zwJjaOud1EWhkWPMilCG4mS4qUXiCaXcQRJqLBubE4JV0K4TwGQHnNBUuq48jXZmfrt2JTDAVQv1GMSo07xdheFb/wfCdtWD8CXB5BFYTSL5tZamX0zijNhZY7bx0pIK1KC55izzFoevMAQItBDp0pm2wBiuXQ85gXRdFREGxBHcInb39CD+dzXoEUGPhyLpvL1dvfnil+nCE23M+DRlWB7KNICR2df5e67XVGAGl80ob38k7Ifq/Vf4QX9A40A+85aqhE4NTgErsv5kRkPvOr6O1NGNF82RJjIl/iVpVkGRwSgbk1Bax9qDwvoT2B0MqvQlAvf5O1mN2rUvQdw9BABlrOpoy/CQmgxX65ez6Do8VHBGaB3J6ezERpwCmfUDxgFEYIbzsVU4Nl2Zo’

Be sure to commit the changed config value and then you should be good to go.

Conclusion

Now your sensitive config values are encrypted and your config server is set up to decrypt the values when they are requested by your Microservice. The next time you run your app or view your config through your browser you will see the plain text values being used even though they are encrypted in your configuration repo. This is a much more secure solution. Some advisable next steps would be to; add Spring Security to your config server app to prevent unauthorized users from viewing your config in a browser, and if your config repo is being connected to your config server outside of your LAN you should also be sure that this connection takes place over HTTPS.