Created
March 14, 2026 23:30
-
-
Save mohashari/1b3535e93040e28aba679c2c8a00ee59 to your computer and use it in GitHub Desktop.
HashiCorp Vault: Secrets Management for Production Systems
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # policies/myapp.hcl | |
| path "secret/data/myapp/*" { | |
| capabilities = ["read", "list"] | |
| } | |
| path "auth/token/renew-self" { | |
| capabilities = ["update"] | |
| } | |
| path "sys/leases/renew" { | |
| capabilities = ["update"] | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| vault policy write myapp policies/myapp.hcl |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| -- Vault needs a privileged role to create/revoke users | |
| CREATE ROLE vault_admin WITH LOGIN PASSWORD 'vault_admin_pass' CREATEROLE; | |
| -- Grant necessary permissions | |
| GRANT ALL PRIVILEGES ON DATABASE appdb TO vault_admin; | |
| GRANT ALL ON SCHEMA public TO vault_admin; | |
| -- Template Vault will use when creating dynamic users | |
| -- Vault substitutes {{name}} and {{password}} at runtime |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Enable the database secrets engine | |
| vault secrets enable database | |
| # Configure the PostgreSQL connection | |
| vault write database/config/appdb \ | |
| plugin_name=postgresql-database-plugin \ | |
| connection_url="postgresql://{{username}}:{{password}}@localhost:5432/appdb" \ | |
| allowed_roles="myapp-role" \ | |
| username="vault_admin" \ | |
| password="vault_admin_pass" | |
| # Define a role: credentials expire after 1 hour, max lease 24 hours | |
| vault write database/roles/myapp-role \ | |
| db_name=appdb \ | |
| creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \ | |
| default_ttl="1h" \ | |
| max_ttl="24h" | |
| # Test it — Vault creates a real Postgres user | |
| vault read database/creds/myapp-role |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package secrets | |
| import ( | |
| "context" | |
| "database/sql" | |
| "fmt" | |
| vault "github.com/hashicorp/vault/api" | |
| _ "github.com/lib/pq" | |
| ) | |
| type DBCredentials struct { | |
| Username string | |
| Password string | |
| } | |
| func GetDynamicDBCredentials(ctx context.Context) (*DBCredentials, error) { | |
| config := vault.DefaultConfig() | |
| // VAULT_ADDR and VAULT_TOKEN are read from environment | |
| client, err := vault.NewClient(config) | |
| if err != nil { | |
| return nil, fmt.Errorf("vault client init: %w", err) | |
| } | |
| secret, err := client.Logical().ReadWithContext(ctx, "database/creds/myapp-role") | |
| if err != nil { | |
| return nil, fmt.Errorf("reading db creds: %w", err) | |
| } | |
| if secret == nil { | |
| return nil, fmt.Errorf("no secret returned from vault") | |
| } | |
| return &DBCredentials{ | |
| Username: secret.Data["username"].(string), | |
| Password: secret.Data["password"].(string), | |
| }, nil | |
| } | |
| func NewDBPool(ctx context.Context, host string) (*sql.DB, error) { | |
| creds, err := GetDynamicDBCredentials(ctx) | |
| if err != nil { | |
| return nil, err | |
| } | |
| dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=appdb sslmode=require", | |
| host, creds.Username, creds.Password) | |
| return sql.Open("postgres", dsn) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package auth | |
| import ( | |
| "context" | |
| "fmt" | |
| vault "github.com/hashicorp/vault/api" | |
| ) | |
| func AppRoleLogin(ctx context.Context, roleID, secretID string) (*vault.Client, error) { | |
| config := vault.DefaultConfig() | |
| client, err := vault.NewClient(config) | |
| if err != nil { | |
| return nil, fmt.Errorf("vault client: %w", err) | |
| } | |
| // Exchange roleID + secretID for a token | |
| data := map[string]interface{}{ | |
| "role_id": roleID, | |
| "secret_id": secretID, | |
| } | |
| secret, err := client.Logical().WriteWithContext(ctx, "auth/approle/login", data) | |
| if err != nil { | |
| return nil, fmt.Errorf("approle login: %w", err) | |
| } | |
| // Set the resulting token on the client | |
| client.SetToken(secret.Auth.ClientToken) | |
| // Start background token renewal | |
| renewer, err := client.NewLifetimeWatcher(&vault.LifetimeWatcherInput{ | |
| Secret: secret, | |
| }) | |
| if err != nil { | |
| return nil, fmt.Errorf("token renewer: %w", err) | |
| } | |
| go renewer.Start() | |
| return client, nil | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| FROM golang:1.23-alpine AS builder | |
| WORKDIR /app | |
| COPY go.mod go.sum ./ | |
| RUN go mod download | |
| COPY . . | |
| RUN CGO_ENABLED=0 go build -o server ./cmd/server | |
| FROM alpine:3.19 | |
| RUN apk add --no-cache ca-certificates | |
| WORKDIR /app | |
| COPY --from=builder /app/server . | |
| # VAULT_ADDR, VAULT_ROLE_ID, and VAULT_SECRET_ID are injected | |
| # by the orchestrator at container start — never hardcoded here | |
| ENV VAULT_ADDR="" | |
| EXPOSE 8080 | |
| ENTRYPOINT ["./server"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Start Vault dev server (in a separate terminal) | |
| vault server -dev -dev-root-token-id="root" | |
| # In your working terminal | |
| export VAULT_ADDR='http://127.0.0.1:8200' | |
| export VAULT_TOKEN='root' | |
| # Enable KV v2 secrets engine | |
| vault secrets enable -path=secret kv-v2 | |
| # Write your first secret | |
| vault kv put secret/myapp/database \ | |
| username="app_user" \ | |
| password="s3cur3p@ss" \ | |
| host="postgres.internal:5432" | |
| # Verify | |
| vault kv get secret/myapp/database |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment