Reviewed commit: be11bdcb5. Note: origin/main moved to 3e9660d06 during the review; re-verify against current main before changing code, since a finding may have been fixed or moved.
Blast radius: customer credential material. This is not a CI/deploy-infrastructure finding. The grant below reaches the AES-256-GCM key that decrypts stored customer cloud credentials, which is why it carries priority/p0 / impact/all-users rather than the priority/p1 / impact/internal used for our own deploy-identity findings.
Where
terraform/modules/compute/gcp/cleanup-function/main.tf:9-13 (the grant)
terraform/modules/compute/gcp/cleanup-function/main.tf:47 (the one secret the function actually needs)
terraform/modules/compute/gcp/cloud-run/main.tf:247-276 (the sibling module that already fixed this)
terraform/modules/secrets/gcp/main.tf:258-288 (the credential-encryption key that becomes readable)
What
google_project_iam_member.cleanup_secrets grants roles/secretmanager.secretAccessor at project scope to the cleanup function's service account, ungated (no count, no condition). Verified still present verbatim at 3e9660d06:
# Grant access to Secret Manager
resource "google_project_iam_member" "cleanup_secrets" {
project = var.project_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${google_service_account.cleanup.email}"
}
The function only ever needs one secret: var.db_password_secret_id, wired into DB_PASSWORD_SECRET at main.tf:47.
The sibling Cloud Run module explicitly documents removing this exact anti-pattern and replacing it with per-secret bindings (compute/gcp/cloud-run/main.tf:247-276):
Previously this granted the Cloud Run SA project-wide roles/secretmanager.secretAccessor, which let it read every secret in the project... Now we bind only the specific secrets
The cleanup function was never migrated.
Failure scenario
Anyone who compromises the cleanup function, or anyone who can impersonate its service account (for example a developer holding roles/iam.serviceAccountTokenCreator on the project), reads every secret in the project. That set includes:
<service>-credential-encryption-key (terraform/modules/secrets/gcp/main.tf:258-288), the AES-256-GCM key that decrypts stored customer cloud credentials
jwt-secret
session-secret
scheduled-task-secret
- the SendGrid API key
Full tenant-credential compromise reachable from a session-cleanup job that has no business touching any of it.
Fix direction
Replace google_project_iam_member.cleanup_secrets with a google_secret_manager_secret_iam_member scoped to var.db_password_secret_id only, mirroring the pattern at compute/gcp/cloud-run/main.tf:262-267. Then add a repo-level guard (a grep-based lint or a policy test) that fails on any new project-scope roles/secretmanager.secretAccessor binding, so the next module does not repeat it.
Related
Reviewed commit:
be11bdcb5. Note:origin/mainmoved to3e9660d06during the review; re-verify against currentmainbefore changing code, since a finding may have been fixed or moved.Blast radius: customer credential material. This is not a CI/deploy-infrastructure finding. The grant below reaches the AES-256-GCM key that decrypts stored customer cloud credentials, which is why it carries
priority/p0/impact/all-usersrather than thepriority/p1/impact/internalused for our own deploy-identity findings.Where
terraform/modules/compute/gcp/cleanup-function/main.tf:9-13(the grant)terraform/modules/compute/gcp/cleanup-function/main.tf:47(the one secret the function actually needs)terraform/modules/compute/gcp/cloud-run/main.tf:247-276(the sibling module that already fixed this)terraform/modules/secrets/gcp/main.tf:258-288(the credential-encryption key that becomes readable)What
google_project_iam_member.cleanup_secretsgrantsroles/secretmanager.secretAccessorat project scope to the cleanup function's service account, ungated (nocount, no condition). Verified still present verbatim at3e9660d06:The function only ever needs one secret:
var.db_password_secret_id, wired intoDB_PASSWORD_SECRETatmain.tf:47.The sibling Cloud Run module explicitly documents removing this exact anti-pattern and replacing it with per-secret bindings (
compute/gcp/cloud-run/main.tf:247-276):The cleanup function was never migrated.
Failure scenario
Anyone who compromises the cleanup function, or anyone who can impersonate its service account (for example a developer holding
roles/iam.serviceAccountTokenCreatoron the project), reads every secret in the project. That set includes:<service>-credential-encryption-key(terraform/modules/secrets/gcp/main.tf:258-288), the AES-256-GCM key that decrypts stored customer cloud credentialsjwt-secretsession-secretscheduled-task-secretFull tenant-credential compromise reachable from a session-cleanup job that has no business touching any of it.
Fix direction
Replace
google_project_iam_member.cleanup_secretswith agoogle_secret_manager_secret_iam_memberscoped tovar.db_password_secret_idonly, mirroring the pattern atcompute/gcp/cloud-run/main.tf:262-267. Then add a repo-level guard (a grep-based lint or a policy test) that fails on any new project-scoperoles/secretmanager.secretAccessorbinding, so the next module does not repeat it.Related
sec(iac): onboarding paths drift. This is the same "one path was hardened, its sibling was never migrated" shape, on the host-side module axis rather than the customer-onboarding-template axis. See the comment on sec(iac): onboarding paths drift - shell/CFN/ARM templates lack the guards their Terraform siblings enforce cloud-commitments-platform#91 enumerating the host-side instances.