CI/CD Integration¶
GitLab CI/CD pipelines authenticate with Vault using short-lived JWT tokens. No static Vault tokens or credentials are stored as GitLab CI variables.
How It Works¶
GitLab generates a signed JWT (CI_JOB_JWT_V2) for each CI job. The pipeline exchanges this JWT for a short-lived Vault token scoped to the policies defined for the GitLab JWT role.
GitLab pipeline starts
|
| CI_JOB_JWT_V2 generated by GitLab
v
POST /v1/auth/jwt/login → Vault validates JWT signature
checks bound_claims
returns short-lived token (15m)
|
| vault kv get secret/<path>
v
Pipeline reads the secrets it needs, uses them in the job
|
Token expires automatically at pipeline end
Standard Pipeline Pattern¶
# .gitlab-ci.yml
stages:
- build
- deploy
.vault-auth: &vault-auth
before_script:
- |
VAULT_TOKEN=$(curl -sf \
--request POST \
--data "{\"jwt\": \"$CI_JOB_JWT_V2\", \"role\": \"gitlab-orchestrator\"}" \
"$VAULT_ADDR/v1/auth/jwt/login" | jq -r '.auth.client_token')
export VAULT_TOKEN
deploy-dev:
stage: deploy
<<: *vault-auth
script:
- |
# Read deployment credentials from Vault
NEXUS_PASSWORD=$(vault kv get -field=password secret/nexus/svc-orchestrator)
SONAR_TOKEN=$(vault kv get -field=token secret/sonarqube/svc-orchestrator)
# Use credentials
docker login nexus.shared.cwiq.io:8443 -u svc-orchestrator -p "$NEXUS_PASSWORD"
VAULT_ADDR is a GitLab group-level CI variable
VAULT_ADDR is set at the GitLab group level (group 9) to https://vault.shared.cwiq.io. It does not need to be redefined in per-project YAML.
JWT Role Configuration¶
The gitlab-orchestrator role grants access to the secrets needed by the CWIQ platform pipelines:
vault write auth/jwt/role/gitlab-orchestrator \
role_type="jwt" \
bound_claims='{"namespace_path": ["orchestrator"]}' \
user_claim="sub" \
token_policies="nexus-read-policy,sonarqube-ci,defectdojo-ci" \
token_ttl=15m \
token_max_ttl=30m
The namespace_path bound claim restricts authentication to pipelines from GitLab projects under the orchestrator namespace only.
Listing JWT Roles¶
Adding a New Secret to CI/CD¶
When a pipeline needs a new secret:
-
Store the secret in Vault:
-
Update the policy to grant read access:
-
Update
vault-server/docs/02-cli-operations.mdto document the new secret path.
Debugging JWT Auth Failures¶
Common causes¶
| Symptom | Likely Cause |
|---|---|
invalid role name |
The role name in the pipeline doesn't match what's in Vault |
bound claims mismatch |
Pipeline runs from a namespace not in bound_claims |
permission denied reading secret |
Policy doesn't include the path |
token is expired or invalid |
JWT expired before vault login ran (slow job startup) |
Check the JWT role¶
Verify bound_claims includes the project's namespace.
Check the policy¶
Test manually¶
# Decode the JWT to inspect claims (from within a pipeline job)
echo "$CI_JOB_JWT_V2" | cut -d'.' -f2 | base64 -d 2>/dev/null | python3 -m json.tool
Security Considerations¶
- JWT tokens are valid for the duration of
token_ttl(15 minutes). Pipelines that take longer needtoken_max_ttlset appropriately. - Use
bound_claimsto restrict which GitLab projects and branches can authenticate. Never use a role without bound claims. - Pipelines receive only the secrets their policy allows. Use separate policies per concern (Nexus, SonarQube, DefectDojo).
Related Documentation¶
- Vault Architecture
- AppRole & JWT Auth
- Secret Paths Reference
- Source:
ansible-playbooks/vault-server/docs/03-integration-guide.md