Storing long‑lived AWS access keys inside CI/CD pipelines is common. It
works. It is simple. And it usually stays that way for years.
But static credentials expand the trust boundary more than we often
realize. They live in repository settings, they require manual rotation,
and if exposed, they remain valid until explicitly revoked.
In this lab, I built a minimal deployment pipeline from GitHub Actions
to Amazon S3 and then deliberately refactored it. The goal was not to
deploy a website. The goal was to observe how the trust model changes
when static credentials are replaced with short‑lived role assumption.
The structure is simple: the deployment remains the same, the trust boundary changes.
Goal
Demonstrate a practical migration:
- Phase 1 --- deploy to S3 using static AWS credentials stored in GitHub Secrets
- Phase 2 --- remove static credentials and use GitHub OIDC (OpenID Connect) federation with IAM role assumption
- Keep the deployment behavior identical while improving the authentication model
The target (S3 static site) is intentionally minimal so that
authentication and identity flow stay in focus.
Constraints / Assumptions
- Single AWS account
- Manual setup (no Terraform for clarity)
- S3 static website enabled (lab shortcut)
- GitHub Actions used as CI/CD engine
Public S3 access is used only to simplify validation. In a production
setup, CloudFront and stricter bucket policies would replace it.
High-Level Design
The central question of this lab:
Can we change how CI/CD authenticates to AWS without changing how it
deploys?
Phase 1 --- Make it work
Architecture:
GitHub Actions → GitHub Secrets → IAM User → S3
- An IAM user is created.
- Access keys are generated.
- Keys are stored as repository secrets.
- The workflow uploads files using
aws s3 sync.
The system works exactly as expected.
However:
- Credentials are long‑lived.
- Rotation is manual.
- Any secret leakage provides direct AWS access.
Nothing is broken --- but trust is broad.
Phase 2 --- Reduce Trust / Harden Access
In the second phase, the IAM user is removed from the deployment path.
Architecture becomes:
GitHub Actions → OIDC Token → IAM OIDC Provider → Security Token Service (STS) → IAM Role → S3
Three architectural changes occur.
1. Introduce an IAM OIDC Provider
AWS is configured to trust tokens issued by:
https://token.actions.githubusercontent.com
This creates a formal trust anchor between GitHub and AWS.
2. Define a Scoped Trust Policy
The IAM role trust policy restricts access to:
repo:<OWNER>/<REPO>:ref:refs/heads/main
This means:
- Only this repository
- Only this branch
- Only through OIDC
- Only via STS
Trust becomes explicit and narrow.
3. Replace Static Credentials with Role Assumption
The GitHub workflow:
- Requests an ID token
- Calls
AssumeRoleWithWebIdentity - Receives short‑lived credentials
- Deploys using the same
aws s3 synccommand
The deployment logic remains unchanged.
But no static AWS keys exist in GitHub anymore.
What Actually Changed
Operationally, nothing changed:
- Same repository
- Same workflow structure
- Same deployment command
- Same S3 bucket
From a trust perspective, everything changed:
- Credentials are now short‑lived
- No manual rotation is required
- Access is scoped to one repository and branch
- AWS access depends on a validated identity token
The system now grants access based on verified identity rather than
stored secrets.
Observations from Implementation
A few practical lessons emerged during the lab:
- Incorrect S3 ARNs in role policies immediately cause
AccessDeniederrors. - Missing
id-token: writepermission prevents OIDC role assumption. - Trust policy
submismatches block access cleanly and predictably. -
aws sts get-caller-identityis the most useful debugging command in this flow.
Federated identity setups are less forgiving than static keys --- but
that strictness is the point.
Lessons Learned
- Replacing static credentials does not require changing deployment logic.
- Trust policy design is the most sensitive part of the setup.
- Short‑lived credentials reduce operational overhead and risk.
- CI/CD hardening is primarily about narrowing trust boundaries.
- Security improvements can be incremental and observable.
The lab reinforces a simple pattern: Make it work. Then deliberately reduce trust.
Where to Go Next
This setup can be extended in several directions:
- Build and push Docker images to ECR using the same OIDC role\
- Deploy to ECS instead of S3\
- Provision IAM and OIDC provider via Terraform\
- Add GitHub Environments and environment‑scoped trust conditions\
- Remove public S3 access and introduce CloudFront
Each extension builds on the same identity model.
Repository
Full reproducible runbook and workflows:
👉 https://github.com/iuri-covaliov/devops-labs/tree/main/GitHubActionswithAWSOIDC
This repository is intended to be read alongside the article: the article explains why each layer exists, while the repository shows how it is implemented.


Top comments (0)