»Database Root Credential Rotation
»Database Secrets Engine
Vault's database secrets engine provides a
centralized workflow for managing credentials for various database systems. By
leveraging this, every service instance gets a unique set of database
credentials instead of sharing one. Having those credentials tied directly to
each service instance and live only for the life of the service, any abnormal
access pattern can be mapped to a specific service instance and its credential
can be revoked immediately.
This reduces the manual tasks performed by the database administrator and make
the access to the database to be more efficient and secure.
The Secret as a Service: Dynamic Secrets
guide demonstrates the primary workflow.
»Reference Material
»Estimated Time to Complete
10 minutes
»Challenge
Because Vault is managing the database credentials on behalf of the database
administrator, it must also be given a set of highly privileged credentials
which can grant and revoke access to the database system. Therefore, it is very
common to give Vault the root credentials.
However, these credentials are often long-lived and never change once configured
on Vault. This may violate the Governance, Risk and Compliance (GRC)
surrounding that data stored in the database.
»Solution
Use the Vault's /database/rotate-root/:name
API endpoint to rotate the
root credentials stored for the database connection.

Best Practice: Use this feature to rotate the root credentials
immediately after the initial configuration of each database.
»Prerequisites
To perform the tasks described in this guide, you need to have a Vault
environment. Refer to the Getting
Started guide to install Vault. Make sure
that your Vault server has been initialized and
unsealed.
»PostgreSQL
This guide requires that you have a PostgreSQL server to connect to. If you
don't have one, install PostgreSQL.
»Policy requirements
NOTE: For the purpose of this guide, you can use root
token to work
with Vault. However, it is recommended that root tokens are only used for just
enough initial setup or in emergencies. As a best practice, use tokens with
appropriate set of policies based on your role in the organization.
To perform all tasks demonstrated in this guide, your policy must include the
following permissions:
path "sys/mounts/*" {
capabilities = [ "create", "read", "update", "delete", "list" ]
}
path "database/*" {
capabilities = [ "create", "read", "update", "delete", "list" ]
}
path "sys/mounts/*" { capabilities = [ "create", "read", "update", "delete", "list" ]}
path "database/*" { capabilities = [ "create", "read", "update", "delete", "list" ]}
If you are not familiar with policies, complete the
policies guide.
»Steps
Using Vault, you can easily rotate the root credentials for your database
through the database/rotate-root/:name
endpoint.
This guide demonstrates the overall workflow to manage the database credentials
including the root.
You are going to perform the following:
- Enable the database secret engine
- Configure PostgreSQL secret engine
- Verify the configuration (Optional)
- Rotate the root credentials
»Step 1: Enable the database secret engine
»CLI command
Enable a database secret engine:
$ vault secrets enable database
$ vault secrets enable database
NOTE: This example enables the database secret engine at the /database
path in Vault.
»API call using cURL
To enable a database secret engine, use the /sys/mounts
endpoint.
$ curl --header "X-Vault-Token: ..." \
--request POST \
--data '{"type":"database"}' \
https://127.0.0.1:8200/v1/sys/mounts/database
$ curl --header "X-Vault-Token: ..." \ --request POST \ --data '{"type":"database"}' \ https://127.0.0.1:8200/v1/sys/mounts/database
NOTE: This example mounts database secret engine at /database
,
and passes the secret engine type ("database
") in the request payload.
»Step 2: Configure PostgreSQL secret engine
In the Secret as a Service: Dynamic
Secrets guide, the PostgreSQL
plugin was configured with its root credentials embedded in the connection_url
(root
and rootpassword
) as below:
$ vault write database/config/postgresql \
plugin_name=postgresql-database-plugin \
allowed_roles="*" \
connection_url=postgresql://root:rootpassword@postgres.host.address:5432/postgres
$ vault write database/config/postgresql \ plugin_name=postgresql-database-plugin \ allowed_roles="*" \ connection_url=postgresql://root:rootpassword@postgres.host.address:5432/postgres
The username and password can be templated using the format,
{{<field-name>}}
.
In order to leverage the database root credential rotation feature, you must
use the templated credentials: {{username}}
and {{password}}
.
»CLI command
$ vault write database/config/postgresql \
plugin_name=postgresql-database-plugin \
connection_url="postgresql://{{username}}:{{password}}@postgres.host.address:5432/postgres" \
allowed_roles="*" \
username="root" \
password="rootpassword"
$ vault write database/config/postgresql \ plugin_name=postgresql-database-plugin \ connection_url="postgresql://{{username}}:{{password}}@postgres.host.address:5432/postgres" \ allowed_roles="*" \ username="root" \ password="rootpassword"
Notice that the connection_url
value contains the templated credentials, and
username
and password
parameters are also passed to initiate the connection.
Create a role, readonly
:
$ tee readonly.sql <<EOF
CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO "{{name}}";
EOF
$ vault write database/roles/readonly db_name=postgresql \
creation_statements=@readonly.sql \
default_ttl=1h max_ttl=24h
$ tee readonly.sql <<EOFCREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';GRANT SELECT ON ALL TABLES IN SCHEMA public TO "{{name}}";EOF
$ vault write database/roles/readonly db_name=postgresql \ creation_statements=@readonly.sql \ default_ttl=1h max_ttl=24h
»API call using cURL
$ tee payload.json <<EOF
{
"plugin_name": "postgresql-database-plugin",
"connection_url": "postgresql://{{username}}:{{password}}@postgres.host.address:5432/postgres",
"allowed_roles": "readonly",
"username": "root",
"password": "rootpassword"
}
EOF
$ curl --header "X-Vault-Token: ..."
--request POST \
--data @payload.json \
http://127.0.0.1:8200/v1/database/config/postgresql
$ tee payload.json <<EOF{ "plugin_name": "postgresql-database-plugin", "connection_url": "postgresql://{{username}}:{{password}}@postgres.host.address:5432/postgres", "allowed_roles": "readonly", "username": "root", "password": "rootpassword"}EOF
$ curl --header "X-Vault-Token: ..." --request POST \ --data @payload.json \ http://127.0.0.1:8200/v1/database/config/postgresql
Notice that the connection_url
value contains the templated credentials, and
username
and password
parameters are also passed to initiate the connection.
Create a role, readonly
:
$ tee payload.json <<EOF
{
"db_name": "postgres",
"creation_statements": "CREATE ROLE '{{name}}' WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO '{{name}}';",
"default_ttl": "1h",
"max_ttl": "24h"
}
EOF
$ curl --header "X-Vault-Token: ..."
--request POST \
--data @payload.json \
http://127.0.0.1:8200/v1/database/roles/readonly
$ tee payload.json <<EOF{ "db_name": "postgres", "creation_statements": "CREATE ROLE '{{name}}' WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO '{{name}}';", "default_ttl": "1h", "max_ttl": "24h"}EOF
$ curl --header "X-Vault-Token: ..." --request POST \ --data @payload.json \ http://127.0.0.1:8200/v1/database/roles/readonly
»Step 3: Verify the configuration (Optional) ((#step3))
Before rotate the root credentials, make sure that the secret engine was
configured correctly.
»CLI command
$ vault read database/creds/readonly
Key Value
--- -----
lease_id database/creds/readonly/999c43f0-f79e-ba90-24a8-4de5af33a2e9
lease_duration 1h
lease_renewable true
password A1a-u7wxtrpx09xp40yq
username v-root-readonly-x6q809467q98yp4yx4z4-1525378026e
$ psql -h postgres.host.address -p 5432 \
-U v-root-readonly-x6q809467q98yp4yx4z4-1525378026e postgres
Password for user v-root-readonly-x6q809467q98yp4yx4z4-1525378026:
postgres=> \du
Role name | Attributes | Member of
------------------------------------------------+------------------------------------------------------------+----------
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
v-root-readonly-x6q809467q98yp4yx4z4-1525378026 | Password valid until 2018-05-03 21:07:11+00 | {}
postgres=> \q
$ vault read database/creds/readonlyKey Value--- -----lease_id database/creds/readonly/999c43f0-f79e-ba90-24a8-4de5af33a2e9lease_duration 1hlease_renewable truepassword A1a-u7wxtrpx09xp40yqusername v-root-readonly-x6q809467q98yp4yx4z4-1525378026e
$ psql -h postgres.host.address -p 5432 \ -U v-root-readonly-x6q809467q98yp4yx4z4-1525378026e postgresPassword for user v-root-readonly-x6q809467q98yp4yx4z4-1525378026:
postgres=> \duRole name | Attributes | Member of------------------------------------------------+------------------------------------------------------------+----------postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {}v-root-readonly-x6q809467q98yp4yx4z4-1525378026 | Password valid until 2018-05-03 21:07:11+00 | {}
postgres=> \q
»API call using cURL
$ curl --header "X-Vault-Token: 1c97b03a-6098-31cf-9d8b-b404e52dcb4a" \
http://127.0.0.1:8200/v1/database/creds/readonly | jq
{
"request_id": "527970fd-f5e8-4de5-d4ed-1b7970eaef0b",
"lease_id": "database/creds/readonly/ac79265e-668c-242f-4f67-1dae33da094c",
"renewable": true,
"lease_duration": 3600,
"data": {
"password": "A1a-0tr8u15y0us2u08v",
"username": "v-root-readonly-x7v65y1xuprzxv9vpt80-1525378873"
},
"wrap_info": null,
"warnings": null,
"auth": null
}
$ psql -h postgres.host.address -p 5432 \
-U v-root-readonly-x6q809467q98yp4yx4z4-1525378026e postgres
Password for user v-root-readonly-x6q809467q98yp4yx4z4-1525378026:
postgres=> \du
Role name | Attributes | Member of
------------------------------------------------+------------------------------------------------------------+----------
postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {}
v-root-readonly-x6q809467q98yp4yx4z4-1525378026 | Password valid until 2018-05-03 21:07:11+00 | {}
v-root-readonly-x7v65y1xuprzxv9vpt80-1525378873 | Password valid until 2018-05-03 21:21:18+00 | {}
postgres=> \q
$ curl --header "X-Vault-Token: 1c97b03a-6098-31cf-9d8b-b404e52dcb4a" \ http://127.0.0.1:8200/v1/database/creds/readonly | jq{ "request_id": "527970fd-f5e8-4de5-d4ed-1b7970eaef0b", "lease_id": "database/creds/readonly/ac79265e-668c-242f-4f67-1dae33da094c", "renewable": true, "lease_duration": 3600, "data": { "password": "A1a-0tr8u15y0us2u08v", "username": "v-root-readonly-x7v65y1xuprzxv9vpt80-1525378873" }, "wrap_info": null, "warnings": null, "auth": null}
$ psql -h postgres.host.address -p 5432 \ -U v-root-readonly-x6q809467q98yp4yx4z4-1525378026e postgresPassword for user v-root-readonly-x6q809467q98yp4yx4z4-1525378026:
postgres=> \duRole name | Attributes | Member of------------------------------------------------+------------------------------------------------------------+----------postgres | Superuser, Create role, Create DB, Replication, Bypass RLS | {}v-root-readonly-x6q809467q98yp4yx4z4-1525378026 | Password valid until 2018-05-03 21:07:11+00 | {}v-root-readonly-x7v65y1xuprzxv9vpt80-1525378873 | Password valid until 2018-05-03 21:21:18+00 | {}
postgres=> \q
This confirms that the Vault successfully connected to your PostgreSQL server
and created a new user based on the privilege defined by readonly.sql
.
The user credentials generated by the Vault has a limited TTL based on your
configuration (default_ttl
). In addition, you can revoke them if necessary.
»Step 4: Rotate the root credentials
Vault provides an API endpoint to easily rotate the root database credentials.
»CLI command
$ vault write -force database/rotate-root/postgresql
$ vault write -force database/rotate-root/postgresql
»API call using cURL
$ curl --header "X-Vault-Token: ..." \
--request POST \
http://127.0.0.1:8200/v1/database/rotate-root/postgresql
$ curl --header "X-Vault-Token: ..." \ --request POST \ http://127.0.0.1:8200/v1/database/rotate-root/postgresql
This is all you need to do.
Repeat Step 3 to verify that Vault continues to generate database
credentials after the root credential rotation.
To verify that the root credential was rotated:
$ psql -h postgres.host.address -p 5432 -U root postgres
Password for user root:
$ psql -h postgres.host.address -p 5432 -U root postgresPassword for user root:
Entering the initial password (e.g. rootpassword
) will not work since
the password was rotated by the Vault.
You can invoke the database/rotate-root/:name
endpoint periodically to
secure the root credential.
NOTE: Once the root credential was rotated, only the Vault knows the new
root password. This is the same for all root database credentials given to Vault.
Therefore, you should create a separate superuser dedicated to the Vault usage
which is not used for other purposes.
»Next steps
In this guide, you learned how to rotate the root database credentials.
Read the AppRole Pull Authentication
guide to learn about generating a client token for your app so that it can
request database credentials from Vault.