Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
83 changes: 27 additions & 56 deletions infra/cdk/NAG.md
Original file line number Diff line number Diff line change
@@ -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:<AWS::Partition>:...`), e.g.:
- IAM4: `/^Policy::.*$/g`
- IAM5: `/^(Action|Resource)::.*$/g`

AwsSolutions::AwsSolutions-IAM4[Policy::arn:<AWS::Partition>: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:<AWS::Partition>: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:<AWS::Partition>:...`). 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`.
10 changes: 1 addition & 9 deletions infra/cdk/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>25</maven.compiler.target>
<cdk.version>2.250.0</cdk.version>
<cdk.version>2.260.0</cdk.version>
<constructs.version>10.6.0</constructs.version>
<cdknag.version>2.38.2</cdknag.version>
<junit.version>6.1.1</junit.version>
Expand Down Expand Up @@ -113,13 +113,5 @@
<version>${junit.version}</version>
<scope>test</scope>
</dependency>

<!-- Property-based testing with jqwik -->
<dependency>
<groupId>net.jqwik</groupId>
<artifactId>jqwik</artifactId>
<version>1.10.1</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
25 changes: 9 additions & 16 deletions infra/cdk/src/main/java/sample/com/WorkshopApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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:<AWS::Partition>:iam::aws:policy/AdministratorAccess",
"Policy::arn:<AWS::Partition>:iam::aws:policy/PowerUserAccess",
"Policy::arn:<AWS::Partition>:iam::aws:policy/ReadOnlyAccess",
"Policy::arn:<AWS::Partition>:iam::aws:policy/AmazonSSMManagedInstanceCore",
"Policy::arn:<AWS::Partition>:iam::aws:policy/CloudWatchAgentServerPolicy",
"Policy::arn:<AWS::Partition>: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:<AWS::Partition>:ec2:<AWS::Region>:<AWS::AccountId>:network-interface/*",
"Resource::arn:<AWS::Partition>:codebuild:<AWS::Region>:<AWS::AccountId>:report-group/<CodeBuildProjectA0FF5539>-*",
"Resource::arn:<AWS::Partition>:logs:<AWS::Region>:<AWS::AccountId>:log-group:/aws/codebuild/<CodeBuildProjectA0FF5539>:*",
"Resource::arn:aws:logs:<AWS::Region>:<AWS::AccountId>: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(),
Expand Down
13 changes: 5 additions & 8 deletions infra/cdk/src/test/java/sample/com/constructs/IdePropsTest.java
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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)
Expand Down Expand Up @@ -57,11 +59,6 @@ void architectureDeterminesInstanceTypes(@ForAll("ideArchProvider") IdeArch arch
}
}

@Provide
Arbitrary<IdeArch> ideArchProvider() {
return Arbitraries.of(IdeArch.ARM64, IdeArch.X86_64_AMD, IdeArch.X86_64_INTEL);
}

/**
* Unit test: ARM64 returns Graviton instance types
*/
Expand Down