Overview
cdk-nag’s AwsSolutions-IAM5 rule is one of the most frequent findings in real-world stacks. It flags wildcard permissions in both Action (e.g., kms:GenerateDataKey*) and Resource (e.g., *) when there is no evidence-backed suppression. The challenge is that some AWS APIs are designed to require * (e.g., AWS X-Ray). Blanket suppressions hide too much. This post demonstrates how to record granular evidence specifically for actions that require *, ensuring that all other findings remain active.
Understanding the Problem
Why IAM5 triggers
You’ll hit AwsSolutions-IAM5 when a policy uses an asterisk ("*") in the resource or wildcards in actions. This is beneficial because least privilege is critical. However, several AWS APIs require * by design, or you might initially group actions broadly before refining the scope. Common examples include:
new PolicyStatement({
actions: ["xray:PutTelemetryRecords", "xray:PutTraceSegments"],
resources: ["*"],
effect: Effect.ALLOW,
});new PolicyStatement({
actions: ["kms:GenerateDataKey*", "kms:ReEncrypt*"],
resources: [
"arn:aws:kms:eu-central-1:471112809534:key/a096e96c-780e-48eb-993b-5b8cc46d8fd7",
],
effect: Effect.ALLOW,
}),Example Failure
An IAM5 failure typically looks like this:
[Error at /StackMain/MyRole/Policy/Resource] AwsSolutions-IAM5[Resource::*]: The IAM entity contains wildcard permissions and does not have a cdk-nag rule suppression with evidence for those permissions.Why blanket suppression isn’t enough
Suppressing the entire resource or the entire rule hides legitimate issues and loses the evidence that auditors expect. The goal is to:
- Keep the rule active for statements that can be scoped.
- Provide explicit evidence for the few actions that require
*.
// Bad: over-broad, hides everything
NagSuppressions.addResourceSuppressions(role, [
{ id: "AwsSolutions-IAM5", reason: "needs wildcard" },
]);This approach makes reviews harder and weakens the principle of least privilege.
The Solution
Overview
The solution involves using granular suppressions that document exactly which actions require * and why. Everything else remains flagged by cdk-nag. The
cdk-nag-custom-nag-pack automates this by:
- Inspecting policy statements with
*. - Adding a single AwsSolutions-IAM5 suppression per resource with
appliesToevidence for only those actions. - Leaving non-allowed wildcard actions reported so you can scope them properly.
Implementation
The repository includes a dedicated helper (iam5NagSuppressions.ts) that applies these granular suppressions automatically. The essential components are:
- Statement scanning: Iterates IAM policy statements and selects those with
Resource: *(including arrays that contain*). - Evidence builder: Produces
appliesToentries forResource::*and only the allow-listedAction::<service:operation>values present in the statement. - Targeting: Attaches a single
AwsSolutions-IAM5suppression to each IAM Policy construct via its path, leaving any non-allowed wildcard actions unsuppressed (so they continue to fail until scoped).
You can find the source code here: iam5NagSuppressions.ts
Critical Difference: String-Based vs. Statement-Based Suppressions
Important: There are two fundamentally different approaches to granular IAM5 suppressions, each offering different levels of safety:
1. cdk-nag’s Built-in appliesTo
The standard cdk-nag approach (see Example 6 in the cdk-nag docs) relies on string patterns:
NagSuppressions.addResourceSuppressions(
lambda,
[
{
id: "AwsSolutions-IAM5",
reason: "X-Ray requires wildcard",
appliesTo: ["Resource::*"],
},
],
true,
);The Problem: This suppresses Resource::* and the specified actions anywhere they appear in the construct tree. If you later add a new policy statement like:
// This will be silently suppressed!
new PolicyStatement({
actions: ["xray:PutTelemetryRecords"], // Matches the suppression
resources: ["*"], // Matches the suppression
effect: Effect.ALLOW,
});The suppression hides it because the strings match, even though this might be an unintended permission you added months later.
2. cdk-nag-custom-nag-pack’s Statement Validation
The cdk-nag-custom-nag-pack approach requires exact statement matching:
const policyStatementForSuppression: PolicyStatementProps = {
actions: ["xray:PutTelemetryRecords", "xray:PutTraceSegments"],
resources: ["*"],
effect: Effect.ALLOW,
};
Iam5NagSuppressions.addIam5StatementResourceSuppressions(
lambda,
{
id: "AwsSolutions-IAM5",
reason: "X-Ray requires wildcard",
appliesTo: [policyStatementForSuppression],
},
true,
);The Benefit: The suppression applies only if there is an exact match of the entire policy statement (Effect, Actions, Resources). If you add a new statement with different actions or resources later:
// This will NOT be suppressed - it will be flagged!
new PolicyStatement({
actions: ["xray:PutTelemetryRecords", "s3:*"], // Different actions
resources: ["*"],
effect: Effect.ALLOW,
});It will be flagged because the statement does not match exactly. This prevents the accidental hiding of new wildcards added over time.
Key Takeaway: The cdk-nag-custom-nag-pack method validates the complete policy statement structure, whereas cdk-nag’s built-in appliesTo relies on string patterns that may inadvertently suppress unrelated future wildcards.
Benefits
This statement-based approach:
- Documents exactly why
*is needed for specific AWS APIs - Keeps AwsSolutions-IAM5 active for everything else
- Produces review-ready evidence via
appliesTo - Prevents accidental suppression of future wildcard permissions
Lambda Construct Wildcard Scenario
The aws-lambda.Function construct (and its default execution role or managed policies) often introduces unavoidable wildcard permissions, such as those required for CloudWatch Logs creation or X-Ray tracing.
The Risk: If you suppress AwsSolutions-IAM5 broadly at the Function or Role level, you inadvertently hide every future wildcard you might add (e.g., s3:* or dynamodb:*).
The Granular Strategy:
- Isolate: Put truly unavoidable wildcard actions (e.g.,
logs:CreateLogGroup,xray:PutTraceSegments) in their own dedicatedPolicyStatement. - Suppress: Apply the suppression only to that specific statement, citing the AWS service limitation as the reason.
- Scope: Ensure all other actions are scoped to specific ARNs (log streams, tables, buckets, queues, etc.).
- Avoid: Do not use parent-level suppressions, as they mask future configuration drift.
Result: Required wildcards are documented and allowed, while accidental broad permissions continue to be reported.