HashiCorp Vault — Dynamic Secrets Exercises

4 minute read

Practice dynamic secrets with these hands-on exercises.


Exercise 1 — PostgreSQL Dynamic Credentials

Task: Set up dynamic PostgreSQL credentials and test the full lifecycle.

  1. Start a PostgreSQL container
  2. Enable the database secrets engine
  3. Configure the PostgreSQL connection
  4. Create a read-only role
  5. Generate credentials and connect to the database
  6. Verify you can SELECT but cannot INSERT
  7. Revoke the lease and verify access is denied
Show Answer
# 1. Start PostgreSQL
docker run -d --name pg-test \
    -e POSTGRES_USER=vault_admin \
    -e POSTGRES_PASSWORD=vault_pass \
    -e POSTGRES_DB=testdb \
    -p 5432:5432 postgres:16

# Create a test table
psql -h 127.0.0.1 -U vault_admin -d testdb -c \
    "CREATE TABLE users (id SERIAL, name TEXT); INSERT INTO users (name) VALUES ('Alice');"

# 2. Enable database engine
vault secrets enable database

# 3. Configure connection
vault write database/config/pg-test \
    plugin_name="postgresql-database-plugin" \
    allowed_roles="pg-ro" \
    connection_url="postgresql://:@127.0.0.1:5432/testdb?sslmode=disable" \
    username="vault_admin" \
    password="vault_pass"

# 4. Create read-only role
vault write database/roles/pg-ro \
    db_name="pg-test" \
    creation_statements="CREATE ROLE \"\" WITH LOGIN PASSWORD '' VALID UNTIL ''; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"\";" \
    revocation_statements="REVOKE ALL ON ALL TABLES IN SCHEMA public FROM \"\"; DROP ROLE IF EXISTS \"\";" \
    default_ttl="10m" \
    max_ttl="1h"

# 5. Generate credentials
CREDS=$(vault read -format=json database/creds/pg-ro)
USERNAME=$(echo $CREDS | jq -r .data.username)
PASSWORD=$(echo $CREDS | jq -r .data.password)
LEASE_ID=$(echo $CREDS | jq -r .lease_id)

# 6. Test access
psql -h 127.0.0.1 -U "$USERNAME" -d testdb -c "SELECT * FROM users;"
# Should succeed

psql -h 127.0.0.1 -U "$USERNAME" -d testdb -c "INSERT INTO users (name) VALUES ('Bob');"
# Should fail: permission denied

# 7. Revoke lease
vault lease revoke "$LEASE_ID"

psql -h 127.0.0.1 -U "$USERNAME" -d testdb -c "SELECT 1;"
# Should fail: role does not exist / password authentication failed

Exercise 2 — Lease Lifecycle

Task: Explore lease renewal and expiration behavior.

  1. Generate database credentials with TTL=2m, max_ttl=5m
  2. Look up the lease
  3. Wait 1 minute, then renew the lease
  4. Try to renew past the max_ttl
  5. Let the lease expire and verify credentials stop working
Show Answer
# Create a role with short TTL for testing
vault write database/roles/pg-short \
    db_name="pg-test" \
    creation_statements="CREATE ROLE \"\" WITH LOGIN PASSWORD '' VALID UNTIL ''; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"\";" \
    revocation_statements="DROP ROLE IF EXISTS \"\";" \
    default_ttl="2m" \
    max_ttl="5m"

# 1. Generate credentials
CREDS=$(vault read -format=json database/creds/pg-short)
LEASE_ID=$(echo $CREDS | jq -r .lease_id)

# 2. Lookup lease
vault lease lookup "$LEASE_ID"
# ttl: ~2m

# 3. Wait 1 minute, then renew
sleep 60
vault lease renew "$LEASE_ID"
# TTL reset to ~2m from now

# 4. After a few minutes, try to renew past max_ttl
sleep 120
vault lease renew -increment=10m "$LEASE_ID"
# Will be capped at max_ttl (5m from original issue)

# 5. Wait for expiration
# After max_ttl (5m from original issue), the credentials are revoked
# Test with psql — should fail

Exercise 3 — AWS Dynamic Credentials

Task: (Conceptual — requires AWS account) Set up dynamic AWS IAM credentials.

  1. Enable the AWS secrets engine
  2. Configure root credentials
  3. Create a role for S3 read-only access
  4. Generate credentials
  5. Use them with the AWS CLI
Show Answer
# 1. Enable AWS engine
vault secrets enable aws

# 2. Configure
vault write aws/config/root \
    access_key="YOUR_ACCESS_KEY" \
    secret_key="YOUR_SECRET_KEY" \
    region="us-east-1"

# 3. Create S3 read-only role
vault write aws/roles/s3-reader \
    credential_type="iam_user" \
    policy_document=-<<EOF
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["s3:GetObject", "s3:ListBucket"],
    "Resource": ["arn:aws:s3:::*"]
  }]
}
EOF

# 4. Generate credentials
vault read aws/creds/s3-reader
# Returns access_key, secret_key

# 5. Use with AWS CLI
export AWS_ACCESS_KEY_ID="<returned_access_key>"
export AWS_SECRET_ACCESS_KEY="<returned_secret_key>"
aws s3 ls

# 6. Cleanup
vault lease revoke -prefix aws/creds/s3-reader

Exercise 4 — Prefix Revocation

Task: Practice emergency credential revocation.

  1. Generate 5 sets of database credentials
  2. List all active leases
  3. Revoke all leases for the role using prefix revocation
  4. Verify all credentials are invalidated
Show Answer
# 1. Generate 5 credentials
for i in $(seq 1 5); do
    vault read database/creds/pg-ro
    echo "---"
done

# 2. List active leases
vault list sys/leases/lookup/database/creds/pg-ro
# Shows 5 lease IDs

# Count them
vault list -format=json sys/leases/lookup/database/creds/pg-ro | jq '. | length'
# 5

# 3. Revoke all by prefix
vault lease revoke -prefix database/creds/pg-ro

# 4. Verify
vault list sys/leases/lookup/database/creds/pg-ro
# No keys found (empty)
# All 5 database users have been dropped

Exercise 5 — Static Roles

Task: Set up a static role for password rotation (no new users, just rotating an existing user’s password).

  1. Create a PostgreSQL user manually
  2. Configure a static role for that user with 1-minute rotation
  3. Read the current credentials
  4. Wait for rotation
  5. Read again and verify the password changed
Show Answer
# 1. Create user manually
psql -h 127.0.0.1 -U vault_admin -d testdb -c \
    "CREATE ROLE app_user WITH LOGIN PASSWORD 'initial_password'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_user;"

# 2. Configure static role
vault write database/static-roles/app-user \
    db_name="pg-test" \
    username="app_user" \
    rotation_period="60" \
    rotation_statements="ALTER ROLE \"\" WITH PASSWORD '';"

# 3. Read current credentials
vault read database/static-creds/app-user
# Note the password

# 4. Wait for rotation
sleep 65

# 5. Read again
vault read database/static-creds/app-user
# Password has changed!
# The old password no longer works

What’s Next?

You’ve mastered dynamic secrets. Next, we’ll explore encryption as a service with the Transit secrets engine.

Next Chapter: Transit Secrets Engine