AppRole is HashiCorp Vault's recommended authentication method for machines, applications, and CI/CD pipelines. Unlike human-oriented auth methods, AppRole is designed for automated workflows that need to authenticate programmatically without human intervention. This guide covers everything from basic setup to production-grade CI/CD integrations.
Understanding AppRole Authentication
AppRole uses two credentials to authenticate:
┌─────────────────────────────────────────────────────────────────────┐
│ AppRole Authentication Flow │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ role_id │ + │secret_id │ ──▶ │ Token │ │
│ │ (Public) │ │(Private) │ │(Policies)│ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ Embedded in Delivered Used for all │
│ app config securely API requests │
│ │
└─────────────────────────────────────────────────────────────────────┘
role_id (The "Username")
- Static identifier for the AppRole
- Can be embedded in application code or configuration
- Does not change unless explicitly regenerated
- Safe to store in version control (not sensitive alone)
secret_id (The "Password")
- Sensitive credential that completes authentication
- Should be delivered securely and never committed to source control
- Can be configured with TTL, usage limits, and CIDR restrictions
- Should be rotated regularly
Why AppRole for CI/CD?
| Challenge | AppRole Solution |
|---|---|
| Long-lived credentials | Short-lived tokens with automatic expiry |
| Hard to rotate secrets | Easy secret_id regeneration without downtime |
| No audit trail | Clear logs showing which app authenticated |
| Broad permissions | Fine-grained policies per application |
| IP restrictions | CIDR binding for network security |
Setting Up AppRole
Step 1: Enable AppRole Auth Method
# Enable at default path
vault auth enable approle
# Or enable at custom path
vault auth enable -path=cicd approle
Step 2: Create a Policy for Your Application
Define what secrets your CI/CD pipeline can access:
# Create a CI/CD policy
vault policy write ci-deploy - <<EOF
# Read secrets for deployment
path "secret/data/production/*" {
capabilities = ["read"]
}
# Read database credentials
path "database/creds/deploy-role" {
capabilities = ["read"]
}
# No access to admin paths
path "secret/data/admin/*" {
capabilities = ["deny"]
}
EOF
Step 3: Create the AppRole
vault write auth/approle/role/jenkins-deploy \
token_policies="ci-deploy" \
token_ttl=20m \
token_max_ttl=1h \
secret_id_ttl=24h \
secret_id_num_uses=10
AppRole Configuration Options
| Parameter | Description | Recommended Value |
|---|---|---|
token_policies | Policies attached to generated tokens | Least privilege policies |
token_ttl | Initial token lifetime | 10-30 minutes |
token_max_ttl | Maximum token lifetime after renewals | 1-4 hours |
secret_id_ttl | How long secret_id remains valid | 24 hours or less |
secret_id_num_uses | Number of times secret_id can authenticate | 1-10 |
secret_id_bound_cidrs | IP ranges that can use secret_id | CI/CD network CIDR |
token_bound_cidrs | IP ranges for generated tokens | Application network CIDR |
bind_secret_id | Require secret_id (set false for IP-only auth) | true |
Step 4: Retrieve Credentials
# Get the role_id (can be done once and stored)
vault read auth/approle/role/jenkins-deploy/role-id
# Output:
# Key Value
# --- -----
# role_id db02de05-fa39-4855-059b-67221c5c2f63
# Generate a secret_id (do this securely, per-job ideally)
vault write -f auth/approle/role/jenkins-deploy/secret-id
# Output:
# Key Value
# --- -----
# secret_id 6a174c20-f6de-a53c-74d2-6018fcceff64
# secret_id_accessor c454f7e5-996e-7230-6074-6ef26b7bcf86
# secret_id_num_uses 10
# secret_id_ttl 24h
Step 5: Authenticate with AppRole
# Login and receive a token
vault write auth/approle/login \
role_id="db02de05-fa39-4855-059b-67221c5c2f63" \
secret_id="6a174c20-f6de-a53c-74d2-6018fcceff64"
# Output:
# Key Value
# --- -----
# token hvs.CAESIG...
# token_accessor aaBbCcDdEe...
# token_duration 20m
# token_renewable true
# token_policies ["ci-deploy", "default"]
CI/CD Platform Integrations
Jenkins Integration
Using HashiCorp Vault Plugin
- Install the HashiCorp Vault Plugin in Jenkins
- Configure Vault server in Jenkins system configuration
- Add AppRole credentials
Pipeline Example:
pipeline {
agent any
environment {
VAULT_ADDR = 'https://vault.example.com:8200'
}
stages {
stage('Deploy') {
steps {
withVault(
configuration: [
vaultUrl: "${VAULT_ADDR}",
vaultCredentialId: 'vault-approle'
],
vaultSecrets: [
[
path: 'secret/data/production/database',
secretValues: [
[envVar: 'DB_PASSWORD', vaultKey: 'password'],
[envVar: 'DB_USERNAME', vaultKey: 'username']
]
]
]
) {
sh '''
echo "Deploying with database credentials..."
./deploy.sh
'''
}
}
}
}
}
Using Shell Steps
pipeline {
agent any
environment {
VAULT_ADDR = 'https://vault.example.com:8200'
ROLE_ID = credentials('vault-role-id')
SECRET_ID = credentials('vault-secret-id')
}
stages {
stage('Get Secrets') {
steps {
script {
// Authenticate and get token
def response = sh(
script: '''
vault write -format=json auth/approle/login \
role_id="$ROLE_ID" \
secret_id="$SECRET_ID"
''',
returnStdout: true
)
def json = readJSON text: response
env.VAULT_TOKEN = json.auth.client_token
// Retrieve secrets
env.DB_PASSWORD = sh(
script: 'vault kv get -field=password secret/production/database',
returnStdout: true
).trim()
}
}
}
stage('Deploy') {
steps {
sh './deploy.sh'
}
}
}
post {
always {
// Revoke the token when done
sh 'vault token revoke -self || true'
}
}
}
GitHub Actions Integration
name: Deploy with Vault
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Import Secrets from Vault
uses: hashicorp/vault-action@v2
with:
url: https://vault.example.com:8200
method: approle
roleId: ${{ secrets.VAULT_ROLE_ID }}
secretId: ${{ secrets.VAULT_SECRET_ID }}
secrets: |
secret/data/production/database password | DB_PASSWORD ;
secret/data/production/database username | DB_USERNAME ;
secret/data/production/api api_key | API_KEY
- name: Deploy Application
run: |
echo "Deploying with secrets..."
./deploy.sh
env:
DB_PASSWORD: ${{ env.DB_PASSWORD }}
DB_USERNAME: ${{ env.DB_USERNAME }}
API_KEY: ${{ env.API_KEY }}
GitLab CI Integration
variables:
VAULT_ADDR: "https://vault.example.com:8200"
.vault_auth: &vault_auth
before_script:
- export VAULT_TOKEN=$(vault write -field=token auth/approle/login
role_id="$VAULT_ROLE_ID"
secret_id="$VAULT_SECRET_ID")
deploy:
stage: deploy
<<: *vault_auth
script:
- export DB_PASSWORD=$(vault kv get -field=password secret/production/database)
- export API_KEY=$(vault kv get -field=api_key secret/production/api)
- ./deploy.sh
after_script:
- vault token revoke -self || true
CircleCI Integration
version: 2.1
orbs:
vault: circleci/[email protected]
jobs:
deploy:
docker:
- image: cimg/base:stable
steps:
- checkout
- vault/get-secrets:
path: secret/data/production/database
field: password
env_var: DB_PASSWORD
- run:
name: Deploy
command: ./deploy.sh
workflows:
deploy:
jobs:
- deploy:
context: vault-context
Security Best Practices
1. Use Short-Lived secret_ids
# Create role with short-lived, single-use secret_ids
vault write auth/approle/role/secure-app \
token_policies="app-policy" \
secret_id_ttl=5m \
secret_id_num_uses=1
2. Implement CIDR Restrictions
# Restrict secret_id usage to CI/CD network
vault write auth/approle/role/jenkins-deploy \
token_policies="ci-deploy" \
secret_id_bound_cidrs="10.0.0.0/8,192.168.1.0/24" \
token_bound_cidrs="10.0.0.0/8"
3. Use Response Wrapping for secret_id Delivery
# Generate a wrapped secret_id
vault write -wrap-ttl=120s -f auth/approle/role/jenkins-deploy/secret-id
# Output:
# Key Value
# --- -----
# wrapping_token hvs.CAESI...
# wrapping_token_ttl 2m
# wrapping_accessor aaBbCc...
# Recipient unwraps to get the actual secret_id
vault unwrap hvs.CAESI...
4. Separate role_id and secret_id Delivery
Follow the "two-phase" delivery pattern:
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Configuration │ │ Vault Admin │ │ CI/CD Job │
│ Management │ │ (Trusted) │ │ │
└────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘
│ │ │
│ 1. Store role_id │ │
│ in config │ │
├────────────────────────┼───────────────────────▶│
│ │ │
│ │ 2. Deliver secret_id │
│ │ just-in-time │
│ ├───────────────────────▶│
│ │ │
│ │ 3. Login │
│ │◀────────────────────── │
│ │ │
5. Revoke Tokens After Use
# In your CI/CD script, always revoke when done
vault token revoke -self
6. Monitor and Audit
# Enable audit logging
vault audit enable file file_path=/var/log/vault/audit.log
# Query for AppRole authentications
grep "approle" /var/log/vault/audit.log | grep "login"
Advanced Configurations
Pull vs Push secret_id Modes
Pull Mode (Default): Applications retrieve their own secret_ids
# Application calls Vault to get secret_id
vault write -f auth/approle/role/my-app/secret-id
Push Mode: Orchestrator delivers secret_ids to applications
# Orchestrator generates secret_id with custom metadata
vault write auth/approle/role/my-app/custom-secret-id \
secret_id="my-custom-secret-id" \
metadata="job=deploy,env=prod"
IP-Only Authentication (No secret_id)
For trusted networks, you can disable secret_id requirement:
vault write auth/approle/role/internal-app \
token_policies="internal-policy" \
bind_secret_id=false \
token_bound_cidrs="10.0.0.0/8"
Warning: Only use this for internal, trusted networks. All requests from allowed IPs will be authenticated.
Multiple AppRoles per Application
Create different roles for different environments:
# Development role (more permissive)
vault write auth/approle/role/myapp-dev \
token_policies="dev-policy" \
token_ttl=8h \
secret_id_num_uses=0
# Production role (restricted)
vault write auth/approle/role/myapp-prod \
token_policies="prod-policy" \
token_ttl=20m \
secret_id_num_uses=1 \
secret_id_bound_cidrs="10.100.0.0/16"
Managing AppRole Credentials
Rotating secret_ids
# Generate new secret_id (old ones remain valid until expiry)
vault write -f auth/approle/role/jenkins-deploy/secret-id
# List active secret_id accessors
vault list auth/approle/role/jenkins-deploy/secret-id
# Destroy specific secret_id by accessor
vault write auth/approle/role/jenkins-deploy/secret-id-accessor/destroy \
secret_id_accessor="aaBbCcDd..."
Regenerating role_id
# Generate new role_id (invalidates old one immediately)
vault write -f auth/approle/role/jenkins-deploy/role-id
Warning: Regenerating role_id requires updating all applications using this AppRole.
Listing and Deleting AppRoles
# List all AppRoles
vault list auth/approle/role
# Read AppRole configuration
vault read auth/approle/role/jenkins-deploy
# Delete an AppRole
vault delete auth/approle/role/jenkins-deploy
Troubleshooting
Invalid role_id or secret_id
Error: invalid role or secret ID
Causes:
- role_id doesn't exist or was regenerated
- secret_id expired (check
secret_id_ttl) - secret_id usage exhausted (check
secret_id_num_uses) - secret_id already used (if
secret_id_num_uses=1)
Solution:
# Verify role exists
vault read auth/approle/role/my-role
# Generate fresh secret_id
vault write -f auth/approle/role/my-role/secret-id
CIDR Restriction Blocked
Error: source address "1.2.3.4" unauthorized
Cause: Request IP not in allowed CIDR ranges
Solution:
# Check current CIDR restrictions
vault read auth/approle/role/my-role
# Update CIDR restrictions
vault write auth/approle/role/my-role \
secret_id_bound_cidrs="1.2.3.0/24,10.0.0.0/8"
Token Has No Policies
Cause: AppRole created without token_policies
Solution:
vault write auth/approle/role/my-role \
token_policies="my-policy,another-policy"
Permission Denied After Authentication
Cause: Policy doesn't grant access to requested path
Solution:
# Check token's policies
vault token lookup
# Verify policy grants access
vault policy read my-policy
AppRole vs Other Auth Methods
| Feature | AppRole | Token | Kubernetes | AWS IAM |
|---|---|---|---|---|
| Designed for | Machines/CI | Any | K8s pods | AWS workloads |
| Credential rotation | Easy | Manual | Automatic | Automatic |
| IP binding | Yes | No | No | No |
| Usage limits | Yes | No | No | No |
| Cloud dependency | No | No | K8s cluster | AWS account |
| Setup complexity | Medium | Low | Medium | Medium |
Command Reference
| Command | Description |
|---|---|
vault auth enable approle | Enable AppRole auth method |
vault write auth/approle/role/NAME ... | Create/update an AppRole |
vault read auth/approle/role/NAME | Read AppRole configuration |
vault list auth/approle/role | List all AppRoles |
vault delete auth/approle/role/NAME | Delete an AppRole |
vault read auth/approle/role/NAME/role-id | Get role_id |
vault write -f auth/approle/role/NAME/role-id | Regenerate role_id |
vault write -f auth/approle/role/NAME/secret-id | Generate secret_id |
vault list auth/approle/role/NAME/secret-id | List secret_id accessors |
vault write auth/approle/login role_id=X secret_id=Y | Authenticate |
Next Steps
- Configure Vault Policies for fine-grained access control
- Learn about Token Management for token lifecycle
- Explore KV v2 Secrets Engine for secret versioning
For more Vault CI/CD and automation guides, explore our complete HashiCorp Vault series.


