Skip to content

AppRole Authentication for Services

How CWIQ application containers authenticate with Vault using AppRole, and how Role ID and Secret ID credentials are provisioned and managed.

AppRole is Vault's machine-to-machine authentication method. It is designed for services and containers that need to authenticate programmatically — without a human user present and without a short-lived JWT. In CWIQ, AppRole is used exclusively by the Vault Agent sidecar that runs alongside each application container.


How AppRole Works

AppRole authentication requires two credentials:

  • Role ID — A static, non-sensitive identifier for the application. Think of it as a username. It is safe to store in configuration files.
  • Secret ID — A dynamic credential. Think of it as a password. It has a TTL, can be rotated, and should be treated as a secret.
sequenceDiagram
    participant AGENT as Vault Agent
    participant V as Vault

    AGENT->>V: POST /v1/auth/approle/login {role_id, secret_id}
    V-->>AGENT: client_token (TTL: 12h, renewable)
    AGENT->>V: GET /v1/secret/data/cwiq/dev/orchestrator/database
    V-->>AGENT: {pg_host, pg_user, pg_password, ...}
    Note over AGENT: Render template, write /vault/secrets/.env
    loop Every 45 minutes
        AGENT->>V: POST /v1/auth/token/renew-self
        V-->>AGENT: Renewed token
    end

AppRole Roles in CWIQ

Each application has its own AppRole role. The role name matches the application and environment. This ensures each application can only access its own secrets.

Application Vault Role Can Access
Orchestrator (DEV) orchestrator-dev secret/data/cwiq/dev/orchestrator/*
Authentik (Shared) authentik secret/data/cwiq/shared/authentik/*
GitLab (Shared) gitlab secret/data/cwiq/shared/gitlab/*
Nexus (Shared) nexus secret/data/cwiq/shared/nexus/*

New applications get a new role scoped exclusively to their secret paths. Cross-application secret access is not permitted.


Role Configuration

A role definition in Vault specifies its token TTL and the policies attached to it:

# Create a role for the DEV orchestrator (run on the Vault server or via CLI)
vault write auth/approle/role/orchestrator-dev \
  token_ttl=12h \
  token_max_ttl=24h \
  token_policies=orchestrator-dev \
  secret_id_ttl=0        # 0 = no expiry; rotate manually or via Ansible

The token_policies value references a Vault policy that defines which paths the token can access:

# orchestrator-dev policy
path "secret/data/cwiq/dev/orchestrator/*" {
  capabilities = ["read"]
}

Credential Provisioning

Role ID and Secret ID are provisioned by Ansible during application deployment. This is the only acceptable way to deliver these credentials to a server.

flowchart TD
    ANSIBLE[Ansible deploy playbook] -->|Reads from Vault| RID[Role ID]
    ANSIBLE -->|Generates or reads from Vault| SID[Secret ID]
    ANSIBLE -->|Writes to host| ENVFILE["/opt/cwiq/orchestrator.env\nVAULT_ROLE_ID=...\nVAULT_SECRET_ID=..."]
    COMPOSE[Docker Compose] -->|env_file:| ENVFILE
    COMPOSE -->|environment:| AGENT[Vault Agent container]

The Ansible playbook:

  1. Reads the Role ID from Vault (auth/approle/role/orchestrator-dev/role-id).
  2. Generates a new Secret ID via the Vault API (auth/approle/role/orchestrator-dev/secret-id).
  3. Writes both values to /opt/cwiq/orchestrator.env on the target server with mode: 0600, owner: cwiq.
  4. The Docker Compose file references this file via env_file:.

Never hardcode Role ID or Secret ID in Docker Compose files, Ansible variables, or source code.

Role IDs are non-sensitive (safe in configs), but Secret IDs are equivalent to passwords. Both must be delivered by Ansible to the target server at deploy time — never committed to a repository.


Secret ID Rotation

Secret IDs should be rotated when:

  • A server is decommissioned or rebuilt.
  • A compromise of the Secret ID is suspected.
  • A routine rotation policy is enforced.

To rotate a Secret ID:

# On the Vault server or via a privileged CLI session
vault write -f auth/approle/role/orchestrator-dev/secret-id

# Capture the new secret_id from the response, then re-run the Ansible deploy playbook
# to write the new value to the target server

After rotation, restart the Vault Agent container on the target server so it authenticates with the new Secret ID.


Verifying AppRole Authentication

To verify that a Role ID and Secret ID pair are valid:

# From the server where the container runs
VAULT_ROLE_ID=$(grep VAULT_ROLE_ID /opt/cwiq/orchestrator.env | cut -d= -f2)
VAULT_SECRET_ID=$(grep VAULT_SECRET_ID /opt/cwiq/orchestrator.env | cut -d= -f2)

curl -sf \
  --request POST \
  --header "Content-Type: application/json" \
  --data "{\"role_id\": \"${VAULT_ROLE_ID}\", \"secret_id\": \"${VAULT_SECRET_ID}\"}" \
  https://vault.shared.cwiq.io/v1/auth/approle/login

A successful response returns a client_token. A 400 or 403 response indicates an invalid Role ID or Secret ID.


Troubleshooting

Symptom Likely Cause Fix
Vault Agent container exits immediately Invalid Role ID or Secret ID Verify credentials with the curl test above; re-run Ansible deploy
Application container starts but has empty env vars Vault Agent rendered an empty template Check Vault Agent logs: docker logs vault-agent
Token renewal failures after hours of uptime Secret ID TTL expired Rotate the Secret ID and restart Vault Agent
HTTP 403 when reading a secret path Path not covered by the role's policy Verify the policy attached to the role covers the path
service_healthy check never passes /vault/secrets/.env not written Vault Agent may have failed auth silently; check its logs