Skip to content

Enforcing per-repo privileges on AWS with GitHub Actions OIDC, without session tags

License

Notifications You must be signed in to change notification settings

Skyscanner/gha-aws-oidc-sample

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

gha-aws-oidc-sample

End-to-end demonstration of enforcing per-repo privileges on AWS with GitHub Actions OIDC.

Context and problem statement

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

Utopia: Directly enforce resource naming in IAM based on OIDC claims

Diagram: Utopia

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).

Blockers

(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.

Contents

Deploying

  1. Create a new GitHub repo

  2. Configure fork / branch protection settings (see also, § Guarding against malicious forks)

  3. 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"]}'
  1. Deploy cfn.yaml with AWS CloudFormation

  2. Update environment variables in .github/workflows/reusable-s3.yaml (BUCKET, ROLE_TO_ASSUME, and REGION) to match your AWS account using the CloudFormation outputs

  3. Observe GitHub Actions authenticating to AWS with OIDC before uploading a test file

Design: Introduce a Reusable Workflow that enforces resource naming

Diagram: Reality - Introduce a Reusable Workflow that enforces resource naming

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.

Escape Path 🏃

  • 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.

Downsides 😥

  • 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

Upsides 🚀

  • We can introduce override logic inside the Reusable Workflow for projects that need non-standard AWS resource names

Security Properties

Reducing Privileges at Runtime

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.

- name: Login AWS
uses: aws-actions/configure-aws-credentials@v2.2.0
with:
role-to-assume: ${{ env.ROLE_TO_ASSUME }}
role-session-name: ${{ steps.prepare-aws-role-session-name.outputs.result }}
aws-region: ${{ env.REGION }}
mask-aws-account-id: 'no'
inline-session-policy: >-
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::${{ env.BUCKET }}"
]
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectAcl",
"s3:GetObjectVersion",
"s3:PutObject",
"s3:PutObjectAcl"
],
"Resource": [
"arn:aws:s3:::${{ env.BUCKET }}/${{ github.repository }}/*"
]
}
]
}

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.

Requiring use of our Reusable Workflow

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.

AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action: sts:AssumeRoleWithWebIdentity
Principal:
Federated: !Ref GithubOidc
Condition:
StringEquals:
token.actions.githubusercontent.com:aud: "sts.amazonaws.com"
StringLike:
token.actions.githubusercontent.com:sub: !Sub "repo:${GitHubOrg}/*:job_workflow_ref:${GitHubReusableWorkflow}"

Guarding against malicious forks

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.

Separating execution environments

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.

Further Reading

About

Enforcing per-repo privileges on AWS with GitHub Actions OIDC, without session tags

Topics

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks