From b26f0df003fec01df1fdac0d58f022352f218228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jukka=20Palom=C3=A4ki?= Date: Thu, 2 Dec 2021 22:36:51 +0200 Subject: [PATCH] Make AWS access keys optional and document OIDC (#11) * Make AWS access keys optional For OIDC (AssumeRoleWithWebIdentity) * Update action.yml * Update README.md * Update README.md * Update README.md * Update action.yml * Update action.yml * Update README.md * Update README.md * Update action.yml * Update action.yml * Update README.md * Update README.md * Update README.md * Update README.md --- README.md | 127 +++++++++++++++++++++++++++++++++++------------ start/action.yml | 10 ++-- stop/action.yml | 10 ++-- 3 files changed, 106 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index d697ba4..04f79dc 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,97 @@ # ec2-actions-runner -⚠️ This is a new project and is very experimental +⚠️ This is a new project and as such, backwards-incompatible changes may occur between releases Composite actions for managing an on-demand, self-hosted GitHub actions _repository_ runner (Linux on EC2). -Inspired by - -## Requirements - -- AWS account and VPC network - - A default VPC works fine, too -- AWS credentials with EC2 permissions - - You can use either a plain IAM user, or assume a role -- VPC subnet with Internet access - - Public subnet (public IP) **OR** - - Private subnet and NAT gateway -- Linux runner AMI (amd64 or arm64), with the following things pre-configured: - - Non-root user to run actions-runner service as - - [Actions-runner](https://github.com/actions/runner) v2.283.1+ and required [dependencies](https://github.com/actions/runner/blob/main/docs/start/envlinux.md) - - `git`, `docker`, `curl` and optionally `at` (if using the `auto-shutdown-at` feature) - - See e.g. for an example AMI build -- EC2 launch template (AMI, instance type, VPC subnet, security groups, spot options etc) - - See example [Cloudformation template](https://gist.github.com/jpalomaki/003c4d173a856cf64c6d35f8869a2de8) that sets up a launch template -- GitHub personal access token (PAT) with `repo` scope - -See [start/action.yml](start/action.yml) and [stop/action.yml](stop/action.yml) for all available input parameters. +Inspired by ❤️ + +## Pre-requisites + +- AWS account + - Permissions to provision IAM, EC2 and VPC resources (to set up the runner scaffolding) +- VPC network + - Subnet with Internet access (required because self-hosted runners communicate with github.com) + +## Limitations + +- GitHub Enterprise Server (GHES) is not currently supported + +## Setup + +1. AWS: Configure GitHub OIDC identity provider (GitHub [documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services)) + - Use of OIDC is recommended, so that static AWS access keys need not be stored in GitHub secrets + - NOTE: if you cannot configure OIDC roles, it is possible to utilize an IAM user with static access keys +2. AWS: Configure the IAM role that is assumed by the workflow, for starting/stopping runner EC2 instances + - Example OIDC assume role (trust) policy, that defines who can assume the role (see related [docs]()) + + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Federated": "arn:aws:iam:::oidc-provider/token.actions.githubusercontent.com" + }, + "Action": [ + "sts:AssumeRoleWithWebIdentity", + "sts:TagSession" + ], + "Condition": { + "StringLike": { + "token.actions.githubusercontent.com:sub": "repo:/:*" + } + } + } + ] + } + ``` + - Example role policy (inline or customer-managed), that defines the _minimum_ permissions needed for starting/stopping runner EC2 instances + + ```json + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ec2:RunInstances", + "ec2:TerminateInstances" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "ec2:CreateTags" + ], + "Resource": "*", + "Condition": { + "StringEquals": { + "ec2:CreateAction": "RunInstances" + } + } + } + ] + } + ``` +4. AWS: Linux runner AMI (amd64 or arm64), with the following things pre-configured: + - Non-root user to run the actions-runner service as + - [Actions-runner](https://github.com/actions/runner) v2.283.1+ and required [dependencies](https://github.com/actions/runner/blob/main/docs/start/envlinux.md) + - `git`, `docker`, `curl` and optionally `at` (if using the `auto-shutdown-at` feature) + - See e.g. for an example AMI build +5. AWS: EC2 runner launch template (defines AMI, instance type, VPC subnet, security groups, spot options etc) + - See example [Cloudformation template](https://gist.github.com/jpalomaki/003c4d173a856cf64c6d35f8869a2de8) that sets up a launch template +6. GitHub: personal access token (PAT) with `repo` scope, required for registering self-hosted repository runners ## Example workflows 💡 EC2 instance ID is automatically assigned as a unique, self-hosted runner label -⚠️ Do not simply copy these examples verbatim, but adjust action version, AWS region, launch template name etc to match your config +⚠️ Do not simply copy these examples verbatim, but adjust action version, AWS region, launch template name etc to match your configuration + +See [start/action.yml](start/action.yml) and [stop/action.yml](stop/action.yml) for all available input parameters ### Simple @@ -39,6 +100,8 @@ Simple default. Leverages ephemeral runners that are automatically deregistered ```yaml jobs: start-runner: + permissions: + id-token: write runs-on: ubuntu-20.04 steps: - id: runner @@ -46,8 +109,7 @@ jobs: uses: superblk/ec2-actions-runner/start@ with: aws-region: eu-north-1 - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-role-to-assume: arn:aws:iam:::role/ aws-launch-template: LaunchTemplateName=my-special-runner github-token: ${{ secrets.GH_PAT }} outputs: @@ -61,6 +123,8 @@ jobs: stop-runner: if: always() + permissions: + id-token: write needs: [start-runner, main] runs-on: ubuntu-20.04 steps: @@ -68,8 +132,7 @@ jobs: uses: superblk/ec2-actions-runner/stop@ with: aws-region: eu-north-1 - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-role-to-assume: arn:aws:iam:::role/ instance-id: ${{ needs.start-runner.outputs.instance-id }} ``` @@ -84,6 +147,8 @@ A more fail-safe alternative. Deregisters GitHub runner explicitly (not relying ```yaml jobs: start-runner: + permissions: + id-token: write runs-on: ubuntu-20.04 steps: - id: runner @@ -91,8 +156,7 @@ jobs: uses: superblk/ec2-actions-runner/start@ with: aws-region: eu-north-1 - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-role-to-assume: arn:aws:iam:::role/ aws-launch-template: LaunchTemplateName=my-special-runner runner-labels: ubuntu-18.04-arm64-${{ github.run_id }} github-token: ${{ secrets.GH_PAT }} @@ -114,6 +178,8 @@ jobs: stop-runner: if: always() + permissions: + id-token: write needs: [start-runner, main] runs-on: ubuntu-20.04 steps: @@ -121,8 +187,7 @@ jobs: uses: superblk/ec2-actions-runner/stop@ with: aws-region: eu-north-1 - aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} - aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-role-to-assume: arn:aws:iam:::role/ instance-id: ${{ needs.start-runner.outputs.instance-id }} runner-id: ${{ needs.start-runner.outputs.runner-id }} github-token: ${{ secrets.GH_PAT }} diff --git a/start/action.yml b/start/action.yml index 5ffe1d4..42bc358 100644 --- a/start/action.yml +++ b/start/action.yml @@ -7,13 +7,13 @@ inputs: description: AWS region, e.g. eu-west-1 required: true aws-access-key-id: - description: AWS access key ID (pass via GitHub secret) - required: true + description: AWS access key ID (pass via GitHub secret). Required only if **not** using OIDC + required: false aws-secret-access-key: - description: AWS secret access key (pass via GitHub secret) - required: true + description: AWS secret access key (pass via GitHub secret). Required only if **not** using OIDC + required: false aws-role-to-assume: - description: AWS IAM role (ARN) to assume (optional) + description: AWS IAM role (ARN) to assume. Required if using OIDC (AssumeRoleWithWebIdentity) required: false aws-launch-template: description: AWS EC2 launch template (AWS CLI format, e.g. LaunchTemplateId=lt-0abcd290751193123) diff --git a/stop/action.yml b/stop/action.yml index 2a210c0..6e28dda 100644 --- a/stop/action.yml +++ b/stop/action.yml @@ -7,13 +7,13 @@ inputs: description: AWS region, e.g. eu-west-1 required: true aws-access-key-id: - description: AWS access key ID (pass via GitHub secret) - required: true + description: AWS access key ID (pass via GitHub secret). Required only if **not** using OIDC + required: false aws-secret-access-key: - description: AWS secret access key (pass via GitHub secret) - required: true + description: AWS secret access key (pass via GitHub secret). Required only if **not** using OIDC + required: false aws-role-to-assume: - description: AWS IAM role to assume (optional) + description: AWS IAM role (ARN) to assume. Required if using OIDC (AssumeRoleWithWebIdentity) required: false github-token: description: GitHub auth token (PAT with repo scope, pass via GitHub secret). Optional if using ephemeral runners