Home/Blog/Vault AppRole Authentication: Complete CI/CD Integration Guide
Secrets Management

Vault AppRole Authentication: Complete CI/CD Integration Guide

Master HashiCorp Vault AppRole authentication for CI/CD pipelines. Step-by-step setup for Jenkins, GitHub Actions, GitLab CI, and automated workflows with security best practices.

By InventiveHQ Team
Vault AppRole Authentication: Complete CI/CD Integration Guide

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?

ChallengeAppRole Solution
Long-lived credentialsShort-lived tokens with automatic expiry
Hard to rotate secretsEasy secret_id regeneration without downtime
No audit trailClear logs showing which app authenticated
Broad permissionsFine-grained policies per application
IP restrictionsCIDR 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

ParameterDescriptionRecommended Value
token_policiesPolicies attached to generated tokensLeast privilege policies
token_ttlInitial token lifetime10-30 minutes
token_max_ttlMaximum token lifetime after renewals1-4 hours
secret_id_ttlHow long secret_id remains valid24 hours or less
secret_id_num_usesNumber of times secret_id can authenticate1-10
secret_id_bound_cidrsIP ranges that can use secret_idCI/CD network CIDR
token_bound_cidrsIP ranges for generated tokensApplication network CIDR
bind_secret_idRequire 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

  1. Install the HashiCorp Vault Plugin in Jenkins
  2. Configure Vault server in Jenkins system configuration
  3. 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

FeatureAppRoleTokenKubernetesAWS IAM
Designed forMachines/CIAnyK8s podsAWS workloads
Credential rotationEasyManualAutomaticAutomatic
IP bindingYesNoNoNo
Usage limitsYesNoNoNo
Cloud dependencyNoNoK8s clusterAWS account
Setup complexityMediumLowMediumMedium

Command Reference

CommandDescription
vault auth enable approleEnable AppRole auth method
vault write auth/approle/role/NAME ...Create/update an AppRole
vault read auth/approle/role/NAMERead AppRole configuration
vault list auth/approle/roleList all AppRoles
vault delete auth/approle/role/NAMEDelete an AppRole
vault read auth/approle/role/NAME/role-idGet role_id
vault write -f auth/approle/role/NAME/role-idRegenerate role_id
vault write -f auth/approle/role/NAME/secret-idGenerate secret_id
vault list auth/approle/role/NAME/secret-idList secret_id accessors
vault write auth/approle/login role_id=X secret_id=YAuthenticate

Next Steps

For more Vault CI/CD and automation guides, explore our complete HashiCorp Vault series.

Need Expert IT & Security Guidance?

Our team is ready to help protect and optimize your business technology infrastructure.