GitHub OIDC and AWS-Owned Control
Use this pattern when GitHub should authorize a bounded AWS control action without making an interactive browser, MCP, SSO, or runner session responsible for long execution.
Core separation
Keep five concerns separate:
- Repository access — GitHub App or normal GitHub permissions.
- Workflow definition — GitHub Actions.
- Execution substrate — GitHub-hosted runner, CodeBuild-hosted runner, or another reviewed runner.
- AWS control identity — GitHub OIDC to a bounded IAM role.
- Long execution — an AWS-owned service such as SSM Automation, CodeBuild, CodePipeline, Step Functions, Image Builder, or another appropriate native service.
The runner service role is not the workload/control role.
Preferred office control path
For durable office AWS automation, prefer:
GitHub request -> GitHub Actions -> runner -> GitHub OIDC -> bounded AWS role -> short control action -> AWS-owned execution
A CodeBuild-hosted GitHub Actions runner is useful when AWS-local execution, VPC access, runner scheduling, or GitHub-hosted-runner constraints justify it. Do not make it universal when a normal GitHub-hosted runner is sufficient.
The GitHub job should normally remain short:
authenticate -> verify repo/ref -> verify account -> verify Region -> verify exact target -> start one bounded AWS operation -> persist execution ID -> exit
Durable execution checkpoint
Before the short controller exits, persist enough state for another session or worker to resume safely:
- request or plan digest;
- account/environment and Region;
- exact target resource;
- execution service;
- execution ID;
- last observed status;
- provider/readback evidence pointer.
Examples include:
- SSM Automation execution ID;
- CodeBuild build ID;
- Step Functions execution ARN;
- CodePipeline execution ID;
- CloudFormation stack or change-set identifier.
Before retrying a mutation, read the existing execution state first. Never blindly start a duplicate mutation merely because a ChatGPT, MCP, SSO, runner, or browser session ended.
Fail-closed mutation gate
Before a write, verify at minimum:
- expected repository and ref;
- expected AWS account;
- expected Region;
- exact target resource;
- expected change shape;
- no unrelated IAM, network, compute, or replacement change;
- provider/readback path exists.
For Terraform-controlled changes prefer:
import/read live state -> plan -> machine gate -> review gate when required -> apply exact reviewed plan -> provider/Terraform readback
Interactive tools versus durable control
AWS MCP, CLI, SSO, AgentCore Harness, or another interactive operator surface may be appropriate for:
- discovery;
- read-only inspection;
- short troubleshooting;
- preparing a bounded request;
- reading execution status.
They should not be the durable office mutation dependency when their credentials can expire mid-work.
A useful separation is:
operator surface -> detect/explain/prepare -> durable approved request -> GitHub/OIDC controller -> AWS-owned execution -> provider readback
Connector expiry is a credential event, not a planning reset:
checkpoint in GitHub -> continue repository-only work -> reconnect only when live AWS is the blocker -> verify identity -> resume from execution state
SSM does not imply EC2
Do not create one EC2 instance per account merely to gain control.
For compliance and remediation prefer:
native AWS API -> AWS-managed SSM Automation -> custom SSM Automation -> Lambda only when justified
SSM Automation can invoke AWS APIs without an EC2 managed node.
Use EC2 plus SSM only when a real host or VPC-local requirement exists, such as:
- private endpoints;
- internal DNS;
- local binaries;
- host patching;
- host-level validation.
Multi-account default
Prefer one durable controller and the same narrow target-role contract in each account:
GitHub OIDC controller -> bounded controller role -> STS AssumeRole -> target role -> AWS-owned execution
Re-verify target identity after every role assumption.
Do not require one runner or one EC2 instance per account unless network locality requires it.
Troubleshooting order
Use this order:
- runner assigned?
- workflow step reached?
- OIDC token exchange reached?
- STS caller identity correct?
- target account/Region correct?
- AWS API permitted?
- execution ID created?
- AWS-owned execution succeeded?
- provider readback correct?
Do not classify RUNNER_NOT_ASSIGNED as an OIDC or IAM failure.
Reuse rule
Generalize the controller substrate; keep workload-specific behavior in repository-owned configuration; keep permissions bounded.
Avoid creating a new broad role, runner, or workflow for every workload when the execution shape is materially the same.
Citations
- https://docs.aws.amazon.com/codebuild/latest/userguide/action-runner-overview.html
- https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-idp_oidc.html
- https://docs.aws.amazon.com/systems-manager/latest/userguide/systems-manager-automation.html
- https://github.com/amitkarpe/ami-factory
- https://github.com/amitkarpe/nextflow-offline