Skip to content

QA plan: Secrets and least-privilege IAM (#25)

Story: Secrets and least-privilege IAM, per env (#25)
Capability: AWS infrastructure (#19)
ADRs: RAG009 — authorizer role scoping; RAG001 — per-env isolation

TypeCoverage
IaC linttfsec or checkov rule in CI: no wildcard * on Resource in any aws_iam_policy_document except cloudwatch:PutMetricData and xray:PutTraceSegments
IaC linttfsec rule: no plaintext secret value in Terraform variables, outputs, or *.tfvars
Static (plan review)API Lambda role: scoped to specific Bedrock model ARN, specific cluster ARN, specific secret ARN
Static (plan review)Authorizer Lambda role: scoped to rds-data:ExecuteStatement on cluster ARN + secretsmanager:GetSecretValue on credential secret ARN only
Static (plan review)Secrets Manager secret ARN is referenced in Terraform; secret value never appears in .tf or *.tfvars
Static (plan review)Per-env naming convention followed: secret path includes env prefix (qa/ or prod/); no cross-env secret references
Integration (manual, QA)After apply: aws iam simulate-principal-policy confirms the API Lambda role cannot call s3:GetObject on arbitrary buckets or bedrock:InvokeModel on model IDs not in the allow list
SecurityOIDC trust policy for GitHub Actions names repo and environment explicitly; no * in the sub claim
  • #21, #22, #23, #24 deployed to QA (modules exist for roles to reference)
  • tfsec and checkov installed in CI
  • tfsec and checkov pass with no high-severity findings on modules/ and envs/
  • Plan review checklist: no wildcard resources, no plaintext secrets, per-env naming confirmed
  • iam-policy-simulator (or equivalent terraform plan assertion) confirms least-privilege for both Lambda roles
  • OIDC trust policy check passes in CI

IaC lint and static: CI. Integration check: manual against QA post-deploy using AWS CLI.

tfsec, checkov, terraform validate, terraform plan, AWS CLI (aws iam simulate-principal-policy).

Terminal window
# Given the QA Terraform plan has been applied
# When the IAM policy simulator runs against the API Lambda role
aws iam simulate-principal-policy \
--policy-source-arn "$API_LAMBDA_ROLE_ARN" \
--action-names "s3:GetObject" \
--resource-arns "arn:aws:s3:::*" \
| python3 -c "import sys,json; r=json.load(sys.stdin); assert all(e['EvalDecision']=='implicitDeny' for e in r['EvaluationResults']), 'S3 access not denied'"
# Then all evaluated decisions are implicitDeny

tfsec and checkov run as CI steps without Given/When/Then (they are linters, not tests); the IAM simulation above uses the pattern for verification steps.

  • A wildcard on secretsmanager:* that is scoped to a specific secret ARN pattern (e.g., arn:aws:secretsmanager:*:*:secret:qa/*) may pass tfsec but violate least-privilege intent. PR review must check that the action list, not just the resource, is scoped.
  • Cross-env secret reference is easiest to introduce in shared module outputs; enforce via checkov rule and reviewer checklist.

RAG009 Consequences: authorizer Lambda role scoped to cluster ARN and credential secret ARN only. Security per-feature requirements (#19–#25): no wildcards, no plaintext secrets, per-env naming. Exit criteria map to each row.