diff --git a/Dockerfile b/Dockerfile index f6f0228f111..b8aa460b093 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,6 @@ ARG RUBY_VERSION ARG NODE_VERSION +ARG CHAMBER_VERSION=2.13.4 ### dev @@ -56,6 +57,7 @@ FROM ${TARGETARCH}_jemalloc as production ARG NODE_VERSION ARG TARGETARCH ARG REVISION +ARG CHAMBER_VERSION ENV RAILS_ENV production ENV NODE_ENV production @@ -82,8 +84,14 @@ RUN curl -L https://fly.io/install.sh | sh ENV FLYCTL_INSTALL="/root/.fly" ENV PATH="$FLYCTL_INSTALL/bin:$PATH" +# Set up chamber for SSM-based secrets management +RUN curl -fL "https://github.com/segmentio/chamber/releases/download/v${CHAMBER_VERSION}/chamber-v${CHAMBER_VERSION}-linux-${TARGETARCH}" \ + -o /usr/local/bin/chamber \ + && chmod +x /usr/local/bin/chamber + COPY --from=build /usr/local/bundle /usr/local/bundle COPY --from=build --chown=www /usr/src/intercode /usr/src/intercode +RUN chmod +x /usr/src/intercode/bin/entrypoint.sh # The following two lines are to enable heroku exec support: https://devcenter.heroku.com/articles/exec#using-with-docker ADD ./.profile.d /app/.profile.d @@ -93,4 +101,5 @@ WORKDIR /usr/src/intercode USER www ENV PATH=/opt/node/bin:$PATH -CMD bundle exec bin/rails server -p $PORT -b 0.0.0.0 +ENTRYPOINT ["/usr/src/intercode/bin/entrypoint.sh"] +CMD ["bundle", "exec", "bin/rails", "server", "-p", "3000", "-b", "0.0.0.0"] diff --git a/bin/entrypoint.sh b/bin/entrypoint.sh new file mode 100755 index 00000000000..ec67cca15c6 --- /dev/null +++ b/bin/entrypoint.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -e + +if [ -n "$CHAMBER_SERVICE" ]; then + exec chamber exec "$CHAMBER_SERVICE" -- "$@" +else + exec "$@" +fi diff --git a/fly.toml b/fly.toml index 37f4aae28a5..2ef77e58806 100644 --- a/fly.toml +++ b/fly.toml @@ -15,6 +15,7 @@ release_command = "bundle exec rails release:perform --trace" strategy = "bluegreen" [env] +CHAMBER_SERVICE = "intercode_production" ASSETS_HOST = "assets.neilhosting.net" AUTOSCALE_MIN_INSTANCES = "2" AUTOSCALE_MAX_INSTANCES = "10" diff --git a/terraform/modules/intercode_aws_resources/iam.tf b/terraform/modules/intercode_aws_resources/iam.tf index 8884999f1dc..df8a1c6d6bf 100644 --- a/terraform/modules/intercode_aws_resources/iam.tf +++ b/terraform/modules/intercode_aws_resources/iam.tf @@ -77,6 +77,16 @@ resource "aws_iam_group_policy" "this" { Action = ["events:PutRule", "events:PutTargets"] Resource = "*" }, + { + Sid = "SsmParameterAccess" + Effect = "Allow" + Action = [ + "ssm:GetParametersByPath", + "ssm:GetParameter", + "ssm:GetParameters", + ] + Resource = "arn:aws:ssm:${local.region}:${local.account_id}:parameter/${var.name}/*" + }, ] }) } diff --git a/terraform/modules/intercode_aws_resources/outputs.tf b/terraform/modules/intercode_aws_resources/outputs.tf index b96d91ede88..cff1f01a998 100644 --- a/terraform/modules/intercode_aws_resources/outputs.tf +++ b/terraform/modules/intercode_aws_resources/outputs.tf @@ -48,3 +48,13 @@ output "iam_access_key_secret" { value = aws_iam_access_key.this.secret sensitive = true } + +output "chamber_service" { + description = "Value to set as CHAMBER_SERVICE in the app's environment. Chamber will load all SSM parameters under this path prefix at boot." + value = var.name +} + +output "ssm_path_prefix" { + description = "SSM Parameter Store path prefix where app secrets are stored (e.g. for use with aws ssm put-parameter for manually-managed secrets)." + value = "/${var.name}" +} diff --git a/terraform/modules/intercode_aws_resources/ssm.tf b/terraform/modules/intercode_aws_resources/ssm.tf new file mode 100644 index 00000000000..9ca2916c427 --- /dev/null +++ b/terraform/modules/intercode_aws_resources/ssm.tf @@ -0,0 +1,35 @@ +locals { + ssm_path_prefix = "/${var.name}" +} + +resource "aws_ssm_parameter" "aws_access_key_id" { + name = "${local.ssm_path_prefix}/AWS_ACCESS_KEY_ID" + type = "SecureString" + value = aws_iam_access_key.this.id +} + +resource "aws_ssm_parameter" "aws_secret_access_key" { + name = "${local.ssm_path_prefix}/AWS_SECRET_ACCESS_KEY" + type = "SecureString" + value = aws_iam_access_key.this.secret +} + +resource "aws_ssm_parameter" "aws_s3_bucket" { + name = "${local.ssm_path_prefix}/AWS_S3_BUCKET" + type = "String" + value = aws_s3_bucket.uploads.bucket +} + +resource "aws_ssm_parameter" "aws_region" { + name = "${local.ssm_path_prefix}/AWS_REGION" + type = "String" + value = local.region +} + +resource "aws_ssm_parameter" "secrets" { + for_each = var.secrets + + name = "${local.ssm_path_prefix}/${each.key}" + type = "SecureString" + value = each.value +} diff --git a/terraform/modules/intercode_aws_resources/variables.tf b/terraform/modules/intercode_aws_resources/variables.tf index dadcbee29f0..9421a1f5696 100644 --- a/terraform/modules/intercode_aws_resources/variables.tf +++ b/terraform/modules/intercode_aws_resources/variables.tf @@ -13,3 +13,10 @@ variable "alarm_email_destinations" { type = set(string) default = [] } + +variable "secrets" { + description = "Map of secret name to value for manually-managed secrets (e.g. DATABASE_URL, SECRET_KEY_BASE). Written to SSM Parameter Store as SecureString and loaded by chamber at app boot." + type = map(string) + sensitive = true + default = {} +}