Using Azure Key Vault Secrets as Spring Properties in Azure Kubernetes Service

October 29, 2019 - 3 minute read

Time for some probably too good for your own good magic. The Azure Key Vault is Azures solution to deliver cryptographic secrets in a cloud environment. You can use this to e.g. store passwords and certificates in a centralized yet secure location in a cloud enviroment.

So how do you get these secrets available to your containerized Spring Boot App? Well you can use the Key Vault Library for example, but if for some reason you don’t want or are unable to modify the implementation you need to come up with a way to pass the secrets to the application during runtime.

We are going to use FlexVolume for this. It allows you to access secrets, keys and certificates from your pod. It will simply mount the desired secrets into a file that is available inside the container, e.g. /mnt/secret.property which then contains the secret value.

Setting it up

First you need to go to The Azure Key Vault page to get all the necessary bootstrapping done. Once you have gone through those hoop, you should be ready to mount secrets into the pod.

Say for example we have a database password we would like to mount into the container. To mount /secrets/database-password to contain this password we first declare the VolumeMount for Kubernetes:

volumeMounts:
   - name: test
      mountPath: /secrets
      readOnly: true

Then we could use this in our container, for example nginx:

apiVersion: v1
kind: Pod
metadata:
  name: nginx-flex-kv
spec:
  containers:
  - name: nginx-flex-kv
    image: nginx
    volumeMounts:
    - name: test
      mountPath: /secrets
      readOnly: true
  volumes:
  - name: test
    flexVolume:
      driver: "azure/kv"
      secretRef:
        name: kvcreds                             
      options:
        keyvaultname: "my-key-vault"              
        keyvaultobjectnames: "key-vault-database-password-key"  
     	keyvaultobjectaliases: "database-password" 
        keyvaultobjecttypes: secret               
        resourcegroup: "testresourcegroup"       
        subscriptionid: "testsub"                 
        tenantid: "testtenant"  

This would mount the key vault secret named “key-vault-database-password-key” into /secrets/database-password and it contains the password as its content.

This is fine and dandy, but how do you make the application know that there is a file called /secrets/database-password and its contents is the password? Or worse, what if you can’t even modify this? Say the application assumes that the database password must be in a Spring property called app.database.password?

Creating Spring Properties File From The Secrets

Let’s create a single file with all the secrets and format it as a Spring properties file. Let’s start with a Kubefile again, say we have our app image my-spring-boot-app:

apiVersion: v1
kind: Pod
metadata:
  name: my-spring-boot-app
spec:
  containers:
  - name: my-spring-boot-app
    image: my-app-image
    volumeMounts:
    - name: test
      mountPath: /secrets/
      readOnly: true
  volumes:
  - name: flexvol
    flexVolume:
      driver: "azure/kv"
      secretRef:
        name: kvcreds                             
      options:
        keyvaultname: "my-key-vault"
        keyvaultobjectnames: "db-password;ssl-key"
        keyvaultobjectaliases: "app.db.password;app.ssl.key"
        keyvaultobjecttypes: secret;secret
        resourcegroup: "my-rg"
        subscriptionid: "my-testsub"
        tenantid: "my-tenant"    

With this, we would have a directory /secrets/ with two files, app.db.password and app.ssl.key. So now we have files names with values, but we need a single file with properties with values. So we want to get this data into a single file that has a key-value pair of this data, such as /app/secrets.properties:

app.db.password=324r2rfdlskj
app.ssl.key=234riojgfds89glk

We can modify our Docker container to read all the secrets from disk and append them into a single file and then start the container:

So this is our Spring boot application container Dockerfile:

FROM anapsix/alpine-java:8
ARG JAR_FILE
ENV FINAL_JAR=/app/$JAR_FILE
ENV KVMOUNT=/secrets
ENV PROPERTIES_FILE=/config/application.properties
ENV SECRET_FILE=/app/secrets.properties
ENV ENTRYCLASS=fi.lahtela.demo
WORKDIR /app
COPY target/$JAR_FILE .
# Extract the kvmount as key value pairs generate a properties file
ENTRYPOINT echo > $SECRET_FILE && for i in $(ls $KVMOUNT 2>/dev/null); do echo "$i=$(cat $KVMOUNT/$i)" >> $SECRET_FILE; done && java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -cp $FINAL_JAR -Dloader.path=/app/lib/ -Dspring.config.location=$PROPERTIES_FILE,$SECRET_FILE -Dloader.main=$ENTRYCLASS org.springframework.boot.loader.PropertiesLauncher

During startup, the docker container appends all the secrets to the /app/secrets.properties file. Here _/config/application.properties would be hour Spring properties file that contains all the other properties. So we have two files, one with the secrets, and another with everything else.

This could also be a separate layer in the Docker container for the same effect.

Updated: