Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
736a854
fix(scale): Refactor Runner Type and Owner (#871)
mcaulifn Jun 15, 2021
6e35845
feat: support multiple instance types (#898)
mcaulifn Jun 16, 2021
a768687
docs: fix lambda_security_group_ids incorrect description #738 (#902)
npalm Jun 17, 2021
5b5ee3b
fix: scale down runners (#905)
npalm Jun 17, 2021
8746249
chore: group upgrade lambda dependencies (#906)
npalm Jun 17, 2021
7bda880
feat(runner): Move Lambda Vars to Parameter Store
mcaulifn Jun 23, 2021
333eae7
Add test for ssm module (#1)
mcaulifn Jun 25, 2021
65535fb
Removing KMS/GH Auth from scale-down
mcaulifn Jun 25, 2021
b460a1a
Merge branch 'mcaulifn/ssm' of https://github.com/mcaulifn/terraform-…
mcaulifn Jun 25, 2021
aecc383
Add SSM permissions to runner policy
mcaulifn Jun 30, 2021
c84ae6c
Allow custom key_id
mcaulifn Jun 30, 2021
d2b61f1
Fixing for loop
mcaulifn Jun 30, 2021
8f34d71
Move SSM policy to Lambdas
mcaulifn Jul 1, 2021
7d44ac8
Fixing function call
mcaulifn Jul 1, 2021
66bd078
chore: Bump aws-sdk (#752) (#909)
dependabot[bot] Jun 18, 2021
77c8e13
chore: Bump aws-sdk (#752) (#908)
dependabot[bot] Jun 18, 2021
fe0a126
chore: Bump aws-sdk (#752) (#887)
dependabot[bot] Jun 18, 2021
174b4ca
chore: Bump aws-sdk (#752) (#885)
dependabot[bot] Jun 18, 2021
0f1493f
chore: Bump aws-sdk (#752) (#889)
dependabot[bot] Jun 18, 2021
5314469
chore: Bump aws-sdk (#752) (#892)
dependabot[bot] Jun 18, 2021
640ef5f
chore: Bump aws-sdk (#752) (#907)
dependabot[bot] Jun 18, 2021
1658635
chore: Bump aws-sdk (#752) (#864)
dependabot[bot] Jun 18, 2021
a401c0d
chore: Bump eslint in /modules/webhook/lambdas/webhook (#918)
dependabot[bot] Jun 24, 2021
870280b
chore: Bump typescript (#929)
dependabot[bot] Jun 24, 2021
0fdf8cf
chore: Bump @typescript-eslint/eslint-plugin (#928)
dependabot[bot] Jun 24, 2021
a6b9a29
chore: Bump typescript in /modules/webhook/lambdas/webhook (#926)
dependabot[bot] Jun 24, 2021
b5096bb
feat: Added support for white listing of repositories (#915)
ravenolf Jul 7, 2021
7ac1a25
Need `,` after list item
mcaulifn Jul 7, 2021
cbf7a70
Move Lambda Policy to data resource
mcaulifn Jul 7, 2021
d8e6fbd
Merge branch 'develop' into mcaulifn/ssm
mcaulifn Jul 7, 2021
d46f9e2
Merge branch 'develop' into mcaulifn/ssm
mcaulifn Jul 14, 2021
0c9aff5
Addressing PR comments, fixing lint
mcaulifn Jul 16, 2021
3104d34
Refactoring Parameters to SSM Module
mcaulifn Jul 27, 2021
7c09270
Merge branch 'develop' into mcaulifn/ssm
mcaulifn Jul 27, 2021
4ea2a17
Fixing rebase
mcaulifn Jul 27, 2021
19199e3
Using only key ARN as input value
mcaulifn Jul 29, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Scaling down the runners is at the moment brute-forced, every configurable amoun

Downloading the GitHub Action Runner distribution can be occasionally slow (more than 10 minutes). Therefore a lambda is introduced that synchronizes the action runner binary from GitHub to an S3 bucket. The EC2 instance will fetch the distribution from the S3 bucket instead of the internet.

Secrets and private keys which are passed to the lambdas as environment variables are encrypted by default by a KMS key managed by the module. Alternatively you can pass your own KMS key. Encryption via KMS can be complete disabled by setting `encrypt_secrets` to `false`.
Secrets and private keys are stored in SSM Parameter Store. These values are encrypted using the default KMS key for SSM or passing in a custom KMS key.

![Architecture](docs/component-overview.svg)

Expand Down Expand Up @@ -325,6 +325,7 @@ No requirements.
|------|--------|---------|
| runner_binaries | ./modules/runner-binaries-syncer | |
| runners | ./modules/runners | |
| ssm | ./modules/ssm | |
| webhook | ./modules/webhook | |

## Resources
Expand All @@ -334,7 +335,6 @@ No requirements.
| [aws_kms_key](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/kms_key) |
| [aws_resourcegroups_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/resourcegroups_group) |
| [aws_sqs_queue](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) |
| [aws_ssm_parameter](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) |
| [random_string](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) |

## Inputs
Expand Down Expand Up @@ -405,6 +405,7 @@ No requirements.
|------|-------------|
| binaries\_syncer | n/a |
| runners | n/a |
| ssm\_parameters | n/a |
| webhook | n/a |
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->

Expand Down
19 changes: 14 additions & 5 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ locals {
kms_key_arn = var.kms_key_id != null ? data.aws_kms_key.cmk[0].arn : null

github_app_parameters = {
client_id_arn = aws_ssm_parameter.github_app_client_id.arn
client_secret_arn = aws_ssm_parameter.github_app_client_secret.arn
id_arn = aws_ssm_parameter.github_app_id.arn
key_base64_arn = aws_ssm_parameter.github_app_key_base64.arn
client_id = module.ssm.parameters.github_app_client_id
client_secret = module.ssm.parameters.github_app_client_secret
id = module.ssm.parameters.github_app_id
key_base64 = module.ssm.parameters.github_app_key_base64
}
}

Expand All @@ -35,6 +35,15 @@ resource "aws_sqs_queue" "queued_builds" {
tags = var.tags
}

module "ssm" {
source = "./modules/ssm"

kms_key_arn = local.kms_key_arn
environment = var.environment
github_app = var.github_app
tags = local.tags
}

module "webhook" {
source = "./modules/webhook"

Expand All @@ -44,7 +53,7 @@ module "webhook" {
kms_key_arn = local.kms_key_arn

sqs_build_queue = aws_sqs_queue.queued_builds
github_app_webhook_secret_arn = aws_ssm_parameter.github_app_webhook_secret.arn
github_app_webhook_secret_arn = module.ssm.parameters.github_app_webhook_secret.arn

lambda_s3_bucket = var.lambda_s3_bucket
webhook_lambda_s3_key = var.webhook_lambda_s3_key
Expand Down
2 changes: 1 addition & 1 deletion modules/runners/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ No Modules.
| enable\_ssm\_on\_runners | Enable to allow access the runner instances for debugging purposes via SSM. Note that this adds additional permissions to the runner instances. | `bool` | n/a | yes |
| environment | A name that identifies the environment, used as prefix and for tagging. | `string` | n/a | yes |
| ghes\_url | GitHub Enterprise Server URL. DO NOT SET IF USING PUBLIC GITHUB | `string` | `null` | no |
| github\_app\_parameters | Parameter Store ARNs for GitHub App Parameters. | <pre>object({<br> key_base64_arn = string<br> id_arn = string<br> client_id_arn = string<br> client_secret_arn = string<br> })</pre> | n/a | yes |
| github\_app\_parameters | Parameter Store for GitHub App Parameters. | <pre>object({<br> key_base64 = map(string)<br> id = map(string)<br> client_id = map(string)<br> client_secret = map(string)<br> })</pre> | n/a | yes |
| idle\_config | List of time period that can be defined as cron expression to keep a minimum amount of runners active instead of scaling down to 0. By defining this list you can ensure that in time periods that match the cron expression within 5 seconds a runner is kept idle. | <pre>list(object({<br> cron = string<br> timeZone = string<br> idleCount = number<br> }))</pre> | `[]` | no |
| instance\_profile\_path | The path that will be added to the instance\_profile, if not set the environment name will be used. | `string` | `null` | no |
| instance\_type | [DEPRECATED] See instance\_types. | `string` | `"m5.large"` | no |
Expand Down
10 changes: 5 additions & 5 deletions modules/runners/lambdas/runners/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@
"devDependencies": {
"@types/aws-lambda": "^8.10.75",
"@types/express": "^4.17.11",
"@types/jest": "^26.0.20",
"@types/jest": "^26.0.24",
"@typescript-eslint/eslint-plugin": "^4.17.0",
"@typescript-eslint/parser": "^4.22.0",
"@vercel/ncc": "^0.29.0",
"eslint": "^7.22.0",
"eslint-plugin-prettier": "3.4.0",
"jest": "^26.6.3",
"jest": "27.0.6",
"jest-mock-extended": "^1.0.13",
"moment-timezone": "^0.5.33",
"nock": "^13.0.11",
"prettier": "2.3.1",
"ts-jest": "^26.5.5",
"prettier": "2.3.2",
"ts-jest": "^27.0.4",
"ts-node": "^10.1.0",
"ts-node-dev": "^1.1.6"
},
Expand All @@ -40,7 +40,7 @@
"@octokit/types": "^6.13.0",
"@types/aws-lambda": "^8.10.75",
"@types/express": "^4.17.11",
"@types/node": "^15.12.2",
"@types/node": "^16.4.3",
"aws-sdk": "^2.888.0",
"cron-parser": "^3.3.0",
"typescript": "^4.2.3",
Expand Down
6 changes: 2 additions & 4 deletions modules/runners/lambdas/runners/src/lambda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { scaleUp as scaleUpAction } from './scale-runners/scale-up';
import { scaleDown as scaleDownAction } from './scale-runners/scale-down';
import { SQSEvent, ScheduledEvent, Context } from 'aws-lambda';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
module.exports.scaleUp = async (event: SQSEvent, context: Context, callback: any) => {
export const scaleUp = async (event: SQSEvent, context: Context, callback: any): Promise<void> => {
console.dir(event, { depth: 5 });
try {
for (const e of event.Records) {
Expand All @@ -17,8 +16,7 @@ module.exports.scaleUp = async (event: SQSEvent, context: Context, callback: any
}
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
module.exports.scaleDown = async (event: ScheduledEvent, context: Context, callback: any) => {
export const scaleDown = async (event: ScheduledEvent, context: Context, callback: any): Promise<void> => {
try {
scaleDownAction();
callback(null);
Expand Down
8 changes: 8 additions & 0 deletions modules/runners/lambdas/runners/src/local-down.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { scaleDown } from './scale-runners/scale-down';


export function run(): void {
scaleDown();
}

run();
32 changes: 32 additions & 0 deletions modules/runners/lambdas/runners/src/local.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { scaleUp } from './scale-runners/scale-up';

const sqsEvent = {
Records: [
{
messageId: 'e8d74d08-644e-42ca-bf82-a67daa6c4dad',
// eslint-disable-next-line max-len
receiptHandle: 'AQEBCpLYzDEKq4aKSJyFQCkJduSKZef8SJVOperbYyNhXqqnpFG5k74WygVAJ4O0+9nybRyeOFThvITOaS21/jeHiI5fgaM9YKuI0oGYeWCIzPQsluW5CMDmtvqv1aA8sXQ5n2x0L9MJkzgdIHTC3YWBFLQ2AxSveOyIHwW+cHLIFCAcZlOaaf0YtaLfGHGkAC4IfycmaijV8NSlzYgDuxrC9sIsWJ0bSvk5iT4ru/R4+0cjm7qZtGlc04k9xk5Fu6A+wRxMaIyiFRY+Ya19ykcevQldidmEjEWvN6CRToLgclk=',
// eslint-disable-next-line max-len
body: { "id": 19072, "repositoryName": "ErrBud", "repositoryOwner": "ActionsTest", "eventType": "check_run", "installationId": 5 },
attributes: {
ApproximateReceiveCount: '1',
SentTimestamp: '1626450047230',
SequenceNumber: '18863115285800432640',
MessageGroupId: '19072',
SenderId: 'AROA5KW7SQ6TTB3PW6WPH:cicddev-webhook',
MessageDeduplicationId: '0c458eeb87b7f6d2607301268fd3bf33dd898a49ebd888754ff7db510c4bff1e',
ApproximateFirstReceiveTimestamp: '1626450077251'
},
messageAttributes: {},
md5OfBody: '4aef3bd70526e152e86426a0938cbec6',
eventSource: 'aws:sqs',
eventSourceARN: 'arn:aws:sqs:us-west-2:916370655143:cicddev-queued-builds.fifo',
awsRegion: 'us-west-2'
}
]
};
export function run(): void {
scaleUp(sqsEvent.Records[0].eventSource, sqsEvent.Records[0].body);
}

run();
40 changes: 24 additions & 16 deletions modules/runners/lambdas/runners/src/scale-runners/gh-auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,22 @@ const ENVIRONMENT = 'dev';
const GITHUB_APP_ID = '1';
const GITHUB_APP_CLIENT_ID = '1';
const GITHUB_APP_CLIENT_SECRET = 'client_secret';
const PARAMETER_GITHUB_APP_ID_NAME = `/actions-runner/${ENVIRONMENT}/github_app_id`;
const PARAMETER_GITHUB_APP_KEY_BASE64_NAME = `/actions-runner/${ENVIRONMENT}/github_app_key_base64`;
const PARAMETER_GITHUB_APP_CLIENT_ID_NAME = `/actions-runner/${ENVIRONMENT}/github_app_client_id`;
const PARAMETER_GITHUB_APP_CLIENT_SECRET_NAME = `/actions-runner/${ENVIRONMENT}/github_app_client_secret`;

const mockedGet = mocked(getParameterValue);


beforeEach(() => {
jest.resetModules();
jest.clearAllMocks();
process.env = { ...cleanEnv };
process.env.PARAMETER_GITHUB_APP_ID_NAME = PARAMETER_GITHUB_APP_ID_NAME;
process.env.PARAMETER_GITHUB_APP_KEY_BASE64_NAME = PARAMETER_GITHUB_APP_KEY_BASE64_NAME;
process.env.PARAMETER_GITHUB_APP_CLIENT_ID_NAME = PARAMETER_GITHUB_APP_CLIENT_ID_NAME;
process.env.PARAMETER_GITHUB_APP_CLIENT_SECRET_NAME = PARAMETER_GITHUB_APP_CLIENT_SECRET_NAME;
nock.disableNetConnect();
});

Expand Down Expand Up @@ -74,13 +85,12 @@ describe('Test createGithubAuth', () => {
clientId: GITHUB_APP_CLIENT_ID,
clientSecret: GITHUB_APP_CLIENT_SECRET,
};

const mockedGet = mocked(getParameterValue);
mockedGet
.mockResolvedValueOnce(GITHUB_APP_ID)
.mockResolvedValueOnce(b64)
.mockResolvedValueOnce(GITHUB_APP_CLIENT_ID)
.mockResolvedValueOnce(GITHUB_APP_CLIENT_SECRET);

const mockedAuth = jest.fn();
mockedAuth.mockResolvedValue({ token });
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand All @@ -92,10 +102,10 @@ describe('Test createGithubAuth', () => {
const result = await createGithubAuth(installationId, authType);

// Assert
expect(getParameterValue).toBeCalledWith(ENVIRONMENT, 'github_app_id');
expect(getParameterValue).toBeCalledWith(ENVIRONMENT, 'github_app_key_base64');
expect(getParameterValue).toBeCalledWith(ENVIRONMENT, 'github_app_client_id');
expect(getParameterValue).toBeCalledWith(ENVIRONMENT, 'github_app_client_secret');
expect(getParameterValue).toBeCalledWith(PARAMETER_GITHUB_APP_ID_NAME);
expect(getParameterValue).toBeCalledWith(PARAMETER_GITHUB_APP_KEY_BASE64_NAME);
expect(getParameterValue).toBeCalledWith(PARAMETER_GITHUB_APP_CLIENT_ID_NAME);
expect(getParameterValue).toBeCalledWith(PARAMETER_GITHUB_APP_CLIENT_SECRET_NAME);

expect(mockedCreatAppAuth).toBeCalledTimes(1);
expect(mockedCreatAppAuth).toBeCalledWith(authOptions);
Expand All @@ -121,7 +131,6 @@ describe('Test createGithubAuth', () => {
request: mockedRequestInterface.defaults({ baseUrl: githubServerUrl }),
};

const mockedGet = mocked(getParameterValue);
mockedGet
.mockResolvedValueOnce(GITHUB_APP_ID)
.mockResolvedValueOnce(b64)
Expand All @@ -138,10 +147,10 @@ describe('Test createGithubAuth', () => {
const result = await createGithubAuth(installationId, authType, githubServerUrl);

// Assert
expect(getParameterValue).toBeCalledWith(ENVIRONMENT, 'github_app_id');
expect(getParameterValue).toBeCalledWith(ENVIRONMENT, 'github_app_key_base64');
expect(getParameterValue).toBeCalledWith(ENVIRONMENT, 'github_app_client_id');
expect(getParameterValue).toBeCalledWith(ENVIRONMENT, 'github_app_client_secret');
expect(getParameterValue).toBeCalledWith(PARAMETER_GITHUB_APP_ID_NAME);
expect(getParameterValue).toBeCalledWith(PARAMETER_GITHUB_APP_KEY_BASE64_NAME);
expect(getParameterValue).toBeCalledWith(PARAMETER_GITHUB_APP_CLIENT_ID_NAME);
expect(getParameterValue).toBeCalledWith(PARAMETER_GITHUB_APP_CLIENT_SECRET_NAME);

expect(mockedCreatAppAuth).toBeCalledTimes(1);
expect(mockedCreatAppAuth).toBeCalledWith(authOptions);
Expand All @@ -168,7 +177,6 @@ describe('Test createGithubAuth', () => {
request: mockedRequestInterface.defaults({ baseUrl: githubServerUrl }),
};

const mockedGet = mocked(getParameterValue);
mockedGet
.mockResolvedValueOnce(GITHUB_APP_ID)
.mockResolvedValueOnce(b64)
Expand All @@ -185,10 +193,10 @@ describe('Test createGithubAuth', () => {
const result = await createGithubAuth(installationId, authType, githubServerUrl);

// Assert
expect(getParameterValue).toBeCalledWith(ENVIRONMENT, 'github_app_id');
expect(getParameterValue).toBeCalledWith(ENVIRONMENT, 'github_app_key_base64');
expect(getParameterValue).toBeCalledWith(ENVIRONMENT, 'github_app_client_id');
expect(getParameterValue).toBeCalledWith(ENVIRONMENT, 'github_app_client_secret');
expect(getParameterValue).toBeCalledWith(PARAMETER_GITHUB_APP_ID_NAME);
expect(getParameterValue).toBeCalledWith(PARAMETER_GITHUB_APP_KEY_BASE64_NAME);
expect(getParameterValue).toBeCalledWith(PARAMETER_GITHUB_APP_CLIENT_ID_NAME);
expect(getParameterValue).toBeCalledWith(PARAMETER_GITHUB_APP_CLIENT_SECRET_NAME);

expect(mockedCreatAppAuth).toBeCalledTimes(1);
expect(mockedCreatAppAuth).toBeCalledWith(authOptions);
Expand Down
12 changes: 7 additions & 5 deletions modules/runners/lambdas/runners/src/scale-runners/gh-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ export async function createGithubAuth(
authType: 'app' | 'installation',
ghesApiUrl = '',
): Promise<AppAuthentication> {
const environment = process.env.ENVIRONMENT as string;

let authOptions: StrategyOptions = {
appId: parseInt(await getParameterValue(environment, 'github_app_id')),
privateKey: Buffer.from(await getParameterValue(environment, 'github_app_key_base64'), 'base64').toString(),
clientId: await getParameterValue(environment, 'github_app_client_id'),
clientSecret: await getParameterValue(environment, 'github_app_client_secret'),
appId: parseInt(await getParameterValue(process.env.PARAMETER_GITHUB_APP_ID_NAME)),
privateKey: Buffer.from(
await getParameterValue(process.env.PARAMETER_GITHUB_APP_KEY_BASE64_NAME),
'base64')
.toString(),
clientId: await getParameterValue(process.env.PARAMETER_GITHUB_APP_CLIENT_ID_NAME),
clientSecret: await getParameterValue(process.env.PARAMETER_GITHUB_APP_CLIENT_SECRET_NAME),
};
if (installationId) authOptions = { ...authOptions, installationId };

Expand Down
15 changes: 15 additions & 0 deletions modules/runners/lambdas/runners/src/scale-runners/modules.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
declare namespace NodeJS {
export interface ProcessEnv {
ENVIRONMENT: string
SUBNET_IDS: string
GHES_URL: string
SCALE_DOWN_CONFIG: string
MINIMUM_RUNNING_TIME_IN_MINUTES: string
LAUNCH_TEMPLATE_NAME: string
AWS_REGION: string
PARAMETER_GITHUB_APP_CLIENT_ID_NAME: string
PARAMETER_GITHUB_APP_CLIENT_SECRET_NAME: string
PARAMETER_GITHUB_APP_ID_NAME: string
PARAMETER_GITHUB_APP_KEY_BASE64_NAME: string
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,6 @@ function getInstanceParams(
}

function getSubnet(): string {
const subnets = (process.env.SUBNET_IDS as string).split(',');
const subnets = (process.env.SUBNET_IDS).split(',');
return subnets[Math.floor(Math.random() * subnets.length)];
}
Loading