Dynamically Generating DB Credentials via Vault
Every application has some use for various DBs right? Where are the db credentials kept though. In app config? In k8s secrets? Why not let Vault handle it?
Let’s do a quick and dirty start. To setup our DB Engine in Vault we can follow the below setups. We need
- Docker container for Postgres
- Running Vault somewhere (local is enough)
First we need to get our db secrets engine running in a path of our choosing.
Let’s choose a root path for our engine.
Now we need to configure a connection and create roles to use said connection. Click on create connection.
We can make use of the following value as connection URL;
postgresql://{{username}}:{{password}}@localhost:8888/postgres?sslmode=disable
Now you might be asking where did the username and pass below came from. We we need a db and what better way to get up and running then with a container. We made use of the official image which contained postgres14 and switched the port exposed to 8888 on local with -p 8888:5432 command. You can use whatever port you have available. Additionally I overrode the env var POSTGRES_PASSWORD with my non-default password.
docker run --name postgredb -p 8888:5432 -e POSTGRES_PASSWORD=mysecretpostgrespassword -d postgres
In the next step, Vault wants to take over the credential management and rotate current credentials we gave. Normally, you would be giving vault its own user and click Rotate and Enable but in our scenario, it is not really necessary. Though if something were to happen to our DB we could just nuke the container running it and restart which would refresh everything.
Our connection should look like this. We now need to add roles.
When we select Add Role, we need to choose some basic settings.
- Role name to define what the role is, like read-only,
- Type of role which should be dynamic. Also TTL and max TTL which will define how long the role can live. Max TTL is used when vault lease is extended, it cannot extend the value defined there. (I haven’t put a renew statement out of nothing but laziness)
- Statements which include creation, which create a role valid until a set time and grants usage and select on db elements.
CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}' INHERIT;
GRANT USAGE ON SCHEMA public TO "{{name}}";
GRANT SELECT ON ALL TABLES IN SCHEMA public TO "{{name}}";
- And revocation, which drop all grants owned by a user and the user itself in the end.
drop owned by "{{name}}";
drop user "{{name}}";
When all is set, we can move to the main secret engine page and the following view should be greeting us. When we select Get Credentials and select the role we created from below we should be receiving our dynamic db creds.
Trying it out you should be able to connect immediately with the provided creds.
If we were to simulate lease end by moving to the access tab and clicking revoke the db connection immediately drops.
You may be asking, “Great, we created creds but are we going to hand them out manually?”. Not if you don’t want to. We can ask for dynamic creds programmatically as well using Vault API via something like: http://localhost:8200/v1/postgres14/creds/read-only or by cli as below
vault read postgres14/creds/read-only
Lease revocation can be done via cli (or using the endpoint as well) as below;
vault lease revoke postgres14/creds/read-only/q2hiKZVSQgp24mBS7nImsCMw
Thanks for reading and see you next time.
Bonus:
(Not Working) Setup Via CLI
Note: Ignore the CLI version because it doesn’t work. If you prefer to see why, keep reading. Just putting it here, if I solve it I’ll update this.
We can do the same stuff via Vault cli as well, at least that’s what Vault docs say. We first need to enable the path and type of our secrets engine as before.
vault secrets enable --path=postgres14cli database
Now we create the connection that’s going to be forming the basis of our credential generation. Notice that we use the path we just created first and finally the connection name.
vault write postgres14cli/config/postgres14cli-localdb plugin_name=postgresql-database-plugin connection_url="postgresql://{{username}}:{{password}}@localhost:8888/postgres?sslmode=disable" username="postgres" password="mysecretpostgrespassword"
Finally we should be getting roles up and running with the below command. Also notice that we can type any sort of role here and Vault just accepts it. Even the wrong ones (try one with wrong db) which we cannot delete afterwards
vault write postgres14cli/roles/readonly \
db_name=postgres14cli-localdb \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}' INHERIT; \
GRANT USAGE ON SCHEMA public TO \"{{name}}\"; \
GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
revocation_statements="drop owned by \"{{name}}\"; \
drop user \"{{name}}\";" \
default_ttl="1h" \
max_ttl="24h"
And finally, we get to not being able to generate credentials due to some unknown bug with no logs on the Vault side. Creating the exact same role via UI nets us a working role.