End-to-end demonstration of enforcing per-repo privileges on AWS with GitHub Actions OIDC.
We'd like to use GitHub Actions' OIDC IdP for directly presenting repository identities to AWS for authentication+authorisation control, hoping we'll be able to mostly get rid of AWS Access Key secrets and IAM Instance Roles across our CI estate.
Unfortunately, despite AWS' support for OIDC with IAM and GitHub Actions' OIDC IdP, the AWS community has experienced long-standing issues with presenting a repository identity for use during IAM policy evaluation.
Utopia: Directly enforce resource naming in IAM based on OIDC claims
While directly enforcing resource naming in IAM based on OIDC claims would be the best option toward reducing the number of internally-managed components in the security critical path, we’re unable to take this option today due to parallel blockers on GitHub and AWS' issuance and de-marshalling of OIDC JWT tokens (respectively).
(see also, Aidan Steele's 2021 blog post: AWS IAM OIDC IDPs need more controls)
In this repository, we show off using a Reusable Workflow to provide AWS IAM with repository information through an inline session policy while also enforcing use of the Reusable Workflow through a customised sub
claim.
Hoping for a future where either AWS or GitHub are able to unblock us, we also call out future migration ‘escape path’s against our presented workaround.
-
cfn.yaml: CloudFormation template
- AWS::IAM::OIDCProvider, to configure GitHub Actions' OIDC provider with AWS
- AWS::S3::Bucket, to hold files uploaded by GitHub Actions
- AWS::IAM::Role, to allow a reusable workflow broad access to the aforementioned S3 bucket
-
.github/workflows/reusable-s3.yaml: Reusable Workflow
- aws-actions/configure-aws-credentials, to establish a session with AWS IAM/STS
- actions/download-artifact, to retrieve artifacts uploaded by earlier jobs
- AWS CLI, for uploading artifacts to AWS S3
-
.github/workflows/main-s3.yaml: Unit test workflow
- Demonstrate creating a test file and uploading it to S3, under a bucket prefix determined by the repo name
-
Create a new GitHub repo
-
Configure fork / branch protection settings (see also, § Guarding against malicious forks)
-
Make a GitHub REST API call to Set the customization template for an OIDC subject claim for a repository
curl --request PUT \
--url https://api.github.com/repos/$GH_ORG/$GH_REPO/actions/oidc/customization/sub \
--header 'Accept: application/vnd.github+json' \
--header 'Authorization: Bearer $GH_TOKEN' \
--data '{"use_default":false,"include_claim_keys":["repo","context","job_workflow_ref"]}'
-
Deploy cfn.yaml with AWS CloudFormation
-
Update environment variables in .github/workflows/reusable-s3.yaml (
BUCKET
,ROLE_TO_ASSUME
, andREGION
) to match your AWS account using the CloudFormation outputs -
Observe GitHub Actions authenticating to AWS with OIDC before uploading a test file
While GitHub Actions doesn’t allow generally customising its OIDC claims format, we can require job_workflow_ref
be included in the sub
claim and reference this in an AWS IAM policy to restrict access to a Reusable Workflow managed by our infrastructure team.
This workflow can then use an inline session policy (via aws-actions/configure-aws-credentials#739) and provide reduced privileges scoped to the repository identity.
-
Assuming GitHub introduce support for custom OIDC claims:
- we could emit OIDC claims directly from GitHub in AWS' Session Tags format, obviating our need for the Reusable Workflow’s initial highly privileged policy.
-
Assuming AWS introduce support for reading arbitrary OIDC claims:
- we could create IAM policies that directly provide reduced privileges based on the repository identity, obviating our need for the Reusable Workflow’s initial highly privileged policy.
- We place load bearing security responsibilities upon our Reusable Workflow, significantly increasing the risk of a confused deputy problem through logic or parameter escaping issues
- We can introduce override logic inside the Reusable Workflow for projects that need non-standard AWS resource names
Since passing session tags using AssumeRoleWithWebIdentity requires a proprietary https://aws.amazon.com/tags
claim that GHA can't emit as of 2023, we're unable to immediately rely on session tagging for isolating resources with an IAM role policy.
Instead, we use the github
context to inject the repository identity into an inline session policy.
gha-aws-oidc-sample/.github/workflows/reusable-s3.yaml
Lines 55 to 89 in 2b8d2ee
This happens during a single call to AssumeRoleWithWebIdentity, so we don't expect e.g., early crashes in our Reusable Workflow to be useful for privilege escalation.
We also use the RoleSessionName
parameter (on AssumeRoleWithWebIdentity) to ensure the repository identity is recorded in CloudTrail.
Note
As of 2023, this requires a per-repo GitHub REST API call to Set the customization template for an OIDC subject claim for a repository.
We've observed just calling Set the customization template for an OIDC subject claim for an organization isn't enough to include job_workflow_ref
in the sub
claim.
Trying to access AWS from a repository with an uncustomised OIDC subject claim template (or trying to assume the IAM role without using the Reusable Workflow) will cause AWS to return Error: Not authorized to perform sts:AssumeRoleWithWebIdentity
.
GitHub Docs Example: Requiring a reusable workflow
In an effort to reduce the attack surface for our (initially) widely privileged IAM role, we make use of GitHub Actions OIDC IdP's subject claim customisation to indicate our Reusable Workflow's identity. This job_workflow_ref
claim is then declared as a condition to our AssumeRolePolicyDocument
.
Lines 53 to 64 in 2b8d2ee
We leverage "Approving workflow runs from private forks" to ensure the sub
claim for job_workflow_ref
can't be confused through unexpected PRs against our Reusable Workflow.
Additionally, we use branch protection rules to ensure only our infrastructure team is able to create new branches that are accepted by our IAM role's job_workflow_ref
condition.
Warning
If your reusable workflow provides a customer execution environment (e.g., for Docker image
building), you must ensure the ACTIONS_RUNTIME_TOKEN
and ACTIONS_ID_TOKEN_REQUEST_URL
environment variables are not accessible.
GitHub Docs Configuring OpenID Connect in cloud providers § Using custom actions
GitHub Actions allows jobs to request an OIDC token by making a request to ACTIONS_ID_TOKEN_REQUEST_URL
using ACTIONS_RUNTIME_TOKEN
.
If these environment variables are accesible by a customer execution environment hosted by your reusable workflow, an attacker could achieve privilege escalation by asking GitHub Actions for a fresh OIDC token and skipping the inline-session-policy
.
- Aidan Steele's 2021 blog post: AWS IAM OIDC IDPs need more controls
- AWS Docs Available keys for AWS web identity federation
- AWS Docs Passing session tags using AssumeRoleWithWebIdentity (not available with GHA as of 2023)
- AWS Docs The confused deputy problem
- GitHub Docs Configuring OpenID Connect in Amazon Web Services
- GitHub Docs Configuring OpenID Connect in cloud providers § Using custom actions
- GitHub Docs Example: Requiring a reusable workflow
- GitHub Docs
github
context - configure-aws-credentials Docs Inline session policies
- configure-aws-credentials Docs Session tagging