diff --git a/README.md b/README.md index 04f79dc..7790cd2 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,19 @@ # ec2-actions-runner +Composite actions for managing an on-demand, self-hosted GitHub actions _repository_ runner (Linux on EC2) + ⚠️ 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). +⚠️ Self-hosted runners should **not** be used with _public_ repositories (see GitHub [documentation](https://docs.github.com/en/actions/hosting-your-own-runners/about-self-hosted-runners#self-hosted-runner-security)) Inspired by ❤️ ## Pre-requisites - AWS account - - Permissions to provision IAM, EC2 and VPC resources (to set up the runner scaffolding) + - Permissions to provision IAM, EC2 and VPC resources (to set up the runner AWS scaffolding) - VPC network - - Subnet with Internet access (required because self-hosted runners communicate with github.com) + - Subnet with Internet access (required because self-hosted runners communicate with `github.com`) ## Limitations @@ -20,10 +22,10 @@ Inspired by ❤️ ## 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]()) + - Use of OIDC is recommended (safer), because static AWS access keys need not be stored in GitHub secrets + - NOTE: if you cannot configure OIDC-assumable roles, it is possible to use an IAM user with static access keys +2. AWS: Configure the IAM role that is assumed by the workflow, used only _for starting and stopping the runner EC2 instances_ + - Example OIDC assume role (trust) policy, that defines which GitHub repos can assume the role (see example [CloudFormation template](https://github.com/aws-actions/configure-aws-credentials#sample-iam-role-cloudformation-template)) ```json { @@ -51,51 +53,85 @@ Inspired by ❤️ ```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" - } - } + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "ec2:RunInstances", + "ec2:TerminateInstances" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "ec2:CreateTags" + ], + "Resource": "*", + "Condition": { + "StringEquals": { + "ec2:CreateAction": "RunInstances" } - ] + } + } + ] + } + ``` + - If you need to assign an IAM instance profile (role) to the EC2 instances, you need to use a policy that includes the `iam:PassRole` permission + + ```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" + } + } + }, + { + "Effect": "Allow", + "Action": "iam:PassRole", + "Resource": "arn:aws:iam:::role/" + } + ] } ``` 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 e.g. for an example +5. AWS: EC2 runner launch template (defines AMI, instance type, VPC subnet, security groups, instance profile, 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 +See [start/action.yml](start/action.yml) and [stop/action.yml](stop/action.yml) for all available input parameters + 💡 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 configuration -See [start/action.yml](start/action.yml) and [stop/action.yml](stop/action.yml) for all available input parameters - ### Simple -Simple default. Leverages ephemeral runners that are automatically deregistered from GitHub after the `main` job has run. +Leverages ephemeral runners that are automatically deregistered from GitHub after the `main` job has run to completion ```yaml jobs: @@ -138,11 +174,11 @@ jobs: ### Advanced -A more fail-safe alternative. Deregisters GitHub runner explicitly (not relying on ephemeral runner auto-deregistration behavior alone). Also leverages EC2 [instance-initiated shutdown](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/terminating-instances.html#Using_ChangingInstanceInitiatedShutdownBehavior) **terminate** behavior for ensuring the EC2 instance is terminated, even if the `stop-runner` job fails to run. +A more fail-safe alternative. Deregisters GitHub runner explicitly (not relying on ephemeral runner auto-deregistration behavior alone). Also leverages EC2 [instance-initiated shutdown](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/terminating-instances.html#Using_ChangingInstanceInitiatedShutdownBehavior) terminate behavior for ensuring the EC2 instance is terminated, even if the `stop-runner` job fails to run -⚠️ For the automatic dead-man's switch termination to work, the AMI must include the `at` tool, and the EC2 launch template must specify instance-initiated shutdown behavior as **terminate**. +💡 This example also illustrates the use of extra runner labels and a matrix `main` job, that uses both GitHub-hosted and self-hosted runners -💡 This example also illustrates the use of extra runner labels and a matrix `main` job, that uses both GitHub-hosted and self-hosted runners. +⚠️ For the automatic dead-man's switch termination to work, the AMI must include the `at` tool, and the EC2 launch template must specify instance-initiated shutdown behavior as _terminate_ ```yaml jobs: diff --git a/start/action.yml b/start/action.yml index 42bc358..2bdbecb 100644 --- a/start/action.yml +++ b/start/action.yml @@ -13,7 +13,7 @@ inputs: 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. Required if using OIDC (AssumeRoleWithWebIdentity) + description: AWS IAM role (ARN) to assume, for launching the instance. 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 6e28dda..ba7775f 100644 --- a/stop/action.yml +++ b/stop/action.yml @@ -13,7 +13,7 @@ inputs: 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. Required if using OIDC (AssumeRoleWithWebIdentity) + description: AWS IAM role (ARN) to assume, for terminating the instance. 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