Username templating for Vault dynamic credentials
Some of Vault's secrets engines generate usernames and passwords for an external system to provide short-lived, dynamic credentials to secure the target system.
Organizations may have a username convention or standard that differs from what Vault generates by default.
In the dynamic secrets tutorial, you configured Vault to generate dynamic credentials for a PostgreSQL database. In this tutorial, you will learn how to configure Vault to generate usernames that meet your organization's standards.
If you are not familar with how to configure Vault for dynamic credentials, follow the database secrets engine tutorial before you begin.
Scenario
HashiCups configured Vault to generate dynamic credentials for their PostgreSQL database. Danielle and the development team successfully retrieved the credentials from Vault.
Oliver and the operations team want to customize the generated usernames to meet HashiCups standards.
Username templating lets you customize how usernames are generated for the external systems when using Vault's secrets engines. It defines the format with static text, secrets engine metadata, system information, and randomized values.
Prerequisites
This lab was tested on macOS using an x86_64 based and Apple silicon-based processors. You may also run this tutorial by clicking the Start interactive lab button.
To perform the tasks described in this tutorial, you need to have:
- Docker to run a Vault and PostgreSQL container.
- Vault binary installed.
- Git installed.
Set up the lab
Clone the
learn-vault-dynamic-credentials
repository.$ git clone git@github.com:hashicorp-education/learn-vault-dynamic-credentials.git
Change into the
learn-vault-dynamic-credentials
directory.$ cd learn-vault-dynamic-credentials
Deploy the Vault and PostgreSQL containers.
$ terraform -chdir=vault-dynamic-creds-docker/ init && \ terraform -chdir=vault-dynamic-creds-docker/ apply -auto-approve
Example output:
Initializing the backend... Initializing provider plugins... - Finding kreuzwerker/docker versions matching "3.0.2"... - Installing kreuzwerker/docker v3.0.2... ...snip... Apply complete! Resources: 4 added, 0 changed, 0 destroyed. Outputs: POSTGRES_URL = "export TF_VAR_POSTGRES_URL=172.17.0.2:5432" POSTGRES_URL_HOST = "export 127.0.0.1:5432" VAULT_ADDR = "export VAULT_ADDR=http://127.0.0.1:8200" VAULT_TOKEN = "export VAULT_TOKEN=root"
Copy the export command from the Terraform output and export the environment variables.
Example:
$ export VAULT_ADDR=http://127.0.0.1:8200 \ VAULT_TOKEN=root \ TF_VAR_POSTGRES_URL=172.17.0.2:5432
Verify the PostgreSQL and Vault containers have started.
$ docker ps -f name=learn --format "table {{.Names}}\t{{.Status}}" NAMES STATUS learn-postgres Up 4 minutes learn-vault Up 4 minutes
Vault and PostgreSQL are running. Vault connects to PostgreSQL over the Docker bridge network.
Apply the PostgreSQL configuration used in the dynamic secrets tutorial.
$ terraform -chdir=vault-dynamic-creds-postgres/ init && \ terraform -chdir=vault-dynamic-creds-postgres/ apply -auto-approve
Example output:
Initializing the backend... Initializing provider plugins... - Finding cyrilgdn/postgresql versions matching "1.25.0"... - Installing cyrilgdn/postgresql v1.25.0... - Installed cyrilgdn/postgresql v1.25.0 (self-signed, key ID 418F268A88A6D481) ...snip... Plan: 2 to add, 0 to change, 0 to destroy. postgresql_role.ro: Creating... postgresql_grant.readonly_tables: Creating... postgresql_role.ro: Creation complete after 0s [id=ro] postgresql_grant.readonly_tables: Creation complete after 0s [id=ro_postgres_public_table] Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Apply the Vault configuration used in the dynamic secrets tutorial.
$ terraform -chdir=vault-dynamic-creds-vault/ init && \ terraform -chdir=vault-dynamic-creds-vault/ apply -auto-approve
Example output:
Initializing the backend... Initializing provider plugins... - Finding hashicorp/vault versions matching "4.5.0"... ...snip... vault_database_secrets_mount.database: Creating... vault_database_secrets_mount.database: Creation complete after 0s [id=database] vault_database_secret_backend_role.readonly: Creating... vault_database_secret_backend_role.readonly: Creation complete after 0s [id=database/roles/readonly] Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Vault and PostgreSQL are running and configured. You are ready to proceed with the tutorial.
Request credentials with default username
The applications that require the database credentials read them from the secret engine's readonly role. The database secrets engine generates usernames that adhere to a default pattern.
Read credentials from the
readonly
database role.$ vault read database/creds/readonly Key Value --- ----- lease_id database/creds/readonly/ZxoKlbklsliYA4hZs7umoPIz lease_duration 1h lease_renewable true password 9MSegMz7N1Fr69ZTyb#D username v-token-readonly-wGLPkpDyc6AgqBfMZTD3-1604195404
The generated username uses the default pattern expressed as a Go template:
{{printf "v-%s-%s-%s-%s" (.DisplayName | truncate 8) (.RoleName | truncate 8) (random 20) (unix_time) | truncate 63 }}
The printf "v-%s-%s-%s-%s"
function accepts text, or string, as a parameter.
This string may contain text (v-
and -
) and variables (%s
). These
variables represent functions or values that return a string. These functions or
values immediately follow this string.
The (.DisplayName | truncate 8)
renders the .DisplayName
attribute of the
authenticated token. The result is then piped to the truncate
function and
the string is truncated, or shortened to 8
characters.
The (.RoleName | truncate 8)
renders the name of the requested database
secrets engine role, .RoleName
, truncated again to 8 characters.
The (random 20)
renders a randomized sequence of 20
lowercase letters,
uppercase letters, and numbers.
The unix_time
renders the current unix timestamp (number of seconds since Jan
1, 1970).
The resulting string that printf "v-%s-%s-%s-%s"
produces is piped to the
truncate
function and truncated, or shortened to 63
characters.
Refer to the Username templating documentation to learn more functions that can be applied.
Configure a custom username template
Customized username templates allow you to meet the needs of your organization.
Note
To prevent the same username from being generated multiple times in custom
username templates, include enough randomness in the template. The random
function generates a random sequence of characters. The unix_time
function
generates a timestamp in seconds.
HashiCups wants the generated username to match the following pattern, expressed as a Go template:
myorg-{{.RoleName}}-{{unix_time}}-{{random 8}}
This username template is prefixed with myorg-
, uses the name of role,
readonly
, the unix timestamp in seconds, and a random sequence of 8
characters. These functions and values are displayed inline and escaped with the
{{ }}
sequence.
Configure the database secrets engine with a new username template.
$ vault write database/config/postgres \ username_template="myorg-{{.RoleName}}-{{unix_time}}-{{random 8}}"
Read credentials from the
readonly
database role.$ vault read database/creds/readonly Key Value --- ----- lease_id database/creds/readonly/NOCGtSbz7g4FFjcztX6Bqh3S lease_duration 1h lease_renewable true password -h3B-JteYjgOPYIC6dGQ username myorg-readonly-1616447348-af9eHMWD
Vault generates a username that matches the custom template.
Clean up
Destroy the Terraform resources.
$ terraform -chdir=vault-dynamic-creds-vault/ destroy -auto-approve && \ terraform -chdir=vault-dynamic-creds-postgres/ destroy -auto-approve && \ terraform -chdir=vault-dynamic-creds-docker/ destroy -auto-approve
Unset the environment variables.
$ unset VAULT_ADDR VAULT_TOKEN TF_VAR_POSTGRES_URL
Summary
You defined a customized username template for the database secrets engine. Vault now generates usernames that meet your organization's standards.