# Add Human Approval Gates to CI/CD Pipelines

> Require a YubiKey tap before production deployments — hardware-verified identity embedded in every CI/CD credential.

Source: https://vouch.sh/docs/cicd/
Last updated: 2026-04-10

---


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.

> **Fully automated pipelines:** If your pipeline does not need a human approval gate and should run unattended, use the [client credentials grant](/docs/applications/#client-credentials-machine-to-machine) instead. This lets CI/CD systems authenticate with a client ID and secret without requiring a YubiKey tap.

## How it works

1. A deployer authenticates with Vouch on their local machine (`vouch login`).
2. The deployer generates a short-lived JWT (`vouch credential aws --role <ROLE_ARN>`).
3. The JWT is passed to the CI/CD pipeline (as a workflow input, secret, or environment variable).
4. The pipeline exchanges the JWT for AWS credentials using `AssumeRoleWithWebIdentity`.
5. 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](/docs/aws/) for full details.

---

## Step 2 -- Create a Deployment Role

Create an IAM role that trusts Vouch tokens and restricts access to authorized deployers:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/us.vouch.sh"
      },
      "Action": [
        "sts:AssumeRoleWithWebIdentity",
        "sts:SetSourceIdentity",
        "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.

```yaml
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:

```json
{
  "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.
