This project shows how to use HashiCorp Vault to manage dynamic database credentials with a Spring Boot app deployed to Kubernetes.
These credentials are not known by the app or even set in Kubernetes descriptors: Vault will take care of generating credentials, and make sure these are periodically updated. Using Spring Cloud Vault you can leverage Vault in your app without having to write any code: you'll be able to securely connect to your database.
Create a Docker image using pack
CLI from
Cloud Native Buildpacks:
$ pack build myuser/k8s-vault-dynamic-credentials --publish
Replace myuser
with your Docker ID: this image will be pushed to
Docker Hub.
Of course, you could build a Docker image the old way using a Dockerfile
,
but I'm a lazy person: just let Cloud Native Buildpacks build a secure Docker
image for you!
You may skip this step, by using the already built Docker image available
under alexandreroman/k8s-vault-dynamic-credentials
.
I assume you have access to a Kubernetes cluster.
Use Helm to deploy a PostgreSQL database instance:
$ helm upgrade mydb stable/postgresql \
--set postgresqlPassword=mysupersecretpassword \
--set postgresqlDatabase=mydb \
--timeout 600 --install
The database is named mydb
: admin credentials are postgres
/ mysupersecretpassword
.
Use these Kubernetes descriptors to deploy a sample app:
$ kubectl apply -f k8s
This app will not work until you set up Vault to manage database credentials: let's do this.
Use these instructions to deploy a Vault instance to your Kubernetes cluster.
Make sure everything works by opening a connection to your Vault instance:
$ kubectl port-forward vault-0 8200
Connect to your Vault instance:
$ export VAULT_ADDR=http://127.0.0.1:8200
$ vault status
Key Value
--- -----
Seal Type shamir
Initialized true
Sealed false
Total Shares 1
Threshold 1
Version 1.2.1
Cluster Name vault-cluster-757e0511
Cluster ID 17deeeb1-0523-a92d-911f-64aa46625f52
HA Enabled false
You should be able to log in using the root
token
(please remember this Vault instance is running in development mode):
$ vault login
Token (will be hidden): root
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.
Key Value
--- -----
token root
token_accessor 8pOjQwkoInyYtOQU34sPWHsJ
token_duration ∞
token_renewable false
token_policies ["root"]
identity_policies []
policies ["root"]
You now have a running Vault instance, an empty PostgreSQL database instance, and an idle Spring Boot app: let's connect the dots!
The Spring Boot app will connect to the Vault instance by authenticating using the
approle
method:
$ vault auth enable approle
Success! Enabled approle auth method at: approle/
Use this policy to allow database credentials access:
$ vault policy write k8s-vault-dynamic-credentials k8s/app-policy.hcl
Success! Uploaded policy: k8s-vault-dynamic-credentials
Create approle credentials:
$ vault write auth/approle/role/k8s-vault-dynamic-credentials-role \
secret_id_ttl=30m \
token_num_uses=10 \
token_ttl=30m \
token_max_ttl=60m \
secret_id_num_uses=40 \
policies="k8s-vault-dynamic-credentials"
Success! Data written to: auth/approle/role/k8s-vault-dynamic-credentials-role
Get the role-id
credential:
$ vault read auth/approle/role/k8s-vault-dynamic-credentials-role/role-id
Key Value
--- -----
role_id 716cbb29-fc91-a4dd-b772-8713d5b3b37f
You need role-id
and secret-id
credentials to authenticate your
Spring Boot app with Vault. Let's generate a secret-id
:
$ vault write -f auth/approle/role/k8s-vault-dynamic-credentials-role/secret-id
Key Value
--- -----
secret_id 9f05d1cf-89e7-20f5-58d4-997595dd6731
secret_id_accessor 76efa1ab-18d0-02f2-fe5a-d6af6bbe95f1
Beware that secret-id
credentials will expire in 30 min
(see parameter secret_id_ttl
used when you created the role-id
):
you may have to generate a new value if you deploy a new app instance.
Keep role-id
and secret-id
credentials handy:
you will need these values in a couple of minutes.
Create a Vault database secrets engine:
$ vault secrets enable database
Success! Enabled the database secrets engine at: database/
Configure Vault database access:
$ vault write database/config/mydb \
plugin_name=postgresql-database-plugin \
allowed_roles="mydb-role" \
connection_url="postgresql://{{username}}:{{password}}@mydb-postgresql.default.svc.cluster.local:5432/mydb?sslmode=disable" \
username="postgres" \
password="mysupersecretpassword"
Vault will use this configuration to connect to the database. Only Vault is aware of the admin password, not your apps.
Configure database credentials generation:
$ vault write database/roles/mydb-role \
db_name=mydb \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl="5m" \
max_ttl="24h"
Success! Data written to: database/roles/mydb-role
At this point, Vault will automatically create credentials when an app is requesting database access: these credentials will expire after 5 min.
As you can see, you can use these credentials to connect to your database:
$ vault read database/creds/mydb-role
Key Value
--- -----
lease_id database/creds/mydb-role/uIbLkdxJD4YjzCdDmGtBPhQs
lease_duration 5m
lease_renewable true
password A1a-W4YhHBvALXmcv0Ci
username v-token-mydb-rol-60JlLhdh2TyQwypqh6MY-1566290708
Create file bootstrap-kubernetes.yml
:
$ cp k8s/bootstrap-kubernetes.template bootstrap-kubernetes.yml
Edit this file, and set role-id
and secret-id
credentials, and the Vault URL:
spring:
cloud:
vault:
enabled: true
# Set path to your Vault service instance.
uri: http://vault.default.svc.cluster.local:8200
authentication: APPROLE
app-role:
# Insert Vault role-id and secret-id here.
role-id: 716cbb29-fc91-a4dd-b772-8713d5b3b37f
secret-id: 9f05d1cf-89e7-20f5-58d4-997595dd6731
role: k8s-vault-dynamic-credentials-role
generic:
# Disable secret store access since it's not used in this configuration.
enabled: false
database:
# Enable database dynamic credentials.
enabled: true
role: mydb-role
backend: mydb
Create file application-kubernetes.yml
:
$ cp k8s/application-kubernetes.yml.template application-kubernetes.yml
Edit this file, and set the database URL:
spring:
datasource:
# Set path to your PostgreSQL database instance.
url: "jdbc:postgresql://mydb-postgresql.default.svc.cluster.local/mydb"
# You do not need to set database credentials here:
# Spring Cloud Vault will take care of setting username/password properties
# by retrieving credentials from Vault when the app starts.
Deploy these configuration files to your cluster:
$ kubectl -n k8s-vault-dynamic-credentials create configmap app \
--from-file bootstrap-kubernetes.yml --from-file application-kubernetes.yml
Check your app is running fine:
kubectl -n k8s-vault-dynamic-credentials get pods
NAME READY STATUS RESTARTS AGE
app-596cb5465f-lp4zq 1/1 Running 0 29m
Get the allocated IP address to access this app:
kubectl -n k8s-vault-dynamic-credentials get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
app-lb LoadBalancer 10.100.200.113 34.77.95.55 80:30975/TCP 173m
This app allows you to create random superhero instances using endpoint /new
:
$ curl -X POST 34.77.95.55/new -w '\n'
{"name":"Ultra Magneto","power":"Heat Resistance","created":"2019-08-20T14:44:36.329+0000"}
A new entry is added to SQL table superhero
.
Check that this superhero is properly stored in the database:
$ curl -s 34.77.95.55 | jq -r
[
{
"name": "Ultra Magneto",
"power": "Heat Resistance",
"created": "2019-08-20T14:44:36.329+0000"
},
{
"name": "Red Quantum",
"power": "Pyrokinesis",
"created": "2019-08-20T14:44:34.393+0000"
},
{
"name": "Bolt",
"power": "Animal Oriented Powers",
"created": "2019-08-20T14:44:32.509+0000"
},
{
"name": "Giant Lobo IX",
"power": "Symbiote Costume",
"created": "2019-08-20T14:44:31.633+0000"
},
{
"name": "Cable",
"power": "Energy Armor",
"created": "2019-08-20T14:44:30.554+0000"
},
{
"name": "Dash Boy",
"power": "Stealth",
"created": "2019-08-20T14:44:29.835+0000"
},
{
"name": "Red Nathan Petrelli",
"power": "Technopath/Cyberpath",
"created": "2019-08-20T14:44:29.045+0000"
},
{
"name": "General Toad Fist",
"power": "Phasing",
"created": "2019-08-20T14:44:27.898+0000"
}
]
As you scale out this app, you will notice that Vault will generate credentials for each app instance:
$ kubectl -n k8s-vault-dynamic-credentials scale --replicas 3 deployment app
deployment.extensions/app scaled
$ psql -U postgres -h localhost mydb
Password for user postgres:
psql (11.4, server 11.5)
Type "help" for help.
mydb=# SELECT rolname FROM pg_roles;
rolname
----------------------------------------------------
pg_monitor
pg_read_all_settings
pg_read_all_stats
pg_stat_scan_tables
pg_read_server_files
pg_write_server_files
pg_execute_server_program
pg_signal_backend
postgres
v-approle-mydb-rol-tifMqAud14Kl7SUGB2tF-1566314789
v-approle-mydb-rol-QJ0PAQ9vVv1WmE3rO4c2-1566336148
v-approle-mydb-rol-dfHFfRjVPD1KeIUcipFc-1566336155
(13 rows)
In this example, each PostgreSQL role is used by an app instance to connect to the database instance. Each role has its own expiration period.
Using Vault, you can manage service credentials from a single point. Your apps don't need to embed credentials, and you don't need to declare these credentials in your Kubernetes descriptors (using environment variables or files).
Use Spring Cloud Vault to leverage Vault with your Spring Boot apps: just add a few configuration properties to connect your app to Vault, and the framework will take care of retrieving credentials as needed.
Contributions are always welcome!
Feel free to open issues & send PR.
Copyright © 2019 Pivotal Software, Inc.
This project is licensed under the Apache Software License version 2.0.