HashiCorp Vault — AppRole
AppRole is Vault’s recommended auth method for machines and applications. It uses a two-part credential (role_id + secret_id) that provides secure, automated authentication without human intervention.
How AppRole Works
┌──────────────┐ ┌──────────────┐ ┌──────────┐
│ CI/CD │ │ Vault │ │ Secrets │
│ Pipeline │ │ │ │ │
│ │ │ │ │ │
│ ① Get │ │ │ │ │
│ role_id │────▶│ │ │ │
│ (from conf) │ │ │ │ │
│ │ │ │ │ │
│ ② Get │ │ │ │ │
│ secret_id │────▶│ │ │ │
│ (from Vault)│ │ │ │ │
│ │ │ │ │ │
│ ③ Login │ │ │ │ │
│ (role_id + │────▶│ ④ Returns │ │ │
│ secret_id) │ │ Token │────▶│ │
│ │ │ │ │ │
│ ⑤ Read │ │ │ │ │
│ secrets │────▶│──────────────│────▶│ Return │
│ │◀────│ │◀────│ secrets │
└──────────────┘ └──────────────┘ └──────────┘
The two credentials:
| Credential | Analogous To | Distribution |
|---|---|---|
role_id |
Username | Embedded in config, CI/CD variable (less sensitive) |
secret_id |
Password | Generated on demand, short-lived, single-use |
Enable AppRole
vault auth enable approle
Create a Role
# Create an AppRole for a web application
vault write auth/approle/role/webapp \
token_policies="webapp-policy" \
token_ttl=1h \
token_max_ttl=4h \
secret_id_ttl=10m \
secret_id_num_uses=1
Parameters explained:
| Parameter | Value | Meaning |
|---|---|---|
token_policies |
webapp-policy |
Policies attached to generated tokens |
token_ttl |
1h |
Token lives for 1 hour |
token_max_ttl |
4h |
Token can be renewed up to 4 hours |
secret_id_ttl |
10m |
Secret ID expires in 10 minutes |
secret_id_num_uses |
1 |
Secret ID can only be used once |
Get Credentials
# Get the role_id (static, embed in config)
vault read auth/approle/role/webapp/role-id
Expected output:
Key Value
--- -----
role_id db02de05-fa39-4855-059b-67221c5c2f63
# Generate a secret_id (dynamic, short-lived)
vault write -f auth/approle/role/webapp/secret-id
Expected output:
Key Value
--- -----
secret_id 6a174c20-f6de-a53c-74d2-6018fcceff64
secret_id_accessor c454f7e5-996e-7230-6074-6ef26b7bcf86
secret_id_num_uses 1
secret_id_ttl 10m
Login with AppRole
# Login using role_id + secret_id
vault write auth/approle/login \
role_id="db02de05-fa39-4855-059b-67221c5c2f63" \
secret_id="6a174c20-f6de-a53c-74d2-6018fcceff64"
Expected output:
Key Value
--- -----
token hvs.CAESI...
token_accessor abc123
token_duration 1h
token_renewable true
token_policies ["default", "webapp-policy"]
identity_policies []
policies ["default", "webapp-policy"]
token_meta_role_name webapp
Login via API
curl -s --request POST \
--data '{
"role_id": "db02de05-fa39-4855-059b-67221c5c2f63",
"secret_id": "6a174c20-f6de-a53c-74d2-6018fcceff64"
}' \
"$VAULT_ADDR/v1/auth/approle/login" | jq
CI/CD Integration Pattern
A typical CI/CD pattern separates role_id and secret_id delivery:
┌─────────────────────────────────────────────────┐
│ Secure Bootstrap Pattern │
│ │
│ Config Management (Ansible/Terraform) │
│ └── Delivers role_id to app config │
│ │
│ Trusted Orchestrator (Jenkins/GitHub Actions) │
│ └── Generates secret_id at deploy time │
│ └── Passes secret_id to the app │
│ │
│ Application │
│ └── Combines role_id + secret_id │
│ └── Authenticates to Vault │
│ └── Uses token to read secrets │
└─────────────────────────────────────────────────┘
GitHub Actions example:
jobs:
deploy:
steps:
- name: Get Vault Token
run: |
SECRET_ID=$(vault write -f -field=secret_id auth/approle/role/webapp/secret-id)
VAULT_TOKEN=$(vault write -field=token auth/approle/login \
role_id="$" \
secret_id="$SECRET_ID")
echo "VAULT_TOKEN=$VAULT_TOKEN" >> $GITHUB_ENV
- name: Get Database Credentials
run: |
vault kv get -field=password secret/webapp/db
Secret ID Modes
Wrapped Secret ID (Most Secure)
Response wrapping provides a single-use wrapping token instead of the secret_id directly:
# Generate a wrapped secret_id
vault write -wrap-ttl=120s -f auth/approle/role/webapp/secret-id
Expected output:
Key Value
--- -----
wrapping_token hvs.CAESI...wrapping...
wrapping_accessor abc123
wrapping_token_ttl 2m
wrapping_token_creation_time 2024-01-15T10:30:00Z
# Unwrap to get the actual secret_id
VAULT_TOKEN="hvs.CAESI...wrapping..." vault unwrap -field=secret_id
CIDR-Bound Secret ID
Restrict where a secret_id can be used from:
vault write auth/approle/role/webapp \
secret_id_bound_cidrs="10.0.0.0/8,172.16.0.0/12"
Quick Reference
| Command | Description |
|---|---|
vault auth enable approle |
Enable AppRole |
vault write auth/approle/role/NAME ... |
Create a role |
vault read auth/approle/role/NAME/role-id |
Get role_id |
vault write -f auth/approle/role/NAME/secret-id |
Generate secret_id |
vault write auth/approle/login role_id=X secret_id=Y |
Login |
vault write -wrap-ttl=120s -f auth/approle/role/NAME/secret-id |
Wrapped secret_id |
Next: OIDC & GitHub