Assume AWS IAM roles from GitHub Actions workflows with no stored secrets

AWS IAM roles for GitHub Actions workflows

Background and rationale

GitHub Actions are a pretty nice solution for CI/CD. Where they fall short is integration with other services, like AWS. The approach suggested by AWS is to create an IAM user, allocate it a long-lived access key and store those credentials in GitHub's secret storage. This is undesirable for folks working in an environment where IAM users are not permitted.

This repo is a GitHub action that can grant your workflows access to AWS via an AWS IAM role session. This means no need to store long-lived credentials in GitHub and comes with a few other benefits.

Usage

Inside AWS

api.yml is a CloudFormation template to deploy in one of your AWS accounts. It has three resources:

  • GithubSecret is an AWS::SecretsManager::Secret for storing credentials to access the GitHub API. It needs both a GitHub personal access token and the user_session cookie for github.com from a logged-in user.

  • ExampleRoleForGithub is an AWS::IAM::Role to demonstrate how you can create a role that is assumable by a GitHub Actions workflow. Specifically it demonstrates the role having tags and allowing the Lambda function to pass role session tags.

  • Function is an AWS::Serverless::Function that creates both an API Gateway and a Lambda function as its backend. It has an example IAM policy and set of role session tags that are likely useful.

In your GitHub Action workflows

on:
  - push
name: example
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      
      - name: keygen
        uses: glassechidna/actions2aws/[email protected]

      - name: assume role
        uses: glassechidna/actions2aws/[email protected]
        with:
          step: keygen
          url: ${{ secrets.URL }}
          role: arn:aws:iam::${{ secrets.ACCOUNT_ID }}:role/actions2aws-ExampleRoleForGithub
          # these values should look something like:  
          # url: https://someexample.execute-api.ap-southeast-2.amazonaws.com/
          # role: arn:aws:iam::0123456789012:role/actions2aws-ExampleRoleForGithub

      - run: aws sts get-caller-identity # or whatever else you want

Things worth knowing

  • The github.com user_session cookie value is needed because the GitHub public API currently won't return logs for an in-progress workflow run.

  • How you define your role's trust policies is really up to you. My recommendations would be (these are all defined in api.yml):

    • Restrict the Lambda function's role to only be allowed to assume specific roles. In my case, I don't have a naming scheme - so I limit it to roles with a github:actions-role = true tag.
    • Restrict the Lambda function's role to only be allowed to pass particular session tags (e.g. only github:*)
    • Match the above session tag restrictions on the target role side too, i.e. limit the allowable session tags in the role's trust policy.
    • Limit target roles to only being assumable by the Lambda function, rather than the entire account hosting the Lambda function.

How it works

tl;dr:

  • The keygen action generates an age public-private keypair, saves the private half to disk and logs the public half to the console.
  • The request action invokes the Lambda (via the API Gateway) with a POST body that identifies which repo, workflow and run it is.
  • The Lambda uses this POST body to lookup information about the in-progress run (including its public key logged to the console) and assume a role for the run.
  • The Lambda returns the assumed role session credentials encrypted with the public key displayed in the logs for the keygen step. This ensures that no one else can receive valid credentials on behalf of that workflow run.
  • The request action then sets AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN environment variables for future steps.

sequence diagram

Similar Resources

A Pulumi multi language component to create an IAM role for an EKS cluster

xyz Pulumi Component Provider (Go) This repo is a boilerplate showing how to create a Pulumi component provider written in Go. You can search-replace

Oct 27, 2021

No need for IAM users when we have Yubikeys

cloudkey As far as I can tell, the only justification for AWS IAM users that I hear nowadays is for usage on non-interactive systems outside of AWS, e

Dec 5, 2022

Send IAM-signed requests to AppSync and API Gateway

golang-iam-requests Provides helpers to send IAM-signed requests to AWS AppSync and AWS API Gateway services Generates a v4 sign using IAM credentials

Apr 21, 2022

Git-utils - Utility to automate git workflows

Git-Utilities Utility to automate git workflows. Commands userstory get/set/upda

Jan 2, 2022

A simple action that cancels redundant workflows

A simple action that cancels redundant workflows. For example when merging pull request.

Jan 27, 2022

Simple no frills AWS S3 Golang Library using REST with V4 Signing (without AWS Go SDK)

simples3 : Simple no frills AWS S3 Library using REST with V4 Signing Overview SimpleS3 is a golang library for uploading and deleting objects on S3 b

Nov 4, 2022

Integrate AWS EKS Anywhere cluster with AWS Services

 Integrate AWS EKS Anywhere cluster with AWS Services

This article provides step-by-step instruction on integrating AWS EKS Anywhere with AWS Services so the applications running on customer data center can securely connect with these services.

Mar 6, 2022

Apis para la administracion de notifiaciones, utilizando servicios como AWS SNS y AWS SQS

notificacion_api Servicio para envío de notificaciónes por difusión en AWS SNS Especificaciones Técnicas Tecnologías Implementadas y Versiones Golang

Jan 7, 2022

A package for access aws service using AWS SDK for Golang

goaws 🚀 A package for access aws service using AWS SDK for Golang Advantage with goaws package Example for get user list IAM with AWS SDK for Golang

Nov 25, 2021
Comments
  • Protecting against malicious PRs

    Protecting against malicious PRs

    I think this is a very neat idea, great job on coming up with it!

    A couple of attacks come to mind, this is one of them.

    Could a malicious PR obtain credentials? An attacker:

    1. Forks the repo
    2. Changes the workflow to send the creds to themself
    3. Creates a PR.
    4. The action runs, the lambda generates creds, and the subverted job sends them on to the attacker.

    Or is that what this check is protecting against? The comment mentions forks, I'm not familiar enough with the github api to know if this is protecting against PRs running in the genuine repo but sourced from a fork.

    If this is already protected against (and the lambda won't return creds for PRs sourced from forks), then I think it would be worth mentioning in the README.

  • Potential AWS critical vulnerability

    Potential AWS critical vulnerability

    Anyone with knowledge of the endpoint url for lambda and the AWS accountId has the capability to assume the role. This is because there is no way to verify the token that comes in is actually from github.

    Any one can create a Personal Access Token (as well as any one inside a private org with READ access to any repo for private org repos) which can trick the lambda into granting credentials.

    Until GitHub provides the capability to verify token issuer (via OAuth2.0 JWKs for examples), I recommend Do not use this repository to grant access to AWS.

  • Public key injection

    Public key injection

    I think there is a potential attack on the integrity of the public key.

    An attacker can call the API at any point, and specify any run/job/step combo for which to search the output for a public key. If they can get their own public key included in the output of any step of any job, they can then obtain creds for the victim's account by calling the API specifying where it should look.

    Whilst this won't be exploitable against all workflows, according to Project Zero's research:

    almost any project with somewhat complex Github actions is vulnerable to this bug class

    It's not obvious to users that if you're using actions2aws then allowing attacker-controlled text to appear in the output of any job grants that attacker access to your AWS account.

    One mitigation would be to make the job/step config part of the lambda deployment, so it's not under attacker control.

  • Considerations about Potential Privilege Escalation

    Considerations about Potential Privilege Escalation

    Hi. Thank you for sharing your solution 😀. I just came across your implementation and had a look at your diagram. I’m asking myself how the lambda makes sure the requested credentials are delivered to an action belonging to your repos/org? What’s preventing a malicious GitHub action with any valid GitHub repo and any valid user session token from generating a key pair calling the api and getting your aws creds ? Any thoughts on this ? Kind regards Rocco

Assume-shell - A tool to create a shell with AWS environment credentials set

assume-shell This tool will request AWS credentials for a given profile/role and

Sep 29, 2022
Feb 7, 2022
Assumes roles in AWS that have useful role session tags
Assumes roles in AWS that have useful role session tags

ghaoidc Assumes roles in AWS that have useful role session tags GitHub Actions has (almost) launched OpenID Connect federation. This means you can ass

Jul 21, 2022
Automatically roll your AWS IAM access key (aws_access_key_id) and secret key (aws_secret_access_key).

roll-it Keep your AWS Credentials fresh ?? on Windows, Mac, Linux (arm or x86)! What it Does Programmatically rotate your AWS IAM access keys and secr

Jan 6, 2023
lightweight, self-service AWS IAM management
lightweight, self-service AWS IAM management

Contents Overview Architecture Prerequisites Workflow What groups exist? Who do I ask for access? What groups am I in? How do I add group members? How

Jan 16, 2022
Foundational systems for gitops-style AWS development workflows

aws-basics Foundational systems for gitops-style AWS development workflows. Prerequisites GitHub and AWS accounts Terraform Golang Bootstrapping Setup

Oct 28, 2021
actionlint is a static checker for GitHub Actions workflow files.

actionlint actionlint is a static checker for GitHub Actions workflow files. Features: Syntax check for workflow files to check unexpected or missing

Dec 27, 2022
GitHub Actions demo for a monorepo Go project

GitHub Actions demo for a monorepo Go project The purpose of this repository is to demonstrate using a GitHub action as a pull request status check in

Oct 31, 2021
Explores GitHub Actions in Go Lab from GopherCon 2021

Gopher A Tweet An action that tweets. Gopher A Tweet was created based on GopherCon 2021s Gophers of Microsoft: GitHub Action in Go Lab to explore bui

Dec 10, 2021
Golang-action - A template repository for writing custom GitHub Actions in Golang

Golang Action A template repository for writing custom GitHub Actions in Golang.

Feb 12, 2022