Repository navigation
Description
Re-filed: the original #1827 was lost in the monorepo split; its only trace is dc476f8 ("Deferred and tracked: #1827 (guard walk misses two deploy-path modules; lambda:InvokeFunction Resource scope)").
What the guard checks
terraform/environments/aws/ci-cd-permissions/policy_guard_test.go derives, from the module tree, every IAM action the workload roles are granted (TestBoundaryCoversWorkloadServices), every aws_iam_role that must carry the permissions boundary (TestEveryModuleRoleHasPermissionsBoundary) and every attached managed-policy ARN. All three read their file list from findAwsModuleFiles, which walks terraform/modules and keeps only .tf files whose directory path contains an aws path segment.
Gap 1: the walk misses deploy-path modules
The deploy path is terraform/environments/aws/*.tf plus every module it instantiates. Modules whose directory has no aws segment are invisible to the walk, and so are the root *.tf files:
terraform/modules/build(module "build"inenvironments/aws/build.tf)terraform/modules/deployment-checks(module "deployment_checks"inenvironments/aws/checks.tf)
Neither declares an IAM policy or role today, so nothing is currently uncovered. The walk is a path-name heuristic rather than derived from what the deploy instantiates, so the next IAM policy added to either (or to a root .tf) is not checked against the boundary.
Mutation proof: appended to terraform/modules/build/main.tf
resource "aws_iam_role_policy" "mutation" {
role = "x"
policy = jsonencode({
Version = "2012-10-17"
Statement = [{ Effect = "Allow", Action = ["ecs:DeleteCluster", "lambda:DeleteFunction"], Resource = "*" }]
})
}go test . in ci-cd-permissions on main: ok github.com/LeanerCloud/cloud-commitments-platform/terraform/environments/aws/ci-cd-permissions 0.318s. Neither action is in the boundary, and the guard still passes.
Gap 2: lambda:InvokeFunction Resource scope
policy_boundary.tf WorkloadServiceCeiling allows lambda:InvokeFunction on Resource = "*" (any function in the account, which runs it as its execution role with an attacker-chosen payload and no iam:PassRole). The only workload role that uses it is the API Lambda's async_self_invoke policy (modules/compute/aws/lambda/main.tf, ~line 486), scoped to aws_lambda_function.main.arn, whose name is ${stack_name}-api, stack_name = "${project_name}-${environment}-<hex>", project_name default cudly. The aws_lambda_permission InvokeFunction entries are resource policies and are not gated by the boundary. The deploy role itself can only create arn:aws:lambda:*:*:function:cudly-* (policy_compute.tf), so every function this deploy creates matches. The tightest correct scope is arn:aws:lambda:*:*:function:cudly-*; the boundary comment already names it as the intended follow-up.
Needs a human apply of ci-cd-permissions after the fix merges.
Activity
test(iac/aws): derive boundary guard walk from the module tree; scope…
test(iac/aws): derive boundary guard walk from the module tree; scope…
cristim commentedon Oct 8, 2026
Fixed by PR #512, squash-merged as 15a5eea (reviewed head ab6dd6f). The boundary guard now walks the full module tree, including nested local children and remote-source modules, and lambda:InvokeFunction is scoped to the Cudly functions. Merged with exact-head review, targeted local verification and green CI; the deployed boundary still needs the documented privileged bootstrap apply.