Skip to content

Fix/terraform workflow security hardening#1225

Open
dami-005 wants to merge 4 commits into
solutions-plug:mainfrom
dami-005:fix/terraform-workflow-security-hardening
Open

Fix/terraform workflow security hardening#1225
dami-005 wants to merge 4 commits into
solutions-plug:mainfrom
dami-005:fix/terraform-workflow-security-hardening

Conversation

@dami-005

Copy link
Copy Markdown

Description

Description:

  • Sensitive output leak (High): deploy.yml wrote terraform output -json straight to a file uploaded as a build artifact. Terraform's sensitive = true only redacts the human-readable CLI output, not -json, so rds_endpoint/redis_endpoint were exposed in plaintext to anyone with artifact-read access. Now the JSON is piped through jq 'del(.rds_endpoint, .redis_endpoint)' before it's written, and a plain terraform output is printed to logs (still correctly redacted) for anyone who needs to eyeball values.
  • Missing required variables (Critical): terraform plan/apply only passed the per-environment .tfvars file, none of which set the required no-default sensitive variables (db_username, db_password, hmac_key, sendgrid_api_key, api_signing_key, acm_certificate_arn, api_image_uri, redis_auth_token, sendgrid_key_rotated_at) — every environment hard-failed with "No value for required variable". Added TF_VAR_* env vars to the plan and apply jobs, sourced from per-environment GitHub secrets (secrets[format('DB_PASSWORD_{0}', matrix.environment)], etc.), matching the AWS_ROLE_{0} pattern already used in this file.
  • Prod applies with no approval gate (Critical): the apply job's matrix ran prod automatically on every push to main with no environment: key, contradicting environments/README.md's documented manual-approval requirement. Added environment: ${{ matrix.environment }} to the apply job so each leg maps to a same-named GitHub Environment — configure a prod environment with required reviewers in repo settings to enforce the pause; dev/staging are unaffected and keep auto-applying.
  • Static long-lived AWS keys (High): terraform.yml authenticated with AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY repo secrets, including on the prod matrix leg, unlike deploy.yml's short-lived OIDC role assumption. Switched to role-to-assume: ${{ secrets.AWS_ROLE_ARN }} (resolved per GitHub Environment via the job's existing environment: key) and added the id-token: write permission OIDC requires.

Type of Change

  • Bug fix
  • New feature
  • Refactor / code cleanup
  • Documentation update
  • CI / tooling change
  • Breaking change

Testing Done

Bundle Size

Chunk Before After
vendor.js
main*.js
pages/_app*.js

Checklist

  • Tests pass locally
  • Documentation updated (if applicable)
  • No breaking changes, or breaking changes are documented above
  • If you added or changed an API endpoint, regenerated the OpenAPI spec and committed the result:
    cd services/api && cargo run --bin generate-openapi > openapi.yaml
    git add openapi.yaml && git commit -m "chore: regenerate openapi.yaml"
  • If you changed system architecture (new service, database, external dependency, or network boundary), updated docs/architecture.md
  • Bundle size checked (if frontend changes)

Related Issues

Closes #1204
Closes #1205
Closes #1206
Closes #1207

dami-005 added 4 commits July 26, 2026 06:29
terraform output -json bypasses the CLI's sensitive-value redaction, so
the uploaded outputs-<env>.json artifact contained plaintext
rds_endpoint/redis_endpoint for every environment. Filter those keys
out with jq before writing the file, and print the (correctly
redacted) human-readable `terraform output` to the job logs for any
legitimate need to eyeball values.
terraform plan/apply only ever passed -var-file="environments/<env>.tfvars",
but none of those files (nor any TF_VAR_*/`-var` flag) supplied the
required, no-default variables: db_username, db_password, hmac_key,
sendgrid_api_key, api_signing_key, acm_certificate_arn, api_image_uri,
redis_auth_token, sendgrid_key_rotated_at. Every environment hard-failed
with "No value for required variable".

Populate TF_VAR_* for each of these from per-environment GitHub encrypted
secrets (e.g. DB_PASSWORD_DEV/STAGING/PROD), following the same
secrets[format('X_{0}', matrix.environment)] pattern already used for
AWS_ROLE_* in this workflow. Committed tfvars files remain free of
secrets.
The apply job's matrix (dev/staging/prod) ran unconditionally on every
push to main with no environment: key, so prod applied automatically
with no manual gate -- contradicting environments/README.md, which
documents production as requiring explicit approval.

Set `environment: ${{ matrix.environment }}` on the apply job so each
matrix leg maps to a same-named GitHub Environment. Configure a "prod"
environment with required reviewers in repo settings to make that leg
pause for approval; dev/staging have no protection rules configured and
continue to auto-apply as before.
terraform.yml authenticated with long-lived AWS_ACCESS_KEY_ID/
AWS_SECRET_ACCESS_KEY repo secrets on every PR touching Terraform,
including the prod matrix leg -- weaker than deploy.yml, which already
uses short-lived OIDC role assumption. A leaked static key never
expires and is usable outside CI.

Switch to role-to-assume (AWS_ROLE_ARN), resolved per GitHub Environment
via the job's existing `environment: ${{ matrix.environment }}`, and add
the id-token: write permission OIDC requires. The old
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY secrets are no longer referenced
anywhere and should be deleted from repo settings.
@drips-wave

drips-wave Bot commented Jul 26, 2026

Copy link
Copy Markdown

@dami-005 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment