Goal and architecture

GitHub Actions → GitHub OIDC token → AWS STS → scoped IAM role → private S3 artifact. This is a learning deployment, not a public website.

Prerequisites

Terraform 1.5.7 or newer, AWS CLI v2 with an authenticated sandbox profile, and a GitHub repository whose main branch you control. Your local AWS identity needs permission to manage IAM and S3. Initial provisioning runs locally; GitHub receives only artifact permissions.

Download and setup

Download the complete lab. Extract it and open its github-oidc folder.

  1. Check your account with aws sts get-caller-identity.
  2. Inspect IAM → Identity providers. If token.actions.githubusercontent.com exists, set existing_provider_arn to that ARN; the lab will reuse it without deleting it on cleanup.
  3. Create terraform.tfvars using the example below. Match your repository’s actual subject format: older repositories may use repo:OWNER/REPO:ref:refs/heads/main; repositories with immutable claims use repo:OWNER@OWNER_ID/REPO@REPO_ID:ref:refs/heads/main. Find numeric IDs with gh api repos/OWNER/REPO --jq '{owner_id: .owner.id, repo_id: .id}' and check your repository’s OIDC settings. Do not add a GitHub environment to this workflow; that changes its subject.
region         = "us-east-1"
github_subject = "repo:OWNER@OWNER_ID/REPO@REPO_ID:ref:refs/heads/main"
# existing_provider_arn = "arn:aws:iam::ACCOUNT:oidc-provider/token.actions.githubusercontent.com"
terraform init
terraform validate
terraform plan -out=lab.tfplan
terraform apply lab.tfplan
terraform output
  1. In GitHub repository Settings → Secrets and variables → Actions → Variables, create LAB_ROLE_ARN and LAB_BUCKET from Terraform outputs; set LAB_AWS_REGION to your chosen region.
  2. Copy deploy.yml into .github/workflows/deploy.yml in that repository and commit it to main.
  3. Run OIDC deployment lab from the Actions tab, choosing main.

Validate

The workflow prints its assumed-role identity, uploads demo/hello.txt, downloads it, and compares both files. A successful run proves read/write access to the demo prefix. The bucket remains private. Running from another branch skips deployment; other repositories cannot satisfy the exact trust condition.

Troubleshooting

For an STS access-denied response, compare the exact subject, audience, branch, role ARN, and id-token: write permission. If IAM reports that the provider exists, set existing_provider_arn. For S3 access denied, verify the bucket variable and the demo/ prefix. Allow a short time for IAM changes to propagate before retrying.

Costs and cleanup

S3 storage and requests can incur charges. The bucket is disposable: force_destroy deliberately removes all its objects during destruction.

Disable or remove the workflow first, then run:

terraform plan -destroy
terraform destroy

Confirm Terraform reports no managed resources with terraform state list. Remove the three GitHub variables. A reused identity provider remains intact. Keep your Terraform state until cleanup is complete.

References and verification

This lab has not been deployed to AWS as part of the portfolio build. Provider and static checks are separate from end-to-end verification.