Skip to content

Commit 988fb9c

Browse files
nbudinclaude
andcommitted
Add chamber-based SSM secret loading at container boot
Adds an entrypoint script that conditionally runs chamber (Segment's SSM Parameter Store injector) before starting the app. If CHAMBER_SERVICE is set, all SSM parameters under that path are loaded as environment variables at boot; otherwise the app starts normally. This lets us manage secrets that are sourced from Terraform outputs (AWS credentials, S3 bucket, etc.) without manual copy-paste, while keeping the container bootable without AWS for operators running their own instances. Extends the intercode_aws_resources Terraform module to write the Terraform-derived secrets to SSM automatically, add a `secrets` variable for manually-managed secrets, and grant the app's IAM user permission to read from its own SSM path. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2c60067 commit 988fb9c

7 files changed

Lines changed: 81 additions & 1 deletion

File tree

Dockerfile

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
ARG RUBY_VERSION
22
ARG NODE_VERSION
3+
ARG CHAMBER_VERSION=2.13.4
34

45
### dev
56

@@ -56,6 +57,7 @@ FROM ${TARGETARCH}_jemalloc as production
5657
ARG NODE_VERSION
5758
ARG TARGETARCH
5859
ARG REVISION
60+
ARG CHAMBER_VERSION
5961

6062
ENV RAILS_ENV production
6163
ENV NODE_ENV production
@@ -82,8 +84,14 @@ RUN curl -L https://fly.io/install.sh | sh
8284
ENV FLYCTL_INSTALL="/root/.fly"
8385
ENV PATH="$FLYCTL_INSTALL/bin:$PATH"
8486

87+
# Set up chamber for SSM-based secrets management
88+
RUN curl -fL "https://github.com/segmentio/chamber/releases/download/v${CHAMBER_VERSION}/chamber-v${CHAMBER_VERSION}-linux-${TARGETARCH}" \
89+
-o /usr/local/bin/chamber \
90+
&& chmod +x /usr/local/bin/chamber
91+
8592
COPY --from=build /usr/local/bundle /usr/local/bundle
8693
COPY --from=build --chown=www /usr/src/intercode /usr/src/intercode
94+
RUN chmod +x /usr/src/intercode/bin/entrypoint.sh
8795

8896
# The following two lines are to enable heroku exec support: https://devcenter.heroku.com/articles/exec#using-with-docker
8997
ADD ./.profile.d /app/.profile.d
@@ -93,4 +101,5 @@ WORKDIR /usr/src/intercode
93101

94102
USER www
95103
ENV PATH=/opt/node/bin:$PATH
96-
CMD bundle exec bin/rails server -p $PORT -b 0.0.0.0
104+
ENTRYPOINT ["/usr/src/intercode/bin/entrypoint.sh"]
105+
CMD ["bundle", "exec", "bin/rails", "server", "-p", "3000", "-b", "0.0.0.0"]

bin/entrypoint.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/bash
2+
set -e
3+
4+
if [ -n "$CHAMBER_SERVICE" ]; then
5+
exec chamber exec "$CHAMBER_SERVICE" -- "$@"
6+
else
7+
exec "$@"
8+
fi

fly.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ release_command = "bundle exec rails release:perform --trace"
1515
strategy = "bluegreen"
1616

1717
[env]
18+
CHAMBER_SERVICE = "intercode_production"
1819
ASSETS_HOST = "assets.neilhosting.net"
1920
AUTOSCALE_MIN_INSTANCES = "2"
2021
AUTOSCALE_MAX_INSTANCES = "10"

terraform/modules/intercode_aws_resources/iam.tf

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,16 @@ resource "aws_iam_group_policy" "this" {
7777
Action = ["events:PutRule", "events:PutTargets"]
7878
Resource = "*"
7979
},
80+
{
81+
Sid = "SsmParameterAccess"
82+
Effect = "Allow"
83+
Action = [
84+
"ssm:GetParametersByPath",
85+
"ssm:GetParameter",
86+
"ssm:GetParameters",
87+
]
88+
Resource = "arn:aws:ssm:${local.region}:${local.account_id}:parameter/${var.name}/*"
89+
},
8090
]
8191
})
8292
}

terraform/modules/intercode_aws_resources/outputs.tf

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,13 @@ output "iam_access_key_secret" {
4848
value = aws_iam_access_key.this.secret
4949
sensitive = true
5050
}
51+
52+
output "chamber_service" {
53+
description = "Value to set as CHAMBER_SERVICE in the app's environment. Chamber will load all SSM parameters under this path prefix at boot."
54+
value = var.name
55+
}
56+
57+
output "ssm_path_prefix" {
58+
description = "SSM Parameter Store path prefix where app secrets are stored (e.g. for use with aws ssm put-parameter for manually-managed secrets)."
59+
value = "/${var.name}"
60+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
locals {
2+
ssm_path_prefix = "/${var.name}"
3+
}
4+
5+
resource "aws_ssm_parameter" "aws_access_key_id" {
6+
name = "${local.ssm_path_prefix}/AWS_ACCESS_KEY_ID"
7+
type = "SecureString"
8+
value = aws_iam_access_key.this.id
9+
}
10+
11+
resource "aws_ssm_parameter" "aws_secret_access_key" {
12+
name = "${local.ssm_path_prefix}/AWS_SECRET_ACCESS_KEY"
13+
type = "SecureString"
14+
value = aws_iam_access_key.this.secret
15+
}
16+
17+
resource "aws_ssm_parameter" "aws_s3_bucket" {
18+
name = "${local.ssm_path_prefix}/AWS_S3_BUCKET"
19+
type = "String"
20+
value = aws_s3_bucket.uploads.bucket
21+
}
22+
23+
resource "aws_ssm_parameter" "aws_region" {
24+
name = "${local.ssm_path_prefix}/AWS_REGION"
25+
type = "String"
26+
value = local.region
27+
}
28+
29+
resource "aws_ssm_parameter" "secrets" {
30+
for_each = var.secrets
31+
32+
name = "${local.ssm_path_prefix}/${each.key}"
33+
type = "SecureString"
34+
value = each.value
35+
}

terraform/modules/intercode_aws_resources/variables.tf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,10 @@ variable "alarm_email_destinations" {
1313
type = set(string)
1414
default = []
1515
}
16+
17+
variable "secrets" {
18+
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."
19+
type = map(string)
20+
sensitive = true
21+
default = {}
22+
}

0 commit comments

Comments
 (0)