Connect GitHub Actions to your AWS account (OIDC)
The generated workflow does not use long‑lived AWS access keys in the repository. It uses OpenID Connect (OIDC) so each job can assume a short‑lived role in your account. You add two GitHub repository secrets so the workflow knows which role and which region to use.
What you put in GitHub (repository secrets)
Section titled “What you put in GitHub (repository secrets)”Add these under Repository → Settings → Secrets and variables → Actions → New repository secret:
| Secret | Example value | Purpose |
|---|---|---|
AWS_DEPLOY_ROLE_ARN |
arn:aws:iam::123456789012:role/scalebop-github-oidc-…-DeployRole-… |
IAM role ARN that trusts GitHub OIDC and can run CDK deploy |
AWS_REGION |
us-east-1 |
Region for the deploy; must match cdk bootstrap (the workflow runs bootstrap idempotently before deploy) |
The workflow step Configure AWS credentials reads AWS_DEPLOY_ROLE_ARN and AWS_REGION and exchanges the GitHub OIDC token for AWS credentials for that job only. It requests audience sts.amazonaws.com (required by AWS STS).
You do not create AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY secrets for this OIDC setup.
Create the IAM OIDC provider and role
Section titled “Create the IAM OIDC provider and role”Option 0 — ScaleBop (recommended when AWS + GitHub are connected)
Section titled “Option 0 — ScaleBop (recommended when AWS + GitHub are connected)”If you connected GitHub and AWS under ScaleBop Account → Integrations, use Set up GitHub OIDC there. ScaleBop creates the account-level ScaleBop-GitHubOidc-* stack in your AWS account (provider audience sts.amazonaws.com + a shared deploy role trusted to your GitHub owner) and syncs AWS_DEPLOY_ROLE_ARN / AWS_REGION to GitHub Actions secrets for your projects when possible.
Critical: audience / Client ID
Section titled “Critical: audience / Client ID”The IAM identity provider Audience (also called Client ID list) must include exactly:
sts.amazonaws.comIf the provider is missing, or the audience is set to a GitHub URL / org name instead, Configure AWS credentials fails with:
Could not assume role with OIDC: The web identity token provided could not be validated.That message is token validation (provider URL / audience / thumbprint), not a sub mismatch. sub mismatches usually say AccessDenied / Not authorized to perform sts:AssumeRoleWithWebIdentity.
Option A — Console
Section titled “Option A — Console”- IAM → Identity providers → Add provider → OpenID Connect
- Provider URL:
https://token.actions.githubusercontent.com(includehttps://, no trailing slash) - Audience:
sts.amazonaws.com
- Provider URL:
- IAM → Roles → Create role → Web identity
- Identity provider: the GitHub provider above
- Audience:
sts.amazonaws.com - Then edit the trust policy so
submatches your repo (example below)
- Attach permissions sufficient for CloudFormation / CDK (often AdministratorAccess during bring‑up; tighten later).
- Copy the role ARN →
AWS_DEPLOY_ROLE_ARN.
Option B — CloudFormation (recommended)
Section titled “Option B — CloudFormation (recommended)”Save the template below as docs/github-oidc-role.yaml (also included as a separate file in the Pipeline export ZIP), then deploy:
aws cloudformation deploy \ --template-file docs/github-oidc-role.yaml \ --stack-name scalebop-github-oidc-YOUR_REPO \ --parameter-overrides GitHubOrg=YOUR_ORG GitHubRepo=YOUR_REPO \ --capabilities CAPABILITY_IAMUse a per-repository stack name so a second project in the same AWS account gets its own deploy role and does not collide with or overwrite the first role’s trust policy.
If this account already has a GitHub OIDC provider, add CreateOidcProvider=false to --parameter-overrides, and still confirm that provider’s Client ID list includes sts.amazonaws.com.
Then set AWS_DEPLOY_ROLE_ARN to the stack output DeployRoleArn.
Template (docs/github-oidc-role.yaml)
Section titled “Template (docs/github-oidc-role.yaml)”AWSTemplateFormatVersion: "2010-09-09"Description: >- GitHub Actions OIDC provider + deploy role for ScaleBop-generated workflows. The provider Audience (ClientId) MUST be sts.amazonaws.com — otherwise STS returns "The web identity token provided could not be validated." Trust sub allows legacy repo:ORG/REPO and immutable repo:ORG@ID/REPO@ID subjects (GitHub default for repos created on/after 2026-07-15).
Parameters: GitHubOrg: Type: String Description: GitHub organization or user that owns the repository GitHubRepo: Type: String Description: Repository name (without org prefix); use * for account-shared trust CreateOidcProvider: Type: String Default: "true" AllowedValues: ["true", "false"] Description: >- Set to false if this account already has an IAM OIDC provider for token.actions.githubusercontent.com (only one provider URL per account).
Conditions: ShouldCreateProvider: !Equals [!Ref CreateOidcProvider, "true"]
Resources: GitHubOidcProvider: Type: AWS::IAM::OIDCProvider Condition: ShouldCreateProvider Properties: Url: https://token.actions.githubusercontent.com ClientIdList: - sts.amazonaws.com # Required by CloudFormation; AWS also validates tokens via GitHub JWKS. ThumbprintList: - 6938fd4d98bab03faadb97b93489cec4f5c6fdfd - 1c58a3a8518e8759bf075b76b750d4f2df264fcd
DeployRole: Type: AWS::IAM::Role Properties: # Omit RoleName so CloudFormation assigns a unique physical name (IAM limit 64 chars). # Account-level ScaleBop setup passes GitHubRepo=* for shared repo:OWNER/* trust. Description: Assumed by GitHub Actions via OIDC for ScaleBop CDK deploy AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Federated: !If - ShouldCreateProvider - !GetAtt GitHubOidcProvider.Arn - !Sub arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com Action: sts:AssumeRoleWithWebIdentity Condition: StringEquals: token.actions.githubusercontent.com:aud: sts.amazonaws.com StringLike: # Legacy name-only sub + immutable OWNER@ID/REPO@ID (post-2026-07-15). token.actions.githubusercontent.com:sub: - !Sub repo:${GitHubOrg}/${GitHubRepo}:ref:refs/heads/* - !Sub repo:${GitHubOrg}@*/${GitHubRepo}@*:ref:refs/heads/* ManagedPolicyArns: - arn:aws:iam::aws:policy/AdministratorAccess Policies: - PolicyName: ScaleBopGithubAppDelivery PolicyDocument: Version: "2012-10-17" Statement: - Sid: EcrAuth Effect: Allow Action: - ecr:GetAuthorizationToken Resource: "*" - Sid: EcrPush Effect: Allow Action: - ecr:BatchCheckLayerAvailability - ecr:PutImage - ecr:InitiateLayerUpload - ecr:UploadLayerPart - ecr:CompleteLayerUpload - ecr:BatchGetImage - ecr:GetDownloadUrlForLayer Resource: - !Sub arn:aws:ecr:*:${AWS::AccountId}:repository/scalebop-app-* - Sid: EcsRollout Effect: Allow Action: - ecs:DescribeTaskDefinition - ecs:RegisterTaskDefinition - ecs:UpdateService - ecs:DescribeServices - ecs:DescribeClusters Resource: "*" - PolicyName: ScaleBopGithubCdkDeploy PolicyDocument: Version: "2012-10-17" Statement: - Sid: AssumeCdkBootstrapRoles Effect: Allow Action: - sts:AssumeRole - iam:PassRole Resource: - !Sub arn:aws:iam::${AWS::AccountId}:role/cdk-hnb659fds-* - Sid: ReadCdkBootstrapSsm Effect: Allow Action: - ssm:GetParameter - ssm:GetParameters Resource: - !Sub arn:aws:ssm:*:${AWS::AccountId}:parameter/cdk-bootstrap/* - Sid: CdkBootstrapToolkitStack Effect: Allow Action: - cloudformation:CreateStack - cloudformation:UpdateStack - cloudformation:DescribeStacks - cloudformation:DescribeStackEvents - cloudformation:GetTemplate - cloudformation:ValidateTemplate Resource: - !Sub arn:aws:cloudformation:*:${AWS::AccountId}:stack/CDKToolkit/* - Sid: CdkBootstrapS3Assets Effect: Allow Action: - s3:CreateBucket - s3:PutBucketPolicy - s3:PutBucketPublicAccessBlock - s3:PutEncryptionConfiguration - s3:PutBucketTagging - s3:PutBucketVersioning - s3:GetBucketLocation - s3:ListBucket - s3:PutObject - s3:GetObject Resource: - arn:aws:s3:::cdk-hnb659fds-assets-* - arn:aws:s3:::cdk-hnb659fds-assets-*/* - Sid: CdkBootstrapIamRoles Effect: Allow Action: - iam:CreateRole - iam:DeleteRole - iam:GetRole - iam:UpdateRole - iam:AttachRolePolicy - iam:DetachRolePolicy - iam:PutRolePolicy - iam:DeleteRolePolicy - iam:GetRolePolicy - iam:CreatePolicy - iam:DeletePolicy - iam:GetPolicy - iam:CreatePolicyVersion - iam:PassRole - iam:TagRole - iam:TagPolicy Resource: - !Sub arn:aws:iam::${AWS::AccountId}:role/cdk-hnb659fds-* - !Sub arn:aws:iam::${AWS::AccountId}:policy/cdk-hnb659fds-* - Sid: CdkBootstrapSsmWrite Effect: Allow Action: - ssm:PutParameter - ssm:GetParameter - ssm:GetParameters - ssm:AddTagsToResource Resource: - !Sub arn:aws:ssm:*:${AWS::AccountId}:parameter/cdk-bootstrap/*
Outputs: DeployRoleArn: Description: Set this value as the GitHub Actions secret AWS_DEPLOY_ROLE_ARN Value: !GetAtt DeployRole.Arn OidcProviderArn: Description: IAM OIDC provider ARN used by the deploy role trust policy Value: !If - ShouldCreateProvider - !GetAtt GitHubOidcProvider.Arn - !Sub arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.comExample trust policy
Section titled “Example trust policy”Replace ACCOUNT_ID, YOUR_ORG, and YOUR_REPO. StringLike on sub covers push, workflow_dispatch, and ScaleBop repository_dispatch (scalebop-deploy) on any branch ref.
Allow both subject shapes: legacy repo:ORG/REPO:… and immutable repo:ORG@OWNER_ID/REPO@REPO_ID:… (default for repositories created on/after 2026-07-15, and for older repos that opt in). A policy that only lists the legacy form fails assume for immutable subjects with Not authorized to perform sts:AssumeRoleWithWebIdentity.
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" }, "StringLike": { "token.actions.githubusercontent.com:sub": [ "repo:YOUR_ORG/YOUR_REPO:ref:refs/heads/*", "repo:YOUR_ORG@*/YOUR_REPO@*:ref:refs/heads/*" ] } } } ]}For account-shared trust (GitHubRepo=*), use repo:YOUR_ORG/*:ref:refs/heads/* and repo:YOUR_ORG@*/*@*:ref:refs/heads/*.
To allow only main, change each sub pattern’s ref suffix to ref:refs/heads/main (keep both legacy and immutable prefixes). Prefer exact numeric IDs from GitHub when you want tighter binding than @*.
If assumption still fails
Section titled “If assumption still fails”| Error text | Likely cause | Fix |
|---|---|---|
| web identity token provided could not be validated | Wrong/missing OIDC provider audience, bad provider URL, or stale thumbprint | Provider URL https://token.actions.githubusercontent.com; Client ID list includes sts.amazonaws.com; recreate provider if needed |
| AccessDenied / Not authorized … AssumeRoleWithWebIdentity | Trust sub does not match this run (legacy vs immutable, or wrong org/repo/branch) |
Allow both repo:ORG/REPO:… and repo:ORG@*/REPO@*:… under StringLike; use …:ref:refs/heads/* for dispatch triggers |
| Missing OIDC token / credential step cannot mint token | Workflow lacks id-token: write |
Keep the generated permissions block |
Also confirm AWS_DEPLOY_ROLE_ARN is this GitHub OIDC role (not a ScaleBop cross-account or unrelated role).
Local development (optional, separate from CI)
Section titled “Local development (optional, separate from CI)”To run cdk synth or cdk deploy on your laptop, use your normal AWS CLI setup: aws configure, environment variables, or SSO. That is independent of the GitHub secrets above.
Blueprint context (from analysis)
Section titled “Blueprint context (from analysis)”- Backend: ECS Fargate + Application Load Balancer
- Frontend: CloudFront + S3
- Database: Postgres (optional)
Manual and ScaleBop triggers
Section titled “Manual and ScaleBop triggers”- GitHub UI: Actions → Deploy Generated App → Run workflow (
workflow_dispatch). - ScaleBop: On your project Pipeline page, use Run deploy after the workflow is merged. ScaleBop calls GitHub’s repository dispatch API with event type
scalebop-deploy(same as this workflow’srepository_dispatchtrigger).
MVP note
Section titled “MVP note”This workflow targets a single environment. Add staging/production roles, environments, and promotion rules when you are ready.