From 1252ffcf108ae66c9ff693d736df3f1b9a5cd2bf Mon Sep 17 00:00:00 2001 From: Stijn Seghers Date: Thu, 22 Apr 2021 14:44:52 +1200 Subject: [PATCH] feat: use SSM Parameter Store to store secrets --- modules/runners/encrypt.tf | 24 ----------- modules/runners/lambdas/runners/src/lambda.ts | 16 ++++---- .../runners/src/scale-runners/gh-auth.ts | 32 ++++++++------- .../runners/src/scale-runners/kms/index.ts | 25 ----------- .../lambdas/runners/src/scale-runners/ssm.ts | 15 +++++++ .../instance-ssm-parameters-policy.json | 6 +-- modules/runners/scale-down.tf | 35 ++++++++-------- modules/runners/scale-up.tf | 41 ++++++++++--------- modules/runners/variables.tf | 10 ++--- variables.tf | 12 +++--- 10 files changed, 91 insertions(+), 125 deletions(-) delete mode 100644 modules/runners/encrypt.tf delete mode 100644 modules/runners/lambdas/runners/src/scale-runners/kms/index.ts create mode 100644 modules/runners/lambdas/runners/src/scale-runners/ssm.ts diff --git a/modules/runners/encrypt.tf b/modules/runners/encrypt.tf deleted file mode 100644 index e4cad100e1..0000000000 --- a/modules/runners/encrypt.tf +++ /dev/null @@ -1,24 +0,0 @@ -locals { - github_app_key_base64 = var.encryption.encrypt ? aws_kms_ciphertext.github_app_key_base64[0].ciphertext_blob : var.github_app.key_base64 - github_app_client_secret = var.encryption.encrypt ? aws_kms_ciphertext.github_app_client_secret[0].ciphertext_blob : var.github_app.client_secret -} - -resource "aws_kms_ciphertext" "github_app_key_base64" { - count = var.encryption.encrypt ? 1 : 0 - key_id = var.encryption.kms_key_id - plaintext = var.github_app.key_base64 - - context = { - Environment = var.environment - } -} - -resource "aws_kms_ciphertext" "github_app_client_secret" { - count = var.encryption.encrypt ? 1 : 0 - key_id = var.encryption.kms_key_id - plaintext = var.github_app.client_secret - - context = { - Environment = var.environment - } -} diff --git a/modules/runners/lambdas/runners/src/lambda.ts b/modules/runners/lambdas/runners/src/lambda.ts index 576daafce2..6a27653d27 100644 --- a/modules/runners/lambdas/runners/src/lambda.ts +++ b/modules/runners/lambdas/runners/src/lambda.ts @@ -1,26 +1,26 @@ -import { scaleUp } from './scale-runners/scale-up'; -import { scaleDown } from './scale-runners/scale-down'; +import { scaleUp as scaleUp_ } from './scale-runners/scale-up'; +import { scaleDown as scaleDown_ } from './scale-runners/scale-down'; import { SQSEvent, ScheduledEvent, Context } from 'aws-lambda'; -module.exports.scaleUp = async (event: SQSEvent, context: Context, callback: any) => { +export async function scaleUp(event: SQSEvent, context: Context, callback: any) { console.dir(event, { depth: 5 }); try { for (const e of event.Records) { - await scaleUp(e.eventSource, JSON.parse(e.body)); + await scaleUp_(e.eventSource, JSON.parse(e.body)); } return callback(null); } catch (e) { console.error(e); return callback('Failed handling SQS event'); } -}; +} -module.exports.scaleDown = async (event: ScheduledEvent, context: Context, callback: any) => { +export async function scaleDown(event: ScheduledEvent, context: Context, callback: any) { try { - scaleDown(); + scaleDown_(); return callback(null); } catch (e) { console.error(e); return callback('Failed'); } -}; +} diff --git a/modules/runners/lambdas/runners/src/scale-runners/gh-auth.ts b/modules/runners/lambdas/runners/src/scale-runners/gh-auth.ts index dda1e5cbf1..ece9c33610 100644 --- a/modules/runners/lambdas/runners/src/scale-runners/gh-auth.ts +++ b/modules/runners/lambdas/runners/src/scale-runners/gh-auth.ts @@ -3,7 +3,7 @@ import { request } from '@octokit/request'; import { createAppAuth } from '@octokit/auth-app'; import { Authentication, StrategyOptions } from '@octokit/auth-app/dist-types/types'; import { OctokitOptions } from '@octokit/core/dist-types/types'; -import { decrypt } from './kms'; +import * as ssm from './ssm'; export async function createOctoClient(token: string, ghesApiUrl = ''): Promise { const ocktokitOptions: OctokitOptions = { @@ -21,25 +21,27 @@ export async function createGithubAuth( authType: 'app' | 'installation', ghesApiUrl = '', ): Promise { - const clientSecret = await decrypt( - process.env.GITHUB_APP_CLIENT_SECRET as string, - process.env.KMS_KEY_ID as string, - process.env.ENVIRONMENT as string, + const privateKeyBase64 = await ssm.fetch_parameter( + process.env.GITHUB_APP_KEY_BASE64_PARAMETER_NAME as string ); - const privateKeyBase64 = await decrypt( - process.env.GITHUB_APP_KEY_BASE64 as string, - process.env.KMS_KEY_ID as string, - process.env.ENVIRONMENT as string, + const appIdString = await ssm.fetch_parameter( + process.env.GITHUB_APP_ID_PARAMETER_NAME as string ); - - if (clientSecret === undefined || privateKeyBase64 === undefined) { - throw Error('Cannot decrypt.'); + const clientId = await ssm.fetch_parameter( + process.env.GITHUB_APP_CLIENT_ID_PARAMETER_NAME as string + ); + const clientSecret = await ssm.fetch_parameter( + process.env.GITHUB_APP_CLIENT_SECRET_PARAMETER_NAME as string + ); + if (privateKeyBase64 === undefined + || appIdString === undefined + || clientId === undefined + || clientSecret === undefined) { + throw Error('Cannot decrypt GitHub App parameters.'); } const privateKey = Buffer.from(privateKeyBase64, 'base64').toString(); - - const appId: number = parseInt(process.env.GITHUB_APP_ID as string); - const clientId = process.env.GITHUB_APP_CLIENT_ID as string; + const appId = parseInt(appIdString); const authOptions: StrategyOptions = { appId, diff --git a/modules/runners/lambdas/runners/src/scale-runners/kms/index.ts b/modules/runners/lambdas/runners/src/scale-runners/kms/index.ts deleted file mode 100644 index 12c719a527..0000000000 --- a/modules/runners/lambdas/runners/src/scale-runners/kms/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { KMS } from 'aws-sdk'; -import AWS from 'aws-sdk'; - -AWS.config.update({ - region: process.env.AWS_REGION, -}); - -const kms = new KMS(); - -export async function decrypt(encrypted: string, key: string, environmentName: string): Promise { - let result: string | undefined = encrypted; - if (key != undefined) { - const decrypted = await kms - .decrypt({ - CiphertextBlob: Buffer.from(encrypted, 'base64'), - KeyId: key, - EncryptionContext: { - ['Environment']: environmentName, - }, - }) - .promise(); - result = decrypted.Plaintext?.toString(); - } - return result; -} diff --git a/modules/runners/lambdas/runners/src/scale-runners/ssm.ts b/modules/runners/lambdas/runners/src/scale-runners/ssm.ts new file mode 100644 index 0000000000..98c0cad06e --- /dev/null +++ b/modules/runners/lambdas/runners/src/scale-runners/ssm.ts @@ -0,0 +1,15 @@ +import AWS from 'aws-sdk'; + +AWS.config.update({ + region: process.env.AWS_REGION, +}); + +const ssm = new AWS.SSM({ apiVersion: '2014-11-06' }); + +export async function fetch_parameter(name: string): Promise { + const data = await ssm.getParameter({ + Name: name, + WithDecryption: true, + }).promise(); + return data.Parameter?.Value; +} diff --git a/modules/runners/policies/instance-ssm-parameters-policy.json b/modules/runners/policies/instance-ssm-parameters-policy.json index f2f4f11718..7d1b616be5 100644 --- a/modules/runners/policies/instance-ssm-parameters-policy.json +++ b/modules/runners/policies/instance-ssm-parameters-policy.json @@ -1,14 +1,10 @@ { "Version": "2012-10-17", "Statement": [ - { - "Effect": "Allow", - "Action": ["ssm:DeleteParameter"], - "Resource": "${arn_ssm_parameters}" - }, { "Effect": "Allow", "Action": [ + "ssm:DeleteParameter", "ssm:GetParameters", "ssm:GetParameter" ], diff --git a/modules/runners/scale-down.tf b/modules/runners/scale-down.tf index d82e3d5b0f..fcb91b043a 100644 --- a/modules/runners/scale-down.tf +++ b/modules/runners/scale-down.tf @@ -4,12 +4,6 @@ resource "aws_kms_grant" "scale_down" { key_id = var.encryption.kms_key_id grantee_principal = aws_iam_role.scale_down.arn operations = ["Decrypt"] - - constraints { - encryption_context_equals = { - Environment = var.environment - } - } } resource "aws_lambda_function" "scale_down" { @@ -27,16 +21,15 @@ resource "aws_lambda_function" "scale_down" { environment { variables = { - ENVIRONMENT = var.environment - KMS_KEY_ID = var.encryption.kms_key_id - ENABLE_ORGANIZATION_RUNNERS = var.enable_organization_runners - MINIMUM_RUNNING_TIME_IN_MINUTES = var.minimum_running_time_in_minutes - GITHUB_APP_KEY_BASE64 = local.github_app_key_base64 - GITHUB_APP_ID = var.github_app.id - GITHUB_APP_CLIENT_ID = var.github_app.client_id - GITHUB_APP_CLIENT_SECRET = local.github_app_client_secret - SCALE_DOWN_CONFIG = jsonencode(var.idle_config) - GHES_URL = var.ghes_url + ENVIRONMENT = var.environment + ENABLE_ORGANIZATION_RUNNERS = var.enable_organization_runners + MINIMUM_RUNNING_TIME_IN_MINUTES = var.minimum_running_time_in_minutes + GITHUB_APP_KEY_BASE64_PARAMETER_NAME = var.github_app.key_base64_parameter_name + GITHUB_APP_ID_PARAMETER_NAME = var.github_app.id_parameter_name + GITHUB_APP_CLIENT_ID_PARAMETER_NAME = var.github_app.client_id_parameter_name + GITHUB_APP_CLIENT_SECRET_PARAMETER_NAME = var.github_app.client_secret_parameter_name + SCALE_DOWN_CONFIG = jsonencode(var.idle_config) + GHES_URL = var.ghes_url } } @@ -88,6 +81,14 @@ resource "aws_iam_role_policy" "scale_down" { policy = templatefile("${path.module}/policies/lambda-scale-down.json", {}) } +resource "aws_iam_role_policy" "scale_down_ssm_parameters" { + name = "${var.environment}-lambda-scale-down-ssm-parameters" + role = aws_iam_role.scale_down.name + policy = templatefile("${path.module}/policies/instance-ssm-parameters-policy.json", { + arn_ssm_parameters = "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/${var.environment}-*" + }) +} + resource "aws_iam_role_policy" "scale_down_logging" { name = "${var.environment}-lambda-logging" role = aws_iam_role.scale_down.name @@ -100,4 +101,4 @@ resource "aws_iam_role_policy_attachment" "scale_down_vpc_execution_role" { count = length(var.lambda_subnet_ids) > 0 ? 1 : 0 role = aws_iam_role.scale_down.name policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole" -} \ No newline at end of file +} diff --git a/modules/runners/scale-up.tf b/modules/runners/scale-up.tf index 5418880662..adb32b111e 100644 --- a/modules/runners/scale-up.tf +++ b/modules/runners/scale-up.tf @@ -4,12 +4,6 @@ resource "aws_kms_grant" "scale_up" { key_id = var.encryption.kms_key_id grantee_principal = aws_iam_role.scale_up.arn operations = ["Decrypt"] - - constraints { - encryption_context_equals = { - Environment = var.environment - } - } } resource "aws_lambda_function" "scale_up" { @@ -28,20 +22,19 @@ resource "aws_lambda_function" "scale_up" { environment { variables = { - ENABLE_ORGANIZATION_RUNNERS = var.enable_organization_runners - ENVIRONMENT = var.environment - GHES_URL = var.ghes_url - GITHUB_APP_CLIENT_ID = var.github_app.client_id - GITHUB_APP_CLIENT_SECRET = local.github_app_client_secret - GITHUB_APP_ID = var.github_app.id - GITHUB_APP_KEY_BASE64 = local.github_app_key_base64 - KMS_KEY_ID = var.encryption.kms_key_id - RUNNER_EXTRA_LABELS = var.runner_extra_labels - RUNNER_GROUP_NAME = var.runner_group_name - RUNNERS_MAXIMUM_COUNT = var.runners_maximum_count - LAUNCH_TEMPLATE_NAME = aws_launch_template.runner.name - LAUNCH_TEMPLATE_VERSION = aws_launch_template.runner.latest_version - SUBNET_IDS = join(",", var.subnet_ids) + ENABLE_ORGANIZATION_RUNNERS = var.enable_organization_runners + ENVIRONMENT = var.environment + GHES_URL = var.ghes_url + GITHUB_APP_KEY_BASE64_PARAMETER_NAME = var.github_app.key_base64_parameter_name + GITHUB_APP_ID_PARAMETER_NAME = var.github_app.id_parameter_name + GITHUB_APP_CLIENT_ID_PARAMETER_NAME = var.github_app.client_id_parameter_name + GITHUB_APP_CLIENT_SECRET_PARAMETER_NAME = var.github_app.client_secret_parameter_name + RUNNER_EXTRA_LABELS = var.runner_extra_labels + RUNNER_GROUP_NAME = var.runner_group_name + RUNNERS_MAXIMUM_COUNT = var.runners_maximum_count + LAUNCH_TEMPLATE_NAME = aws_launch_template.runner.name + LAUNCH_TEMPLATE_VERSION = aws_launch_template.runner.latest_version + SUBNET_IDS = join(",", var.subnet_ids) } } @@ -91,6 +84,14 @@ resource "aws_iam_role_policy" "scale_up" { }) } +resource "aws_iam_role_policy" "scale_up_ssm_parameters" { + name = "${var.environment}-lambda-scale-up-ssm-parameters" + role = aws_iam_role.scale_up.name + policy = templatefile("${path.module}/policies/instance-ssm-parameters-policy.json", { + arn_ssm_parameters = "arn:aws:ssm:${var.aws_region}:${data.aws_caller_identity.current.account_id}:parameter/${var.environment}-*" + }) +} + resource "aws_iam_role_policy" "scale_up_logging" { name = "${var.environment}-lambda-logging" role = aws_iam_role.scale_up.name diff --git a/modules/runners/variables.tf b/modules/runners/variables.tf index 0abd518684..c1a752248d 100644 --- a/modules/runners/variables.tf +++ b/modules/runners/variables.tf @@ -108,12 +108,12 @@ variable "enable_organization_runners" { } variable "github_app" { - description = "GitHub app parameters, see your github app. Ensure the key is the base64-encoded `.pem` file (the output of `base64 app.private-key.pem`, not the content of `private-key.pem`)." + description = "GitHub App parameters. See your GitHub App. All values are expected to be valid SSM Parameter names, referring to existing SSM Parameters. Ensure the private key is the base64-encoding of the `.pem` file (the output of `base64 app.private-key.pem`, not the content of `private-key.pem` itself)." type = object({ - key_base64 = string - id = string - client_id = string - client_secret = string + key_base64_parameter_name = string + id_parameter_name = string + client_id_parameter_name = string + client_secret_parameter_name = string }) } diff --git a/variables.tf b/variables.tf index aeb991a192..b31b5fb6b9 100644 --- a/variables.tf +++ b/variables.tf @@ -29,13 +29,13 @@ variable "enable_organization_runners" { } variable "github_app" { - description = "GitHub app parameters, see your github app. Ensure the key is the base64-encoded `.pem` file (the output of `base64 app.private-key.pem`, not the content of `private-key.pem`)." + description = "GitHub App parameters. See your GitHub App. All values except the webhook secret are expected to be valid SSM Parameter names, referring to existing SSM Parameters. Ensure the private key is the base64-encoding of the `.pem` file (the output of `base64 app.private-key.pem`, not the content of `private-key.pem` itself)." type = object({ - key_base64 = string - id = string - client_id = string - client_secret = string - webhook_secret = string + key_base64_parameter_name = string + id_parameter_name = string + client_id_parameter_name = string + client_secret_parameter_name = string + webhook_secret = string }) }