EthicsComputerCourses
Join training +91 90532 10052

CI/CD on AWS: GitHub Actions with OIDC & CodePipeline

AWS · Lesson 32 of 42 Advanced 7 min read Updated

Deploying by hand works for a college project. In a team it causes "it worked on my machine" problems, forgotten steps and late-night mistakes. CI/CD fixes this: continuous integration builds and tests every change automatically, and continuous delivery or deployment releases it the same way every time. The most important security rule for CI/CD on AWS is simple: the pipeline must not hold long-lived AWS access keys.

In this lesson you will connect GitHub Actions to AWS with OpenID Connect (OIDC) and a narrowly scoped role, write a deployment workflow, and see how the AWS-native tools CodePipeline and CodeBuild fit, plus safe ways to release changes.

Why OIDC instead of access keys

The old way was to create an IAM user, generate an access key and paste it into the repository's secrets as AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. That key never expires, often has broad permissions, and leaks easily through logs, forks or a compromised workflow.

With OIDC federation, GitHub issues a short-lived signed token for each workflow run that says exactly which repository, branch and environment it comes from. AWS checks the signature and the claims, then returns temporary credentials for a role that expire on their own. There is no secret to store, rotate or leak.

Step 1: trust GitHub's identity provider

Create the IAM OIDC identity provider once per account. The URL and audience are fixed values for GitHub Actions:

Terminal
aws iam create-open-id-connect-provider \
  --url https://token.actions.githubusercontent.com \
  --client-id-list sts.amazonaws.com
Output
{
    "OpenIDConnectProviderArn": "arn:aws:iam::111122223333:oidc-provider/token.actions.githubusercontent.com"
}

Older guides also pass a --thumbprint-list. AWS now verifies GitHub's certificate with its own trusted certificate authorities, so the thumbprint is not needed for GitHub.

Step 2: a role only your repository can use

The trust policy is where security lives. It must check both the audience and the subject claim, which names the repository and branch or environment. Without the subject check, any GitHub repository in the world could assume your role.

JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::111122223333:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
          "token.actions.githubusercontent.com:sub": "repo:ananya-dev/kochi-bakery-site:environment:production"
        }
      }
    }
  ]
}

Common subject formats are repo:OWNER/REPO:ref:refs/heads/main (a branch), repo:OWNER/REPO:environment:production (a GitHub environment, which can require a reviewer's approval) and repo:OWNER/REPO:pull_request. Give pull requests a separate read-only role that can only run plan or tests, never deploy.

Attach a permissions policy with only what the deployment needs, for example s3:PutObject, s3:DeleteObject and s3:ListBucket on one bucket, and cloudfront:CreateInvalidation on one distribution.

Step 3: the workflow

This workflow deploys the static site from Project: S3 + CloudFront Site with a Lambda API whenever main changes. Save it as .github/workflows/deploy.yml:

YAML
name: deploy-site
on:
  push:
    branches: [main]

permissions:
  id-token: write   # allow the job to request the OIDC token
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v5

      - name: Sign in to AWS with OIDC
        uses: aws-actions/configure-aws-credentials@v6
        with:
          role-to-assume: arn:aws:iam::111122223333:role/github-deploy-site
          aws-region: ap-south-1

      - name: Check identity
        run: aws sts get-caller-identity

      - name: Upload and refresh cache
        run: |
          aws s3 sync site/ s3://kochi-bakery-site-111122223333/ --delete
          aws cloudfront create-invalidation --distribution-id EDFDVBD6EXAMPLE --paths "/*"

The identity step in the job log proves no access key was used. The role session name is set by the action:

Output
{
    "UserId": "AROASAMPLEROLEID0002:GitHubActions",
    "Account": "111122223333",
    "Arn": "arn:aws:sts::111122223333:assumed-role/github-deploy-site/GitHubActions"
}

AWS-native pipelines: CodePipeline and CodeBuild

Some companies prefer to keep the whole pipeline inside AWS:

  • AWS CodePipeline connects stages: Source (GitHub, GitLab or Bitbucket through a CodeConnections connection, an S3 bucket, an ECR image, or AWS CodeCommit), Build, optional Approval, and Deploy.
  • AWS CodeBuild runs build commands in a fresh container and bills by build minute. Its instructions live in buildspec.yml in your repository.
  • Deploy actions include CloudFormation stacks, ECS services, S3 uploads and AWS CodeDeploy for EC2, ECS and Lambda deployments.

A small buildspec.yml for a Python API that runs tests and packages a SAM application:

YAML
version: 0.2

phases:
  install:
    runtime-versions:
      python: 3.12
  pre_build:
    commands:
      - pip install -r requirements.txt pytest
      - pytest -q
  build:
    commands:
      - sam build
      - sam package --s3-bucket "$ARTIFACT_BUCKET" --output-template-file packaged.yaml

artifacts:
  files:
    - packaged.yaml

CodeBuild also supports a role per project, so the build never needs stored keys either. Pick the runtime version from the CodeBuild image you use; available versions change as images are updated.

Deploy safely

Automation makes releases fast; deployment strategy makes them safe:

StrategyHow it worksGood for
RollingReplace instances or tasks a few at a timeMost web apps on ECS or Auto Scaling groups
Blue/greenStart a full new version beside the old, then switch trafficFast rollback, database-independent changes
CanarySend a small share (for example 10%) to the new version first, then the restRisky changes, Lambda functions with aliases

Combine any strategy with CloudWatch alarms that stop and roll back the deployment when errors rise. CodeDeploy and ECS deployments can watch alarms for you, and Lambda aliases can shift traffic gradually.

Common mistakes

  • Trust policies without a sub condition, letting any GitHub repository assume the role.
  • Giving the deploy role AdministratorAccess instead of the few actions the deployment needs.
  • Same role for pull requests and production deploys, so untrusted code could deploy.
  • No tests or plan step before deploy, turning CI/CD into "continuous breaking".
  • No rollback plan, such as alarms that stop a bad deployment.

Key points

  • CI/CD builds, tests and deploys every change the same way; pipelines must not store long-lived AWS keys.
  • GitHub Actions uses OIDC: an IAM identity provider for token.actions.githubusercontent.com, audience sts.amazonaws.com, and a role assumed with sts:AssumeRoleWithWebIdentity.
  • Always restrict the sub claim to your repository and branch or environment, and keep deploy permissions narrow.
  • CodePipeline connects source, build, approval and deploy stages; CodeBuild runs buildspec.yml.
  • Rolling, blue/green and canary deployments with alarm-based rollback keep releases safe.

Exercise

Meera's repository meera-labs/clinic-api must deploy to production only from the main branch, and pull requests must be able to run terraform plan with read-only access. Write the two sub condition values you would use in the two roles' trust policies, and say which AWS managed policy is a reasonable starting point for the plan role.

Show solution
Text
Deploy role: token.actions.githubusercontent.com:sub = repo:meera-labs/clinic-api:ref:refs/heads/main
Plan role:   token.actions.githubusercontent.com:sub = repo:meera-labs/clinic-api:pull_request
Plan role permissions: start from ReadOnlyAccess, plus read and lock access to the Terraform state bucket.

Separating the roles means code in an unmerged pull request can read and plan but cannot change production. Both trust policies also keep the aud check for sts.amazonaws.com.

Quick Quiz

1. What does the GitHub workflow need so it can request an OIDC token?

2. Why must the trust policy check the sub claim?

3. Which file tells CodeBuild what commands to run?

4. Which strategy sends a small share of traffic to the new version first?

Learn AWS with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call