From cc97a9308deb25b8a1e9e8d948dd43a6662f4874 Mon Sep 17 00:00:00 2001 From: Jon Chery Date: Wed, 29 Jul 2026 20:49:36 +0000 Subject: [PATCH] docs(P09): complete iam-policy-least-privilege phase (v1.13.12) ---ci--- project: acdl phase: 9 milestone: v1.14 status: complete requirements: covered: [REQ-143] partial: [] ---/ci--- --- modules/l2/microservice/composition.json | 2 +- terraform/bootstrap/spike_runner_policy.json | 7 +++- tests/test_iam_policy_baseline.py | 41 +++++++++++++++++++- 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/modules/l2/microservice/composition.json b/modules/l2/microservice/composition.json index 2403501..b66cc57 100644 --- a/modules/l2/microservice/composition.json +++ b/modules/l2/microservice/composition.json @@ -18,7 +18,7 @@ "wires": [ {"from": "contract.inputs.name", "to": "alb.inputs.name", "default": "app"}, {"from": "contract.inputs.name", "to": "ecr.inputs.name", "default": "app-repo"}, - {"from": "contract.inputs.name", "to": "roles.inputs.role_name", "default": "app-role"}, + {"from": "contract.inputs.name", "to": "roles.inputs.role_name", "default": "acdl-app-role"}, {"from": "contract.inputs.region", "to": "cluster.inputs.region"}, {"from": "contract.inputs.region", "to": "ecr.inputs.region"}, {"from": "contract.inputs.region", "to": "roles.inputs.region"}, diff --git a/terraform/bootstrap/spike_runner_policy.json b/terraform/bootstrap/spike_runner_policy.json index 99380d8..67d59e3 100644 --- a/terraform/bootstrap/spike_runner_policy.json +++ b/terraform/bootstrap/spike_runner_policy.json @@ -215,7 +215,10 @@ "kms:TagResource", "kms:UntagResource" ], - "Resource": "*" + "Resource": [ + "arn:aws:kms:*:*:key/*", + "arn:aws:kms:*:*:alias/acdl-*" + ] }, { "Effect": "Allow", @@ -233,7 +236,7 @@ "iam:TagRole", "iam:UntagRole" ], - "Resource": "*" + "Resource": "arn:aws:iam::*:role/acdl-*" } ] } diff --git a/tests/test_iam_policy_baseline.py b/tests/test_iam_policy_baseline.py index 33a9b56..1ff7831 100644 --- a/tests/test_iam_policy_baseline.py +++ b/tests/test_iam_policy_baseline.py @@ -176,4 +176,43 @@ class TestIAMPolicyBaseline: res = s.get("Resource", "") if isinstance(res, list): res = " ".join(res) - assert res != "*", "iam:PassRole must not be granted to Resource: *" \ No newline at end of file + assert res != "*", "iam:PassRole must not be granted to Resource: *" + + def test_iam_role_creation_scoped_to_acdl_prefix(self, policy): + """G-104: iam:CreateRole must be scoped to role/acdl-* (not Resource: *).""" + for s in policy["Statement"]: + acts = s.get("Action", []) + if isinstance(acts, str): + acts = [acts] + if "iam:CreateRole" in acts: + res = s.get("Resource", "") + if isinstance(res, list): + res = " ".join(res) + assert "acdl-*" in res, f"iam:CreateRole must be scoped to acdl-* (got: {res})" + + def test_kms_scoped_to_acdl_alias(self, policy): + """G-104: kms:CreateKey etc. must be scoped to alias/acdl-* (not Resource: *).""" + for s in policy["Statement"]: + acts = s.get("Action", []) + if isinstance(acts, str): + acts = [acts] + if any(a.startswith("kms:") for a in acts): + res = s.get("Resource", "") + if isinstance(res, list): + res = " ".join(res) + assert "acdl-*" in res, f"kms actions must be scoped to acdl-* (got: {res})" + + def test_cloudfront_waf_remain_global(self, policy): + """G-104: CloudFront + WAFv2 (CloudFront scope) ARNs are global; + Resource: * is acceptable here (documented constraint, not a defect).""" + global_actions = {"cloudfront:", "wafv2:"} + for s in policy["Statement"]: + acts = s.get("Action", []) + if isinstance(acts, str): + acts = [acts] + if any(any(a.startswith(g) for g in global_actions) for a in acts): + res = s.get("Resource", "") + if isinstance(res, list): + res = res[0] if res else "" + # CloudFront/WAFv2 are allowed to be * (global ARNs) + assert res == "*" or "acdl" in res \ No newline at end of file