HashiCorp Vault is a secret and encryption management system that allows your organization to secure sensitive information such as API keys, certificates, and passwords.
In this article, I will show how a Java Spring Boot web application deployed into a Kubernetes cluster can fetch a secret directly from the Vault server using the Spring Cloud Vault library.
This means that sensitive values no longer need to be baked into the image, set in kubernetes yaml manifest files, placed into base64-encoded Kubernetes secrets, or volume mounted to the filesystem of the container.
Prerequisite
I am assuming you already have a Vault server installed, with the Kubernetes auth method enabled. If you do not, see my previous article on installing a development Vault server on a Kubernetes cluster.
Deploy Vault enabled Spring app to Kubernetes
We will go through implementation details in later sections, but first let’s just deploy the Spring Boot web app into your Kubernetes cluster so we can see the end result of fetching a Vault secret.
Example Vault secret and details
This Spring app was created generic enough that you can specify your own secret path and role, pointing at any remote Vault server. But for this article, I will be using the values setup in my previous article and you should feel free to replace it with your environment specific details.
Based on the previous article, there exists a kv2 Vault secret at “secret/webapp/config” with the following keys:
- foo=bar
- username=static-user
- password=static-password
There is a Vault role named “myrole” and policy named “mypolicy” which provides ‘read’ permission to the secret path above.
Kubernetes auth is enabled on the Vault server, using the Kubernetes Service Account “vault-auth” in the Kubernetes namespace “vault”.
Apply yaml manifest to Kubernetes cluster
# fetch source code for this article cd ~ git clone https://github.com/fabianlee/spring-boot-web-with-spring-cloud-vault.git && cd $(basename $_ .git) # Kubernetes deployment name, namespace, and service account export app=spring-boot-web-vault export namespace_k8s=vault export service_account_k8s=vault-auth echo "Deployment '$app' going into k8s namespace '$namespace_k8s' running under the service account '$service_account_k8s'" # Vault server location export vault_uri=http://vault.vault.svc.cluster.local:8200 # Your specific Vault role and secret path export vault_role=myrole export vault_backend=secret export vault_context=webapp export vault_profile=config echo "going to fetch kv2 secret using role '$vault_role' at path: $vault_backend/data/$vault_context/$vault_profile" # generate from template and deploy to cluster cat src/main/resources/kubernetes/deployment.yaml | DOLLAR_SIGN='$' envsubst | kubectl apply -f - # deployment should now exist kubectl get deployment $app -n $namespace_k8s
Validate fetch of secret from Vault server
First, we test an explicit fetch of the secret from the Vault server using the VaultTemplate class, which returns a VaultResponse.
# fetch secret using Spring VaultTemplate class $ kubectl exec -it deployment/$app -n $namespace_k8s -c main -- curl http://localhost:8080/secret The secret is loaded using VaulTemplate from 'secret/data/webapp/config': password=static-password foo=bar username=static-username
Another mechanism is to have Spring ConfigData automatically fetch the secret, based on the settings in application.properties and place it into an Environment.
# secret auto-fetched by Spring ConfigData using values in application.properties $ kubectl exec -it deployment/$app -n $namespace_k8s -c main -- curl http://localhost:8080/secretConfigData The secret is loaded by ConfigData based on Spring application.properties settings: password=static-password foo=bar username=static-user
Implementation Details
Now that you’ve seen the Spring Boot web app successfully fetch the Vault secret directly and show its keys, let’s look at some critical sections of configuration and code.
Spring Cloud Vault library
Add the spring-cloud-starter-vault-config to your build.gradle dependencies. This will include Spring Cloud Vault library.
dependencies { ... implementation 'org.springframework.cloud:spring-cloud-starter-vault-config' }
Spring application.properties keys
For configuration of the Spring Cloud Vault libraries, use the following basic keys in application.properties.
spring.cloud.vault.enabled=true # URL can be to remote Vault server, in-cluster, or local Vault sidecar proxy spring.cloud.vault.uri=http://vault.vault.svc.cluster.local:8200 #spring.cloud.vault.namespace=default #only enterprise version # 'kubernetes' authentication mode for Vault spring.cloud.vault.authentication=KUBERNETES spring.cloud.vault.kubernetes.role=myrole spring.cloud.vault.kubernetes.kubernetes-path=kubernetes spring.cloud.vault.kubernetes.service-account-token-file=/var/run/secrets/kubernetes.io/serviceaccount/token
Add the keys below to support auto-fetch of a secret with ConfigData.
# Spring ConfigData spring.cloud.vault.kv.enabled=true spring.config.import=vault:// # used to build full path to secret spring.cloud.vault.kv.backend=secret spring.cloud.vault.kv.default-context=webapp spring.cloud.vault.kv.profiles=config
Pulling Vault Secret with VaultTemplate
Our MyVaultTemplateService.java uses the VaultTemplate to fetch the Vault secret.
// fullSecretPath is '/secret/data/webapp/config' response = vaultTemplate.read(fullSecretPath); // go through response from Vault server Object vaultdata = response.getRequiredData().get("data"); LinkedHashMap <String,String>secretmap =(LinkedHashMap<String,String>)vaultdata; // go through each key in secret, add to Properties for return Iterator secretmapit = secretmap.keySet().iterator(); while(secretmapit.hasNext()) { String secretkey = secretmapit.next(); String secretval = secretmap.get(secretkey); props.put(secretkey, secretval); }
Spring ConfigData auto-pull of secret
The Spring ConfigData can also automatically fetch a secret, if the ‘spring.cloud.vault.kv.enabled=true’ and ‘spring.config.import=vault://’ settings in application.properties are enabled.
It uses the keys ‘spring.cloud.vault.kv.backend’, ‘spring.cloud.vault.kv.default-context’, and ‘spring.cloud.vault.kv.profiles’ to construct the full path to the secret, and places it into an Environment. If you already know the expect key name in the secret, you can simply call Environment.getProperty()
@Autowired Environment env; ... // ConfigData already fetched secret and placed into Environment public String getSecretConfigData(String key) { return env.getProperty(key); }
Unfortunately, you cannot simply enumerate the secret keys in the Environment object, so I also wrote a SpringEnvironmentUtils.java that can assist in pulling out just the secret keys from the Environment.
Properties props = SpringEnvironmentUtils.getAllKnownProperties(env,targetPropertySourceType,targetVaultPath);
GreetingController for exposing web endpoints
This is a Spring Boot web app, and we use the GreetingController.java to expose the HTTP endpoints used in this testing.
@GetMapping("/secret") @ResponseBody public String secretVaultTemplate() { ... } @GetMapping("/secretConfigData") @ResponseBody public String secretConfigData() { ... }
REFERENCES
Spring docs, Spring Cloud Vault intro
Github source code for this article spring-boot-web-with-spring-cloud-vault
HashiCorp, compare sidecar to CSI to Operator
Spring docs, VaultTemplate explanation
Spring docs, ConfigData explanation