diff --git a/.gitignore b/.gitignore index 38c6af03..a0b0be3a 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,4 @@ build/ infrastructure/cdk/output* dependency-reduced-pom.xml -# jqwik writes a serialized failing-example DB next to the project root on each run -.jqwik-database - .env diff --git a/infra/cdk/NAG.md b/infra/cdk/NAG.md index cb056bed..316bbce2 100644 --- a/infra/cdk/NAG.md +++ b/infra/cdk/NAG.md @@ -1,64 +1,35 @@ -# cdk-nag / CDK version ceiling +# cdk-nag notes -## Current pinned versions -- `aws-cdk-lib`: **2.250.0** -- `cdk-nag`: **2.38.2** -- EKS: stable `software.amazon.awscdk.services.eks_v2` (the deprecated `eks-v2-alpha` module was dropped). +## Current versions +- `aws-cdk-lib`: **2.260.0** (latest 2.x) +- `cdk-nag`: **2.38.2** (latest 2.x) +- EKS: stable `software.amazon.awscdk.services.eks_v2` -`cdk synth` passes with **0 cdk-nag findings** under the latest CDK CLI with this -combination. IAM4/IAM5 suppressions in `WorkshopApp` carry explicit `appliesTo` -evidence (a bare `AwsSolutions-IAM5` id does not suppress). +`cdk synth` passes with **0 cdk-nag findings** for all five `template.type` +values (java-on-aws, java-on-amazon-eks, java-spring-ai-agents, +java-ai-agents, java-ai-agents-advanced) under the latest CDK CLI. -## Why not newer +## Suppressing IAM findings -`aws-cdk-lib` 2.251.0 introduced CDK's native policy-validation framework -(`software.amazon.awscdk.Validations`). From 2.251 onward, cdk-nag findings are -routed through it and must be suppressed via `Validations.of(scope).acknowledge()` -instead of `NagSuppressions`. +Broad IAM is intentional for this ephemeral workshop. IAM4 (managed +policies) and IAM5 (wildcards) findings are suppressed in `WorkshopApp` +using cdk-nag's `RegexAppliesTo` so the suppression matches every finding +regardless of template type or generated resource name: -That acknowledge API treats `::` as a reserved `prefix::ruleName` delimiter, so it -rejects finding ids that embed IAM ARNs (which contain `iam::aws` and -`arn::...`), e.g.: +- IAM4: `/^Policy::.*$/g` +- IAM5: `/^(Action|Resource)::.*$/g` - AwsSolutions::AwsSolutions-IAM4[Policy::arn::iam::aws:policy/AdministratorAccess] - -> InvalidValidationId: The '::' delimiter is reserved for separating the prefix from the rule name +Do NOT go back to enumerated `appliesTo` lists - they only match one +template's specific findings and silently break `npm run gen` for the +others (each `template.type` builds a different construct/IAM set). -Because every AWS managed-policy finding embeds `iam::aws`, IAM4/IAM5 findings -cannot currently be acknowledged on aws-cdk-lib >= 2.251. +## cdk-nag 3.x -cdk-nag 3.x does not help: its `NagPack` is plugin-only -(`IPolicyValidationPlugin`, no `IAspect`), so it relies entirely on the same -native `acknowledge` mechanism and cannot run report-only either. - -Verified: 2.250 synth passes (0 findings); 2.260 synth fails with 19 IAM4/IAM5 -findings even with correct `appliesTo`. - -## Trigger to update - -Do NOT pin the CDK CLI and do NOT add a Dependabot `ignore` for these bumps. -CI runs `cdk synth` with the latest CLI, so the Dependabot PR that bumps -`aws-cdk-lib` to >= 2.251 (or `cdk-nag` to 3.x) is a live tripwire: - -- While the `build-infra` check on that PR is **red**, the upstream gap is still open. -- When that check goes **green**, the fix has landed. Then: - 1. bump `aws-cdk-lib` (and optionally `cdk-nag` to 3.x), - 2. switch the IAM4/IAM5 suppressions in `WorkshopApp` from - `NagSuppressions` to `Validations.of(stack).acknowledge(...)`, - 3. delete this note. - -### Manual recheck -On a scratch branch with `aws-cdk-lib >= 2.251` + `cdk-nag` 3.x, confirm that - - Validations.of(stack).acknowledge( - Acknowledgment.builder() - .id("AwsSolutions-IAM4[Policy::arn::iam::aws:policy/AdministratorAccess]") - .reason("...").build()); - -no longer throws `InvalidValidationId` and that `cdk synth` exits 0. - -## Upstream references -- aws/aws-cdk#26844 - cdk synth used to return exit 0 despite policy-validation - failures (fixed in recent CLIs; this is what surfaced the findings). -- cdk-nag v3 README: prefix / bulk suppression "not yet supported" (tracked upstream). -- Watch `aws-cdk-lib` release notes for `Validations.acknowledge` id parsing that - accepts embedded `::`. +Not adopted. cdk-nag 3.x's `NagPack` is plugin-only +(`IPolicyValidationPlugin`, no `IAspect`) and suppresses via CDK's native +`Validations.of().acknowledge()`, whose rule-id parser rejects ids +containing `::` - which every IAM managed-policy/ARN finding embeds +(`iam::aws`, `arn::...`). We don't need 3.x: the 2.x Aspect ++ `NagSuppressions` (regex `appliesTo`) path works on the latest +aws-cdk-lib. Revisit 3.x only if cdk-nag/CDK fix the `::` handling in +`acknowledge`. diff --git a/infra/cdk/pom.xml b/infra/cdk/pom.xml index 0de48732..5223f981 100644 --- a/infra/cdk/pom.xml +++ b/infra/cdk/pom.xml @@ -11,7 +11,7 @@ UTF-8 25 25 - 2.250.0 + 2.260.0 10.6.0 2.38.2 6.1.1 @@ -113,13 +113,5 @@ ${junit.version} test - - - - net.jqwik - jqwik - 1.10.1 - test - diff --git a/infra/cdk/src/main/java/sample/com/WorkshopApp.java b/infra/cdk/src/main/java/sample/com/WorkshopApp.java index 35bbbb1d..8940c74e 100644 --- a/infra/cdk/src/main/java/sample/com/WorkshopApp.java +++ b/infra/cdk/src/main/java/sample/com/WorkshopApp.java @@ -3,6 +3,7 @@ import io.github.cdklabs.cdknag.AwsSolutionsChecks; import io.github.cdklabs.cdknag.NagPackSuppression; import io.github.cdklabs.cdknag.NagSuppressions; +import io.github.cdklabs.cdknag.RegexAppliesTo; import software.amazon.awscdk.App; import software.amazon.awscdk.AppProps; import software.amazon.awscdk.Aspects; @@ -37,24 +38,16 @@ public static void main(final String[] args) { new NagPackSuppression.Builder().id("AwsSolutions-APIG6").reason("API Gateway access logging not needed for workshop").build(), new NagPackSuppression.Builder().id("AwsSolutions-COG4").reason("Workshop environment does not require Cognito User Pool authorization").build(), - // IAM - IAM4/IAM5 require appliesTo evidence to suppress + // IAM - broad IAM is intentional for the ephemeral workshop. Regex + // appliesTo suppresses every IAM4 (managed policy) and IAM5 (wildcard) + // finding, so it stays robust across all template types and generated + // resource names. new NagPackSuppression.Builder().id("AwsSolutions-IAM4") - .appliesTo(List.of( - "Policy::arn::iam::aws:policy/AdministratorAccess", - "Policy::arn::iam::aws:policy/PowerUserAccess", - "Policy::arn::iam::aws:policy/ReadOnlyAccess", - "Policy::arn::iam::aws:policy/AmazonSSMManagedInstanceCore", - "Policy::arn::iam::aws:policy/CloudWatchAgentServerPolicy", - "Policy::arn::iam::aws:policy/service-role/AWSLambdaBasicExecutionRole")) - .reason("AWS managed policies are acceptable for workshop").build(), + .appliesTo(List.of(RegexAppliesTo.builder().regex("/^Policy::.*$/g").build())) + .reason("AWS managed policies are acceptable for the ephemeral workshop environment").build(), new NagPackSuppression.Builder().id("AwsSolutions-IAM5") - .appliesTo(List.of( - "Resource::*", - "Resource::arn::ec2:::network-interface/*", - "Resource::arn::codebuild:::report-group/-*", - "Resource::arn::logs:::log-group:/aws/codebuild/:*", - "Resource::arn:aws:logs:::log-group:/aws/bedrock/*")) - .reason("Wildcard permissions acceptable for workshop parallel resource creation").build(), + .appliesTo(List.of(RegexAppliesTo.builder().regex("/^(Action|Resource)::.*$/g").build())) + .reason("Wildcard permissions are acceptable for the ephemeral workshop environment").build(), // RDS new NagPackSuppression.Builder().id("AwsSolutions-RDS2").reason("Workshop non-sensitive test database does not need encryption at rest").build(), diff --git a/infra/cdk/src/test/java/sample/com/constructs/IdePropsTest.java b/infra/cdk/src/test/java/sample/com/constructs/IdePropsTest.java index 7153d238..477909d8 100644 --- a/infra/cdk/src/test/java/sample/com/constructs/IdePropsTest.java +++ b/infra/cdk/src/test/java/sample/com/constructs/IdePropsTest.java @@ -1,7 +1,8 @@ package sample.com.constructs; -import net.jqwik.api.*; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; import sample.com.constructs.Ide.IdeArch; import sample.com.constructs.Ide.IdeProps; @@ -24,8 +25,9 @@ public class IdePropsTest { * - ARM64: instance types contain 'g' suffix (m7g, m6g, c7g, t4g) * - X86_64: instance types do NOT contain 'g' suffix before size (m7i, m6i, m5, t3) */ - @Property(tries = 100) - void architectureDeterminesInstanceTypes(@ForAll("ideArchProvider") IdeArch arch) { + @ParameterizedTest + @EnumSource(IdeArch.class) + void architectureDeterminesInstanceTypes(IdeArch arch) { // Given IdeProps props = IdeProps.builder() .ideArch(arch) @@ -57,11 +59,6 @@ void architectureDeterminesInstanceTypes(@ForAll("ideArchProvider") IdeArch arch } } - @Provide - Arbitrary ideArchProvider() { - return Arbitraries.of(IdeArch.ARM64, IdeArch.X86_64_AMD, IdeArch.X86_64_INTEL); - } - /** * Unit test: ARM64 returns Graviton instance types */