feat(infra): add dev SSM sync and dev credential users#303
feat(infra): add dev SSM sync and dev credential users#303
Conversation
Add a dedicated dev credentials group and list-driven contributor users. Keep all existing IAM users unchanged. Made-with: Cursor
Add a sync script and package command to pull dev SSM parameters, including SecureString decryption, into .env.development.local. Made-with: Cursor
WalkthroughAdds Terraform-managed dev IAM users and a least-privilege "forge-dev-secrets" group, conditional dev SSM parameters for CMS and Web, a TypeScript script to sync SSM into .env.development.local, and npm/Turbo scripts to run the fetch workflow locally or via Turbo. Changes
Sequence DiagramsequenceDiagram
actor Dev as Developer
participant CLI as pnpm / turbo
participant Script as fetch-secrets.ts
participant AWS as AWS SSM & KMS
participant FS as Filesystem
Dev->>CLI: pnpm fetch-secrets (or app-specific)
CLI->>Script: run tsx scripts/fetch-secrets.ts --project <cms|web>
Script->>AWS: GetParametersByPath(/forge/aws/<project>/dev/*) (with Decryption, paginate)
AWS-->>Script: Parameters (may include SecureString)
Script->>AWS: KMS decrypt (handled via AWS SDK/SSM)
AWS-->>Script: Decrypted values
Script->>Script: Normalize names, detect duplicates, sort
Script->>FS: Write .env.development.local
FS-->>Script: File created/updated
Script-->>CLI: exit 0 / exit 1 (error)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
❌ Terraform plan (aws/prod) — Pre-plan step failed (format/validate/init)Changes: n/a
Run: https://github.com/JesusFilm/forge/actions/runs/22835962190 |
❌ Terraform plan (aws/stage) — Pre-plan step failed (format/validate/init)Changes: n/a
Run: https://github.com/JesusFilm/forge/actions/runs/22835962190 |
Use a shared sync script with project-specific execution for cms and web, writing each app's own .env.development.local file. Made-with: Cursor
Add per-app fetch-secrets scripts for cms and web, and wire a Turbo task so one command fetches secrets for both app projects. Made-with: Cursor
Rename the shared dev secret script and update package/app/docs commands to use fetch-secrets naming consistently. Made-with: Cursor
Migrate the shared fetch-secrets script to TypeScript and run it with tsx from app-level Turbo tasks. Made-with: Cursor
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@infra/aws/iam/users/dev_credentials_users.tf`:
- Around line 1-3: The refresh hint comment currently recommending "git shortlog
-sne --since=\"12 months ago\" --all" is incorrect because it yields author
names/emails and doesn't exclude bots; replace that single-line hint with either
(a) a short documented bash one-liner or script that enumerates the exact GitHub
handles used in the IAM usernames (including steps to map author emails/names to
GitHub handles and to filter known bot accounts), or (b) a generated-list
command that directly prints the exact "<github>-dev-credentials" handles
expected; update the comment to include the script name or the exact command and
a brief note about filtering/mapping rules so future refreshes produce valid IAM
usernames.
In `@infra/aws/README.md`:
- Around line 22-28: The README shows SSM paths users can sync that the
dev-credentials IAM group does not permit; update the examples so they only
reference the permitted path (/forge/aws/cms/stage/) or add an explicit note
that syncing web/prod paths requires a different AWS principal, and point to the
IAM policy in infra/aws/iam/groups/dev_credentials/main.tf (which currently
grants SSM/KMS access only via alias/forge-cms-stage-ssm) so readers know why
they’ll get access-denied unless using elevated credentials.
In `@scripts/sync-ssm-dev.mjs`:
- Around line 75-77: The toEnvKey function currently collapses SSM paths to
their final segment (e.g., DATABASE_URL) which allows silent collisions when
merging multiple paths; update the logic used where keys are merged (refer to
toEnvKey and the merge at the code that assigns into the env map around the
later-value-wins block) to detect duplicate env keys and fail fast: when
computing key = toEnvKey(parameterName), check if that key already exists and if
so throw an error that includes both source parameter names; alternatively
provide a namespacing option (e.g., include parent path segment) behind a
flag—implement the collision check and error throw in the merge code so
duplicates are rejected unless an explicit namespace option is enabled.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 883910a1-ebca-4459-8c7b-8c097ce059e7
📒 Files selected for processing (7)
infra/aws/README.mdinfra/aws/iam/groups/dev_credentials/main.tfinfra/aws/iam/groups/dev_credentials/variables.tfinfra/aws/iam/groups/main.tfinfra/aws/iam/users/dev_credentials_users.tfpackage.jsonscripts/sync-ssm-dev.mjs
Rename the IAM group, user naming, and Terraform module/file paths from "dev credentials" to "dev secrets" for consistency. Made-with: Cursor
Switch dev-secrets access and fetch defaults to /dev paths, simplify docs and root command usage, and harden fetch-secrets key collision handling. Made-with: Cursor
Review feedback addressed (eb9e6f1)Handled:
Declined:
Questions / blocked:
|
Create CMS and web dev SSM parameters, move dev-secrets users into a submodule, and add developer-first IAM onboarding documentation. Made-with: Cursor
Remove extra root helper scripts and redundant source-path header output to keep fetch-secrets usage minimal and consistent. Made-with: Cursor
Review feedback addressed (00548cb)Handled:
Declined:
Questions / blocked:
|
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@infra/aws/iam/users/dev_secrets/main.tf`:
- Around line 1-3: The header comment currently uses the contributors API
command "gh api repos/JesusFilm/forge/contributors --paginate --jq '.[].login' |
sort -u" which returns all-time contributors and can include bots; replace that
line with a command that instead queries recent commits (using the commits API
with a --since filter for the last 12 months), extracts author logins, and
filters out bot accounts (e.g., exclude authors with type 'Bot' or logins that
end with "bot") so the regenerated list only contains recent human contributors;
update the header comment text to describe the new regeneration command and its
intent.
- Around line 14-16: Remove the data source lookup
data.aws_iam_group.dev_secrets and instead parameterize the module with a string
variable: add variable "group_name" { type = string } to the users module
(infra/aws/iam/users/dev_secrets/main.tf), update the membership resource to use
var.group_name (replace any data.aws_iam_group.dev_secrets.group_name usage,
e.g., in the aws_iam_group_membership or related resource), and update the
module invocation in infra/aws/iam/users/main.tf to pass group_name =
"forge-dev-secrets"; finally delete the data "aws_iam_group" "dev_secrets" block
so the plan no longer depends on an existing group.
In `@infra/aws/modules/cms/main.tf`:
- Around line 158-239: The dev SSM parameters (aws_ssm_parameter.dev_app_keys,
dev_admin_jwt_secret, dev_jwt_secret, dev_api_token_salt,
dev_transfer_token_salt, dev_encryption_key) are pointed at the existing
ephemeral.random_password.* results for stage; create a dedicated set of
ephemeral.random_password resources for dev (e.g.,
ephemeral.random_password.dev_app_key_1..4,
ephemeral.random_password.dev_admin_jwt_secret,
ephemeral.random_password.dev_jwt_secret,
ephemeral.random_password.dev_api_token_salt,
ephemeral.random_password.dev_transfer_token_salt,
ephemeral.random_password.dev_encryption_key) and update each
aws_ssm_parameter.value_wo (and the join(...) for dev_app_keys) to use the
corresponding ephemeral.random_password.dev_*.result so the /dev namespace mints
independent secrets. Ensure names and dependencies reference these new
dev-specific random_password resources.
In `@infra/aws/modules/platform/web_dev_ssm.tf`:
- Around line 24-39: The SSM parameter
aws_ssm_parameter.web_dev_strapi_api_token is currently set to a literal
placeholder and has lifecycle.ignore_changes on value, which causes the real
secret to be managed in the console (drift); replace the hardcoded value with a
Terraform-managed sensitive input (e.g., value = var.web_dev_strapi_api_token),
declare that variable as sensitive in variables.tf, and remove value from
lifecycle.ignore_changes so Terraform manages and records the true secret state;
apply the same change to the other aws_ssm_parameter resources in this file that
currently use the placeholder + ignore_changes pattern.
In `@scripts/fetch-secrets.ts`:
- Around line 87-113: fetchAllByPath sometimes returns an empty parameters
array; detect this immediately after const parameters =
fetchAllByPath(sourcePath) and throw an Error (including sourcePath and project)
if parameters.length === 0 so the script fails fast instead of writing an empty
outputFile and printing the success message; place the guard before iterating
the parameters (before the for loop) so writeFileSync and the subsequent
process.stdout.write call never run when no secrets were returned.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 4fdc2695-26a3-46db-842c-a8516b3c9254
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (15)
apps/cms/package.jsonapps/web/package.jsoninfra/aws/README.mdinfra/aws/iam/groups/dev_secrets/main.tfinfra/aws/iam/groups/dev_secrets/variables.tfinfra/aws/iam/groups/main.tfinfra/aws/iam/users/dev_secrets/README.mdinfra/aws/iam/users/dev_secrets/main.tfinfra/aws/iam/users/dev_secrets/variables.tfinfra/aws/iam/users/main.tfinfra/aws/modules/cms/main.tfinfra/aws/modules/platform/web_dev_ssm.tfpackage.jsonscripts/fetch-secrets.tsturbo.json
✅ Files skipped from review due to trivial changes (2)
- infra/aws/iam/users/dev_secrets/README.md
- infra/aws/iam/groups/dev_secrets/variables.tf
| data "aws_iam_group" "dev_secrets" { | ||
| group_name = "forge-dev-secrets" | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== users module lookup =="
rg -n -C2 'data "aws_iam_group" "dev_secrets"|group_name = "forge-dev-secrets"' infra/aws/iam/users/dev_secrets
echo
echo "== group resource definition =="
rg -n -C2 'resource "aws_iam_group" "dev_secrets"|name = "forge-dev-secrets"' infra/aws/iam/groups/dev_secrets
echo
echo "== module wiring / outputs =="
rg -n -C3 'module ".*dev.*secrets.*"|output ".*dev.*secrets.*"|depends_on' infra/aws/iamRepository: JesusFilm/forge
Length of output: 1677
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== infra/aws/iam/users/main.tf (full) =="
cat -n infra/aws/iam/users/main.tf
echo
echo "== infra/aws/iam/users/dev_secrets/main.tf (full) =="
cat -n infra/aws/iam/users/dev_secrets/main.tf
echo
echo "== Check for outputs in groups module =="
if [ -f infra/aws/iam/groups/dev_secrets/outputs.tf ]; then
echo "=== infra/aws/iam/groups/dev_secrets/outputs.tf ==="
cat -n infra/aws/iam/groups/dev_secrets/outputs.tf
else
echo "No outputs.tf found"
fi
echo
echo "== infra/aws/iam/groups/main.tf (full) =="
cat -n infra/aws/iam/groups/main.tfRepository: JesusFilm/forge
Length of output: 2454
Remove the data source lookup and use the group reference directly.
The dev_secrets module reads forge-dev-secrets via data "aws_iam_group" (lines 14–16), but the same stack creates that group in infra/aws/iam/groups/dev_secrets/main.tf. On a fresh account, the plan fails before Terraform gets a chance to create the group. The depends_on = [module.groups] at the parent level ensures creation order but does not prevent the data lookup from failing during the plan phase.
Since the group name is already hardcoded as "forge-dev-secrets", pass it as a module input variable instead of reading it via data source:
- Add
variable "group_name" { type = string }toinfra/aws/iam/users/dev_secrets/main.tf - Update the users module call in
infra/aws/iam/users/main.tfto passgroup_name = "forge-dev-secrets" - Use
var.group_namein the membership resource (line 35) instead ofdata.aws_iam_group.dev_secrets.group_name - Remove the data source block entirely
This removes the circular dependency and aligns with the guideline: "Keep Terraform modules explicit and environment inputs parameterized."
Also applies to lines 29–36, where the membership resource depends on the same data source.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infra/aws/iam/users/dev_secrets/main.tf` around lines 14 - 16, Remove the
data source lookup data.aws_iam_group.dev_secrets and instead parameterize the
module with a string variable: add variable "group_name" { type = string } to
the users module (infra/aws/iam/users/dev_secrets/main.tf), update the
membership resource to use var.group_name (replace any
data.aws_iam_group.dev_secrets.group_name usage, e.g., in the
aws_iam_group_membership or related resource), and update the module invocation
in infra/aws/iam/users/main.tf to pass group_name = "forge-dev-secrets"; finally
delete the data "aws_iam_group" "dev_secrets" block so the plan no longer
depends on an existing group.
Replace manual console value for /forge/aws/web/dev/STRAPI_API_TOKEN with Terraform-generated random password output. Made-with: Cursor
Address latest CodeRabbit comments by removing IAM group data lookup, separating dev secret generation from stage, and managing web dev tokens in Terraform. Add fail-fast handling for empty SSM paths and update contributor refresh guidance to 12-month human-only commits. Made-with: Cursor
Review feedback addressed (
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
infra/aws/modules/platform/web_dev_ssm.tf (2)
5-24: Use ephemeral random_password for consistency with CMS pattern.The CMS module uses
ephemeral "random_password"resources combined withvalue_wo/value_wo_versionto prevent secrets from being stored in Terraform state (per Terraform 1.11+ write-only attribute support). This file uses regularresource "random_password"with the standardvalueattribute, which stores the secret values in state.Consider aligning with the CMS approach for consistent security posture across both modules.
♻️ Proposed refactor to use ephemeral passwords and value_wo
-resource "random_password" "web_dev_strapi_api_token" { +ephemeral "random_password" "web_dev_strapi_api_token" { count = local.create_web_dev_ssm_parameters ? 1 : 0 length = 64 special = false } -resource "random_password" "web_dev_strapi_revalidate_token" { +ephemeral "random_password" "web_dev_strapi_revalidate_token" { count = local.create_web_dev_ssm_parameters ? 1 : 0 length = 64 special = false } -resource "random_password" "web_dev_strapi_preview_token" { +ephemeral "random_password" "web_dev_strapi_preview_token" { count = local.create_web_dev_ssm_parameters ? 1 : 0 length = 64 special = false }Then update the SSM parameters to use
value_wo(see next comment).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@infra/aws/modules/platform/web_dev_ssm.tf` around lines 5 - 24, The three random_password resources (random_password.web_dev_strapi_api_token, random_password.web_dev_strapi_revalidate_token, random_password.web_dev_strapi_preview_token) currently create persistent secrets in state; change them to use the ephemeral provider pattern by declaring them as ephemeral "random_password" so they do not emit a readable value into state and then wire their outputs into SSM using the write-only attributes (value_wo / value_wo_version) instead of value. Update the resource declarations for those three identifiers to the ephemeral form and ensure downstream SSM parameter resources reference the ephemeral outputs via value_wo/value_wo_version.
45-88: SecureString parameters should usevalue_woto avoid state exposure.Continuing from above, the SSM parameters should use
value_woandvalue_wo_versioninstead ofvalueto prevent secrets from being stored in Terraform state.♻️ Proposed refactor for SSM parameters
resource "aws_ssm_parameter" "web_dev_strapi_api_token" { count = local.create_web_dev_ssm_parameters ? 1 : 0 name = "/forge/aws/web/dev/STRAPI_API_TOKEN" type = "SecureString" key_id = module.application.ssm_kms_key_arn - value = random_password.web_dev_strapi_api_token[0].result + value_wo = ephemeral.random_password.web_dev_strapi_api_token[0].result + value_wo_version = var.web_ssm_secret_version # Add variable for rotation tags = merge(local.tags, { Environment = "dev" Service = "web" }) - } resource "aws_ssm_parameter" "web_dev_strapi_revalidate_token" { count = local.create_web_dev_ssm_parameters ? 1 : 0 name = "/forge/aws/web/dev/STRAPI_REVALIDATE_TOKEN" type = "SecureString" key_id = module.application.ssm_kms_key_arn - value = random_password.web_dev_strapi_revalidate_token[0].result + value_wo = ephemeral.random_password.web_dev_strapi_revalidate_token[0].result + value_wo_version = var.web_ssm_secret_version tags = merge(local.tags, { Environment = "dev" Service = "web" }) - } resource "aws_ssm_parameter" "web_dev_strapi_preview_token" { count = local.create_web_dev_ssm_parameters ? 1 : 0 name = "/forge/aws/web/dev/STRAPI_PREVIEW_TOKEN" type = "SecureString" key_id = module.application.ssm_kms_key_arn - value = random_password.web_dev_strapi_preview_token[0].result + value_wo = ephemeral.random_password.web_dev_strapi_preview_token[0].result + value_wo_version = var.web_ssm_secret_version tags = merge(local.tags, { Environment = "dev" Service = "web" }) - }Note: You'll need to add a
web_ssm_secret_versionvariable similar tocms_ssm_secret_versionfor rotation support.Based on learnings: "using value_wo and value_wo_version on aws_ssm_parameter resources is valid for write-only secrets (Terraform 1.11+). These fields prevent secrets from being stored in the Terraform state."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@infra/aws/modules/platform/web_dev_ssm.tf` around lines 45 - 88, Replace the use of the value attribute on aws_ssm_parameter resources with the write-only fields to avoid storing secrets in state: in aws_ssm_parameter.web_dev_strapi_api_token, aws_ssm_parameter.web_dev_strapi_revalidate_token, and aws_ssm_parameter.web_dev_strapi_preview_token remove the value = random_password... assignments and instead use value_wo = random_password...result together with value_wo_version = var.web_ssm_secret_version (add a web_ssm_secret_version variable similar to cms_ssm_secret_version for rotation support); keep the existing name, type, key_id and tags unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@infra/aws/iam/users/dev_secrets/main.tf`:
- Around line 1-3: The refresh command uses a BSD/macOS-only date flag (`date -u
-v-12m`) which breaks on GNU/Linux; update the comment to use a portable
approach by either replacing the command with a portable helper that detects the
date implementation (e.g., use `date -d "-12 months"` on GNU and `date -v-12m`
on BSD) or provide both GNU and BSD variants in the refresh instructions so
contributors on both platforms can run the command; ensure the updated text in
the header comment clearly shows both commands or the detection suggestion so
the contributor list can be refreshed cross-platform.
- Around line 14-23: The aws_iam_user resource "aws_iam_user.dev_secrets" can
fail to be destroyed if users retain credentials; add force_destroy = true to
the aws_iam_user "dev_secrets" resource so Terraform will remove users even when
they have access keys/MFA artifacts, ensuring removals of entries from
local.dev_secret_contributors succeed without manual cleanup. Locate resource
aws_iam_user.dev_secrets and insert force_destroy = true in its block; keep
existing tags and name logic unchanged.
---
Nitpick comments:
In `@infra/aws/modules/platform/web_dev_ssm.tf`:
- Around line 5-24: The three random_password resources
(random_password.web_dev_strapi_api_token,
random_password.web_dev_strapi_revalidate_token,
random_password.web_dev_strapi_preview_token) currently create persistent
secrets in state; change them to use the ephemeral provider pattern by declaring
them as ephemeral "random_password" so they do not emit a readable value into
state and then wire their outputs into SSM using the write-only attributes
(value_wo / value_wo_version) instead of value. Update the resource declarations
for those three identifiers to the ephemeral form and ensure downstream SSM
parameter resources reference the ephemeral outputs via
value_wo/value_wo_version.
- Around line 45-88: Replace the use of the value attribute on aws_ssm_parameter
resources with the write-only fields to avoid storing secrets in state: in
aws_ssm_parameter.web_dev_strapi_api_token,
aws_ssm_parameter.web_dev_strapi_revalidate_token, and
aws_ssm_parameter.web_dev_strapi_preview_token remove the value =
random_password... assignments and instead use value_wo =
random_password...result together with value_wo_version =
var.web_ssm_secret_version (add a web_ssm_secret_version variable similar to
cms_ssm_secret_version for rotation support); keep the existing name, type,
key_id and tags unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 342592e6-b012-4ea4-94ef-f72c85d99ae6
📒 Files selected for processing (6)
infra/aws/iam/users/dev_secrets/main.tfinfra/aws/iam/users/dev_secrets/variables.tfinfra/aws/iam/users/main.tfinfra/aws/modules/cms/main.tfinfra/aws/modules/platform/web_dev_ssm.tfscripts/fetch-secrets.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- scripts/fetch-secrets.ts
- infra/aws/iam/users/dev_secrets/variables.tf
- infra/aws/iam/users/main.tf
| # Generated from recent human contributors, bots excluded. | ||
| # Refresh source list with: | ||
| # gh api repos/JesusFilm/forge/commits --paginate -f since="$(date -u -v-12m +%Y-%m-%dT%H:%M:%SZ)" --jq '.[] | select(.author != null and .author.type != "Bot" and (.author.login | test("bot$") | not)) | .author.login' | sort -u |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
date -v flag portability BSD macOS GNU Linux
💡 Result:
date -v is a BSD/macOS-specific extension (it’s not POSIX), so it won’t work on GNU/Linux systems that use GNU coreutils date. (hackmd.io)
GNU/Linux equivalent
On GNU date, the common replacement is -d/--date with a relative date string, e.g.: (gnu.org)
- BSD/macOS:
date -v+1d +"%F" - GNU/Linux:
date -d "+1 day" +"%F"(gnu.org)
Portability options
- If you need one script for both: detect GNU vs BSD
dateand choose-dvs-v. - If you want GNU behavior on macOS: install GNU coreutils and use
gdate(common convention; avoids BSDdatedifferences). - If you want strict POSIX portability: avoid date arithmetic in
date(POSIXdatedoesn’t standardize-v/-d-style adjustments). (man7.org)
Citations:
- 1: https://hackmd.io/%40maelvls/bsd-vs-gnu-vs-busybox-incompat?utm_source=openai
- 2: https://www.gnu.org/software/coreutils/manual/html_node/Options-for-date.html?utm_source=openai
- 3: https://www.gnu.org/software/coreutils/manual/html_node/Options-for-date.html?utm_source=openai
- 4: https://man7.org/linux/man-pages/man1/date.1p.html?utm_source=openai
Make the contributor refresh command portable.
Line 3 uses date -u -v-12m, which is BSD/macOS-only. Contributors on GNU/Linux will get a failing refresh command and may leave this list stale. Switch to a portable helper (e.g., date -d "-12 months" for GNU or detect the date variant) or document both GNU and BSD variants.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infra/aws/iam/users/dev_secrets/main.tf` around lines 1 - 3, The refresh
command uses a BSD/macOS-only date flag (`date -u -v-12m`) which breaks on
GNU/Linux; update the comment to use a portable approach by either replacing the
command with a portable helper that detects the date implementation (e.g., use
`date -d "-12 months"` on GNU and `date -v-12m` on BSD) or provide both GNU and
BSD variants in the refresh instructions so contributors on both platforms can
run the command; ensure the updated text in the header comment clearly shows
both commands or the detection suggestion so the contributor list can be
refreshed cross-platform.
| resource "aws_iam_user" "dev_secrets" { | ||
| for_each = local.dev_secret_contributors | ||
|
|
||
| name = "${each.value}-dev-secrets" | ||
| path = "/" | ||
| tags = merge(var.tags, { | ||
| ManagedBy = "terraform" | ||
| Role = "dev-secrets" | ||
| }) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's find and examine the file mentioned
find . -path "*infra/aws/iam/users/dev_secrets/main.tf" -type fRepository: JesusFilm/forge
Length of output: 100
🏁 Script executed:
# Search for all aws_iam_user resources in the infra directory
rg "resource \"aws_iam_user\"" infra/ -A 10Repository: JesusFilm/forge
Length of output: 1421
🏁 Script executed:
# Check if there are other IAM user resources with force_destroy
rg "force_destroy" infra/ -B 3 -A 3Repository: JesusFilm/forge
Length of output: 41
🌐 Web query:
aws_iam_user force_destroy terraform
💡 Result:
In Terraform, aws_iam_user.force_destroy controls whether Terraform is allowed to delete an IAM user even if the user still has “extra” IAM artifacts that Terraform is not managing, such as:
- access keys created outside Terraform
- a console login profile
- MFA devices
If force_destroy = false (default), terraform destroy (or a replace) can fail because AWS won’t delete a user that still has those items. With force_destroy = true, Terraform will try to remove them and proceed with deletion. [1]
Also: if you attach policies via the legacy aws_iam_policy_attachment and then change the user’s name or path (which forces replacement), you must set force_destroy = true and apply it before the change, otherwise you can hit DeleteConflict. The recommended aws_iam_user_policy_attachment doesn’t have this requirement. [1]
Example:
resource "aws_iam_user" "example" {
name = "my-user"
force_destroy = true
}Source: Terraform AWS Provider docs for aws_iam_user. [1]
Add force_destroy = true to the managed IAM users.
These users are expected to self-manage access keys and likely MFA artifacts as well. Without force_destroy = true, removing someone from local.dev_secret_contributors will fail with a DeleteConflict error because AWS refuses to delete users that still have credentials attached, which pushes cleanup into manual IAM work.
Suggested change
resource "aws_iam_user" "dev_secrets" {
for_each = local.dev_secret_contributors
- name = "${each.value}-dev-secrets"
- path = "/"
+ name = "${each.value}-dev-secrets"
+ path = "/"
+ force_destroy = true
tags = merge(var.tags, {
ManagedBy = "terraform"
Role = "dev-secrets"
})
}This keeps infrastructure changes within Terraform as required by the coding guidelines.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| resource "aws_iam_user" "dev_secrets" { | |
| for_each = local.dev_secret_contributors | |
| name = "${each.value}-dev-secrets" | |
| path = "/" | |
| tags = merge(var.tags, { | |
| ManagedBy = "terraform" | |
| Role = "dev-secrets" | |
| }) | |
| } | |
| resource "aws_iam_user" "dev_secrets" { | |
| for_each = local.dev_secret_contributors | |
| name = "${each.value}-dev-secrets" | |
| path = "/" | |
| force_destroy = true | |
| tags = merge(var.tags, { | |
| ManagedBy = "terraform" | |
| Role = "dev-secrets" | |
| }) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@infra/aws/iam/users/dev_secrets/main.tf` around lines 14 - 23, The
aws_iam_user resource "aws_iam_user.dev_secrets" can fail to be destroyed if
users retain credentials; add force_destroy = true to the aws_iam_user
"dev_secrets" resource so Terraform will remove users even when they have access
keys/MFA artifacts, ensuring removals of entries from
local.dev_secret_contributors succeed without manual cleanup. Locate resource
aws_iam_user.dev_secrets and insert force_destroy = true in its block; keep
existing tags and name logic unchanged.
Summary
forge-dev-credentialsIAM group with MFA enforcement, SSM read, KMS decrypt, and self access-key management<github>-dev-credentialsusers without touching existing IAM userspnpm sync:ssm:devto pull String + SecureString parameters into.env.development.local, and document usageResolves #300
Contracts Changed
Regeneration Required
Validation
Notes:
terraform -chdir=infra/aws init -backend=false && terraform -chdir=infra/aws validateblocked locally by invalid AWS token.node scripts/sync-ssm-dev.mjsblocked locally by invalid AWS token.terraform -chdir=infra/aws/iam fmt -recursivepassed.Made with Cursor
Summary by CodeRabbit
New Features
Security / Access
Documentation