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