fix(aws): scope cross-account SES credentials to EMAILS_SES_* only - #85
Conversation
…, a stranded uncertain row, and the prod client-env sessi
The SES credential scoping change replaced two boundary guards of the form !strcontains(upper(entry.name), "ACCESS_KEY") with `!contains([<the four names this module injects today>], entry.name)`. That narrows a pattern into an allowlist: a credential added later under any other access-key name passes, on exactly the task definitions whose SES-blind task roles are the reason inbound mail keeps working. Neither rewrite was needed for the scoping change — after it, the worker and migration tasks carry only EMAILS_DATABASE_URL, and the API carries EMAILS_DATABASE_URL plus EMAILS_API_SIGNING_KEY when the SES ARNs are unset, so the original patterns still hold. Restored, with a comment saying why they are patterns. Demonstrated: injecting a secret named EMAILS_S3_ACCESS_KEY_ID into the worker task fails the restored guard and passes the allowlist form. The new exact-set assertion on the API container's `secrets` block is kept — it is a genuine strengthening, and it is what catches a reintroduced unscoped pair. terraform test: 24 passed, 0 failed
Rebasing onto 1.3.2 dropped this bullet inside the released 1.3.2 section. The scoping change is not part of that release. Note for a separate fix: 1.3.2 landed with `## [Unreleased]` emptied and the ~10 entries that were sitting under it moved beneath the `## 1.3.2` heading, including a BREAKING one. Those are unrelated to 1.3.2 and are now attributed to it.
6961fac to
895e17c
Compare
|
Rebased onto One fix needed on the way: rebasing onto the new Re-verified on the new base:
Still needs the Unrelated CHANGELOG defect now on
|
…-key guards main already carries this branch's SES credential scoping (landed as #85, 0c54ce3) and then corrected the test guards in c96b196. This merge resolves both conflicts in favour of main: - deploy/aws/tests/dormant.tftest.hcl: restores !strcontains(upper(entry.name), "ACCESS_KEY") in the worker/migration guard of ses_credentials_injected_by_arn_reference_only and in the API guard of no_ses_credentials_leaves_the_task_role_alone. The 4-name allowlist this branch carried let any future credential secret under a different access-key name pass, on exactly the task definitions whose SES-blind task roles keep inbound mail working. - CHANGELOG.md: main's entry is a strict superset; no branch content is lost. The resulting tree is byte-identical to origin/main, so this branch can no longer regress the default branch.
Supersedes #82 — that PR is
CONFLICTINGagainstmain(it branched at45584a7,41 commits ago) and has never had a single CI check run. This is #82's commit
rebased onto
5bb126e, plus one correction described below.What it does
emails-prodcurrently injects both credential name pairs from Secrets Managerinto the API task:
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY— repoints the container's entireSDK default credential chain
EMAILS_SES_ACCESS_KEY_ID/EMAILS_SES_SECRET_ACCESS_KEY— the scoped names the1.3.0 sender reads
The unscoped pair existed only so the 1.2.7 image could send without a code
change. 1.3.0 is live and its boot log confirms
credentials=environment, so thepair is redundant. This drops it, returning the default chain to the API task role.
This is a privilege reduction, not new credential handling. It adds no
credential storage, no database columns, no provider secrets, and no
encryption-at-rest requirement — it removes an over-broad credential from a task
definition. Resolves item 1 of #61.
Correction on top of #82
#82 replaced two boundary guards in
deploy/aws/tests/dormant.tftest.hclof the formwith
!contains([<the four names this module injects today>], entry.name).That narrows a pattern into a name allowlist, on exactly the task
definitions whose SES-blind task roles are the reason inbound mail keeps working.
Neither rewrite was needed: after the scoping change the worker and migration tasks
carry only
EMAILS_DATABASE_URL, and the API carriesEMAILS_DATABASE_URLplusEMAILS_API_SIGNING_KEYwhen the SES ARNs are unset — so the original patternsstill pass. They are restored here, with a comment saying why they are patterns.
Demonstrated, not asserted — injecting a secret named
EMAILS_S3_ACCESS_KEY_IDinto the worker task:
SES credentials are API-only…)#82's new exact-set assertion on the API container's
secretsblock is kept —that one is a genuine strengthening, and it is what catches a reintroduced unscoped
pair. The TypeScript test change is also kept as-is: it splits the constant into
scoped/generic and asserts the generic names are absent, which strengthens the
guard.
Verification
terraform init -backend=false+terraform validate: passterraform test: 24 passed, 0 failedAWS_ACCESS_KEY_IDintoses_credential_secretsfails 2 assertions (the exact-set assertion and the generic-name assertion), so
the guards are live rather than vacuous
EMAILS_S3_ACCESS_KEY_IDon the worker fails the restoredpattern guard (see table above)
HOME,env -uall cloud/AWS creds,EMAILS_MODE=local):2410 pass / 0 fail / 137 skip — identical to
mainNo version bump and no publish.
Before merging: this needs a
[BREAKING]heads-upApplying this module removes the unscoped pair, so any deployed image older than
1.3.0 loses SES sending — those images rely on the default credential chain.
variables.tfnow states the new requirement: when the cross-account SES secretARN inputs are set, the image must implement the scoped
EMAILS_SES_*contract.That is deployment-affecting for third-party self-hosters, so per the operating
rules it wants a
[BREAKING]post to#announcements(what, blast radius, when,rollback) before this merges. Flagging rather than merging.
Scope note
#82's title covers three follow-ups from #61 — credential scoping, a
stranded
uncertainsend intent, and the prod client-env session token. Only thefirst is in the diff. The stranded-intent item is an evidence investigation with
no code change; the client-env token item is not present here. Neither is a blocker
for this change, but #61 should not be closed on this PR alone.
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.