Vault
Database secrets engine for Microsoft SQL Server
Vault can generate secrets on-demand for some systems. For example, when an app needs to access an database server, it asks Vault for credentials. Vault will generate credential granting permissions to access the database server. In addition, Vault will automatically revoke this credential after the time-to-live (TTL) expires.
Challenge
Data protection is a top priority, and database credential rotation is a critical part of any data protection initiative. Each role has a different set of permissions granted to access the database. Automated and continuous credential rotation becomes a necessary element of incident response.
Solution
Applications ask Vault for database credentials rather than setting them as environment variables. The administrator specifies the TTL of the database credentials to enforce its validity so that they are automatically revoked when they are no longer used.
Each app instance can get unique credentials that they don't have to share. By making those credentials short-lived, you reduce the chance that they become compromised. If an attacker compromises an app, you can revoke the app credentials rather than changing more global sets of credentials.
Personas
The end-to-end scenario described in this tutorial involves two personas:
admin
with privileged permissions to configure secrets engines
apps
read the secrets from Vault
Prerequisites
This lab was tested on macOS using an x86_64 based processor. If you are running macOS on an Apple silicon-based processor, use a x86_64 based Linux virtual machine in your preferred cloud provider.
To perform the tasks described in this tutorial, you need to have:
- HCP or Vault Community Edition environment
- jq installed
- Docker installed
- ngrok installed and configured with an auth token (HCP Vault Dedicated only)
Scenario Introduction
In this tutorial, you are going to configure the database secrets engine to support
Microsoft SQL Server, and add users to the myapp
database. The Vault-generated MSSQL
credentials in this tutorial use the built in db_datareader
role to have read-only access to the database.
Lab setup
Start MSSQL
Start MSSQL using Docker.
$ docker run -e "ACCEPT_EULA=Y" \ -e "MSSQL_SA_PASSWORD=P(@)ssword11" \ -p 1433:1433 \ -d mcr.microsoft.com/mssql/server:2022-latest
Export an environment variable for the MSSQL SA user.
$ export SQL_USERNAME=sa
Export an environment variable for the password created for the SA user.
$ export SQL_PASSWORD=P(@)ssword11
Note
Use environment variables to streamline Vault configuration during the tutorial.
Write some example SQL that creates a database called
myapp
and inserts sample data.$ tee configure.sql <<EOF USE [master]; GO CREATE DATABASE myapp; GO USE [myapp]; GO CREATE TABLE location (street varchar(20), city varchar(20), state varchar(20)); GO INSERT INTO location (street, city, state) VALUES ('main', 'anytown', 'california'); EOF
Use
sqlcmd
to run the script to create the database and insert sample data.$ sqlcmd -U $SQL_USERNAME -P $SQL_PASSWORD -S 127.0.0.1 -i configure.sql
Example output:
Changed database context to 'master'. Changed database context to 'myapp'. (1 rows affected)
Verify you can read the data from the
location
table.$ sqlcmd -U $SQL_USERNAME -P $SQL_PASSWORD -S 127.0.0.1 -Q "USE [myapp]; SELECT street FROM location;" street -------------------- main
Start Vault
Note
If you do not have access to an HCP Vault Dedicated cluster, visit the Create a Vault Cluster on HCP tutorial.
Launch the HCP Portal and login.
Click Vault in the left navigation pane.
In the Vault clusters pane, click vault-cluster.
Under Cluster URLs, click Public Cluster URL.
In a terminal, set the
VAULT_ADDR
environment variable to the copied address.$ export VAULT_ADDR=<Public_Cluster_URL>
Return to the Overview page and click Generate token.
Vault generates a new token within moments.
Copy the Admin Token.
Return to the terminal and set the
VAULT_TOKEN
environment variable.$ export VAULT_TOKEN=<token>
Set the
VAULT_NAMESPACE
environment variable toadmin
.$ export VAULT_NAMESPACE=admin
The
admin
namespace is the top-level namespace automatically created by HCP Vault. All CLI operations default to use the namespace defined in this environment variable.For Vault Dedicated to interact with resources running on your local machine, you must first establish a tunnel.
In another terminal, start ngrok and connect to MSSQL.
$ ngrok tcp 127.0.0.1:1433
Example output:
ngrok (Ctrl+C to quit) Session Status online Account username (Plan: Free) Update update available (version 3.0.5, Ctrl-U to update) Version 3.0.3 Region United States (us) Latency 32.791235ms Web Interface http://127.0.0.1:4040 Forwarding tcp://d12b-34-567-89-10.ngrok.io:12345 -> 127.0.0.1:5432 Connections ttl opn rt1 rt5 p50 p90 0 0 0.00 0.00 0.00 0.00
Copy the ngrok forwarding address.
Return to the terminal where you set the
VAULT_ADDR
environment variable and set an environment variable for the ngrok address. Do not includetcp://
.$ export SQL_ADDR=<actual-address-from-ngrok>
The HCP Vault server is ready, you may proceed with the lab.
Configure the database secrets engine
(Persona: admin)
The database secrets engine generates database credentials dynamically based on configured roles.
Enable the database secrets engine at the
database/
path.$ vault secrets enable database
You've enabled the database secrets engine.
Configure the database secrets engine with the connection credentials for the MSSQL database.
$ vault write database/config/mssql \ plugin_name=mssql-database-plugin \ connection_url=sqlserver://{{username}}:{{password}}@$SQL_ADDR \ allowed_roles="readonly" \ username=$SQL_USERNAME \ password=$SQL_PASSWORD
You've configured the secrets engine to work with MSSQL.
Note
Read the Database Root Credential Rotation tutorial to learn about rotating the root credential after the initial configuration of each database.
Define the SQL used to create credentials for the
myapp
database.$ tee readonly.sql <<EOF USE [myapp]; CREATE LOGIN [{{name}}] WITH PASSWORD = '{{password}}'; CREATE USER [{{name}}] FOR LOGIN [{{name}}]; EXEC sp_addrolemember db_datareader, [{{name}}]; EOF
The SQL has templatized fields
{{name}}
, and{{password}}
. Vault fills in these values when you read the credentials.Note
Important: when you define the role in a production deployment, you must create user creation_statements, revocation_statements, and rotation_statements, which are valid for the database you've configured. If you do not specify statements appropriate to creating, revoking, or rotating users, Vault inserts generic statements which can be unsuitable for your deployment.
Also note that Amazon RDS does not support using the
sysadmin
role, which Vault uses by default for MSSQL revocation process. If you are using MSSQL on AWS RDS, you must use a custom revocation statement.Refer to the Database Secrets Engine for Microsoft SQL documentation for more information.
Create the role named
readonly
that creates credentials with thereadonly.sql
script.$ vault write database/roles/readonly \ db_name=mssql \ creation_statements=@readonly.sql \ default_ttl=1h \ max_ttl=24h
Vault is now configured to support generating dynamic credentials for Microsoft SQL Server.
Test MSSQL access
(Persona: apps)
Mimic the steps typically performed by an application to generate dynamic credentials
and read data from the myapp
database.
Read credentials from the
readonly
database role.$ vault read database/creds/readonly Key Value --- ----- lease_id database/creds/readonly/y2X3pgeWA1CZ3MATsZBqKmrX.lVGQF lease_duration 1h lease_renewable true password K5zezkH-GcQZSukOmKUp username v-token-hcp-root-readonly-X0aL2ZMSKNCQ3nMFaSgP-1668538290
Vault generates a unique username and password to access MSSQL.
Read credentials from the
readonly
database role and save them to an environment variable.$ TEMP_CREDS=$(vault read database/creds/readonly -format=json | jq -r .data)
Connect to the MSSQL database and list the sample data using the credentials stored in the
TEMP_CREDS
environment variable.$ sqlcmd -U $(echo $TEMP_CREDS | jq -r .username) \ -P $(echo $TEMP_CREDS | jq -r .password) \ -S 127.0.0.1 -Q "USE [myapp]; SELECT street FROM location;"
The output displays the sample data from the
myapp
database.street -------------------- main
Attempt to insert data into the table.
$ sqlcmd -U $(echo $TEMP_CREDS | jq -r .username) \ -P $(echo $TEMP_CREDS | jq -r .password) -S 127.0.0.1 \ -Q "USE [myapp]; INSERT INTO location (street, city, state) VALUES ('second', 'anytown', 'california');"
The insert permission was denied because the temporary credentials were only assigned a role with read permissions.
Changed database context to 'myapp'. Msg 229, Level 14, State 5, Server EC2AMAZ-S3ASM3ST, Line 1 The INSERT permission was denied on the object 'location', database 'myapp', schema 'dbo'.
Manage leases
(Persona: admin)
The credentials are managed by the lease ID and remain valid for the lease duration (TTL) or until revoked. Once revoked the credentials are no longer valid.
List the existing leases.
$ vault list sys/leases/lookup/database/creds/readonly Keys ---- 0XRYUO9ivzBz44lOiYrtmrBt.OywaL PQ32SURhydsSqu286tvsOpli.OywaL
All valid leases for database credentials are displayed.
Create a variable that stores the first lease ID.
$ LEASE_ID=$(vault list -format=json sys/leases/lookup/database/creds/readonly | jq -r ".[0]")
Renew the lease for the database credential by passing its lease ID.
$ vault lease renew database/creds/readonly/$LEASE_ID Key Value --- ----- lease_id database/creds/readonly/IQKUMCTg3M5QTRZ0abmLKjTX lease_duration 1h lease_renewable true
The TTL of the renewed lease is set to
1h
.Revoke the lease without waiting for its expiration.
$ vault lease revoke database/creds/readonly/$LEASE_ID All revocation operations queued successfully!
List the remaining leases.
$ vault list sys/leases/lookup/database/creds/readonly Keys ---- PQ32SURhydsSqu286tvsOpli.OywaL
The lease is no longer valid and is not displayed.
Revoke all remaining leases associated with the path
database/creds/readonly
.$ vault lease revoke -prefix database/creds/readonly All revocation operations queued successfully!
The
prefix
flag matches all valid leases with the path prefix ofdatabase/creds/readonly
.List the existing leases.
$ vault list sys/leases/lookup/database/creds/readonly No value found at sys/leases/lookup/database/creds/readonly/
All the leases with this path as a prefix have been revoked.
Implement a password policy
(Persona: admin)
Note
Refer to the password policy tutorial for more details.
Define a password policy
The database secrets engines generate passwords that adhere to a default pattern that may be overridden with a new password policy. A policy defines the rules and requirements that the password must adhere to and can provide that password directly through a new endpoint or within secrets engines.
The passwords you want to generate adhere to these requirements.
- length of 20 characters
- at least 1 uppercase character
- at least 1 lowercase character
- at least 1 number
- at least 1 symbol
Define a password policy in a file named
password-policy.hcl
.$ tee password-policy.hcl <<EOF length=20 rule "charset" { charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" min-chars = 1 } rule "charset" { charset = "abcdefghijklmnopqrstuvwxyz" min-chars = 1 } rule "charset" { charset = "0123456789" min-chars = 1 } rule "charset" { charset = "!@#$%^&*" min-chars = 1 } EOF
The policy is written in HashiCorp Configuration Language (HCL).
Each rule stanza defines a character set and the minimum number of occurrences those characters need to appear in the generated password. These rules are cumulative so each one adds more requirements on the password generated.
Line 2: The
length
field sets the length of the password returned to20
characters.Lines 4-7: The
charset
rule includes uppercase characters with a minimum of 1 character.Lines 9-12: The
charset
rule includes lowercase characters with a minimum of 1 character.Lines 14-17: The
charset
rule includes numbers with a minimum of 1 character.Lines 19-22: The
charset
rule includes special characters with a minimum of 1 character.Create a Vault password policy named
mssql
with the password policy rules defined inpassword-policy.hcl
.$ vault write sys/policies/password/mssql policy=@password-policy.hcl Success! Data written to sys/polices/password/password-policy
This policy can now be accessed directly to generate a password or referenced by its name
mssql
when configuring supported secrets engines.Generate a password from the
mssql
password policy.$ vault read sys/policies/password/mssql/generate Key Value --- ----- password #v!RQDHxHunJ1TUmCyys
The password generated adheres to the defined requirements.
Add a password policy to the database configuration
Now that the password policy has been created, you can assign the policy to the database secrets engine configuration.
Configure the database secrets engine with the
mssql
password policy.$ vault write database/config/mssql \ password_policy="mssql"
Read credentials from the
readonly
database role with themssql
policy attached.$ vault read database/creds/readonly
The credentials display the
username
andpassword
generated. Thepassword
generated adheres to the mssql password policy defined in the secrets engine's configuration.
Define a username template
(Persona: apps)
The database secrets engine generates usernames that adhere to a default pattern. A customized username template may be provided to meet the needs of your organization.
Note
Ensure that custom username templates include enough randomness to prevent the same username being generated multiple times.
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,
v-token-readonly-wGLPkpDyc6AgqBfMZTD3-1604195404
, 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 }}
.Refer to the Username Templating documentation to learn more functions that can be applied.
Configure the database secrets engine with the username template.
$ vault write database/config/mssql \ username_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.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
The username generated adheres to the template provided to the configuration.
Next Steps
There are some tools available to help integrate your applications with Vault's database secrets engine. Using those tools, the existing applications may require minimum to no code change to work with Vault.
Refer to the Vault Agent caching tutorial.