Add Human Approval Gates to CI/CD Pipelines
Add human authorization gates to deployment pipelines
Most CI/CD pipelines use service account credentials or workflow-generated OIDC tokens to deploy. Neither proves that a specific person authorized the deployment. If a workflow runs automatically on merge, any merged PR can reach production with no human checkpoint.
Vouch’s OIDC attests human presence – “a verified human authorized this action with their YubiKey.” This enables a pattern where production deployments require an explicit YubiKey tap from an authorized deployer, with the deployer’s identity embedded in the resulting AWS credentials via STS session tags.
How it works
- A deployer authenticates with Vouch on their local machine (
vouch login). - The deployer generates a short-lived JWT (
vouch credential aws --role <ROLE_ARN>). - The JWT is passed to the CI/CD pipeline (as a workflow input, secret, or environment variable).
- The pipeline exchanges the JWT for AWS credentials using
AssumeRoleWithWebIdentity. - The deployment proceeds with credentials tied to the deployer’s hardware-verified identity.
Step 1 – Register Vouch as an IAM OIDC Provider
If you have not already, create the OIDC provider in your AWS account. See the AWS setup guide for full details.
Step 2 – Create a Deployment Role
Create an IAM role that trusts Vouch tokens and restricts access to authorized deployers:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/us.vouch.sh"
},
"Action": ["sts:AssumeRoleWithWebIdentity", "sts:TagSession"],
"Condition": {
"StringEquals": {
"us.vouch.sh:aud": "us.vouch.sh",
"us.vouch.sh:sub": [
"deployer@example.com",
"release-lead@example.com"
]
}
}
}
]
}
The sub condition restricts which Vouch users can assume this role.
Step 3 – GitHub Actions Workflow
In this pattern, a deployer authenticates with Vouch on their local machine, and the resulting JWT is passed to the GitHub Actions workflow as an input, which exchanges it for AWS credentials.
name: Deploy to Production
on:
workflow_dispatch:
inputs:
vouch_token:
description: 'Vouch JWT (from: vouch credential aws --role <ROLE_ARN>)'
required: true
type: string
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Exchange Vouch JWT for AWS credentials
run: |
CREDS=$(aws sts assume-role-with-web-identity \
--role-arn arn:aws:iam::ACCOUNT_ID:role/ProductionDeployRole \
--role-session-name "deploy-${{ github.run_id }}" \
--web-identity-token "${{ inputs.vouch_token }}" \
--output json)
echo "AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r '.Credentials.AccessKeyId')" >> $GITHUB_ENV
echo "AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r '.Credentials.SecretAccessKey')" >> $GITHUB_ENV
echo "AWS_SESSION_TOKEN=$(echo $CREDS | jq -r '.Credentials.SessionToken')" >> $GITHUB_ENV
- name: Deploy
run: cdk deploy --require-approval never
Audit trail
When Vouch tokens are exchanged for STS credentials, the deployer’s email and domain are embedded as STS session tags. These appear in CloudTrail under userIdentity.sessionContext.webIdFederationData, providing a clear chain from YubiKey tap to deployment action:
{
"userIdentity": {
"type": "AssumedRole",
"principalId": "AROA...:deploy-12345",
"sessionContext": {
"webIdFederationData": {
"federatedProvider": "arn:aws:iam::ACCOUNT:oidc-provider/us.vouch.sh",
"attributes": {
"email": "deployer@example.com",
"domain": "example.com"
}
}
}
}
}
Troubleshooting
Token expired in pipeline
Vouch JWTs have a limited lifetime. The deployer must generate the token shortly before triggering the workflow. If the token expires during deployment, the deployer needs to re-authenticate and re-trigger.
Access denied on AssumeRoleWithWebIdentity
Check that the trust policy’s sub condition includes the deployer’s email and the aud matches us.vouch.sh.