Skip to content
Home
Blog
Notes
Back to Blog

Suppress cdk-nag findings for custom resource singleton lambda globally

January 12, 2025•4 min read

This post describes how to suppress cdk-nag findings that are caused by the custom resource singleton Lambda function globally, so that findings which are out of your own control are not reported.

awscdkcdk-nag

Table of Contents

  • Overview
  • Understanding the Problem
  • How CDK Uses Custom Resources
  • Generated CloudFormation Components
  • cdk-nag Findings
  • Why Resource-Level Suppression Isn't Enough
  • The Solution
  • Overview
  • Implementation
  • Benefits
  • Additional Resources

Overview

When using AWS CDK with custom resources, CDK creates a singleton Lambda function that handles all custom resource operations. This Lambda function can trigger several cdk-nag findings, even if you're not directly using custom resources in your stack. This article explains how to properly suppress these findings globally.

Understanding the Problem

How CDK Uses Custom Resources

CDK uses a shared Lambda function to handle all custom resources in your stack. Here's a typical example of creating a custom resource:

new AwsCustomResource(this, "GetParameter", { onUpdate: { service: "SSM", action: "GetParameter", parameters: { Name: "my-parameter", WithDecryption: true, }, physicalResourceId: PhysicalResourceId.of(Date.now().toString()), }, functionName: "GetParameter", policy: AwsCustomResourcePolicy.fromSdkCalls({ resources: AwsCustomResourcePolicy.ANY_RESOURCE, }), });

Generated CloudFormation Components

When CDK synthesizes this code, it creates several CloudFormation resources:

  1. The custom resource itself
  2. An IAM policy for the custom resource
  3. An IAM role for the singleton Lambda function
  4. The singleton Lambda function
# The custom resource, which calling the singleton lambda function "GetParameter42B0A00E": { "Type": "Custom::AWS", "Properties": { "ServiceToken": { "Fn::GetAtt": [ "AWS679f53fac002430cb0da5b7982bd22872D164C4C", "Arn" ] }, "Create": "{\"service\":\"SSM\",\"action\":\"GetParameter\",\"parameters\":{\"Name\":\"my-parameter\",\"WithDecryption\":true},\"physicalResourceId\":{\"id\":\"1736701660301\"}}", "Update": "{\"service\":\"SSM\",\"action\":\"GetParameter\",\"parameters\":{\"Name\":\"my-parameter\",\"WithDecryption\":true},\"physicalResourceId\":{\"id\":\"1736701660301\"}}", "InstallLatestAwsSdk": false }, "DependsOn": [ "GetParameterCustomResourcePolicyD8E5D455" ], "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete", "Metadata": { "aws:cdk:path": "StackMain/GetParameter/Resource/Default" } }, # The policy which is attached to the singleton lambda function role "GetParameterCustomResourcePolicyD8E5D455": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { "Statement": [ { "Action": "ssm:GetParameter", "Effect": "Allow", "Resource": "*" } ], "Version": "2012-10-17" }, "PolicyName": "GetParameterCustomResourcePolicyD8E5D455", "Roles": [ { "Ref": "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2" } ] }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete", "Metadata": { "aws:cdk:path": "StackMain/GetParameter/CustomResourcePolicy/Resource" } }, # The role for the singleton lambda function # Causing the finding AWSSolution-IAM4, because of the used managed policy "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { "Statement": [ { "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" } } ], "Version": "2012-10-17" }, "ManagedPolicyArns": [ { "Fn::Join": [ "", [ "arn:", { "Ref": "AWS::Partition" }, ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" ] ] } ] }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete", "Metadata": { "aws:cdk:path": "StackMain/AWS679f53fac002430cb0da5b7982bd2287/ServiceRole/Resource" } }, # The lambda function which is used for all custom resources "AWS679f53fac002430cb0da5b7982bd22872D164C4C": { "Type": "AWS::Lambda::Function", "Properties": { "Code": { "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, "S3Key": "ce2f3595a340d6c519a65888ef97e3b9b64f053f83608e32cc28162e22d7d99a.zip" }, "FunctionName": "GetParameter", "Handler": "index.handler", "Role": { "Fn::GetAtt": [ "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2", "Arn" ] }, "Runtime": { "Fn::FindInMap": [ "LatestNodeRuntimeMap", { "Ref": "AWS::Region" }, "value" ] }, "Timeout": 120 }, "DependsOn": [ "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2" ], "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete", "Metadata": { "aws:cdk:path": "StackMain/AWS679f53fac002430cb0da5b7982bd2287/Resource", "aws:asset:path": "asset.ce2f3595a340d6c519a65888ef97e3b9b64f053f83608e32cc28162e22d7d99a", "aws:asset:is-bundled": false, "aws:asset:property": "Code" } },

This will be the linked resources if there a two custom resources in the stack.

Diagramm Cloudforamtion Template

cdk-nag Findings

This configuration triggers three main findings from cdk-nag:

[Error at /StackMain/ApplicationsSignals/GetParameter/CustomResourcePolicy/Resource] AwsSolutions-IAM5[Resource::*]: The IAM entity contains wildcard permissions and does not have a cdk-nag rule suppression with evidence for those permission. [Error at /StackMain/AWS679f53fac002430cb0da5b7982bd2287/ServiceRole/Resource] AwsSolutions-IAM4[Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole]: The IAM user, role, or group uses AWS managed policies. [Warning at /StackMain/AWS679f53fac002430cb0da5b7982bd2287/Resource] CdkNagValidationFailure[AwsSolutions-L1]: 'AwsSolutions-L1' threw an error during validation.

Why Resource-Level Suppression Isn't Enough

A simple resource-level suppression like this only handles the AwsSolutions-IAM5 finding:

NagSuppressions.addResourceSuppressions( customResource, [ { id: "AwsSolutions-IAM5", reason: "Policy Creation derived from SdkCalls are OK", }, ], true, );

This doesn't work for the other findings because the singleton Lambda function is attached to the root of the stack and requires a different suppression approach.

The Solution

Overview

To properly suppress all findings related to the custom resource singleton Lambda, we need to:

  1. Identify all possible paths where the custom resource creates resources
  2. Check if these paths exist in our stack
  3. Apply suppressions only to existing paths

Implementation

Here's the complete solution:

const suppressions: NagPackSuppression[] = [ { id: "AwsSolutions-IAM4", reason: "Custom resource singleton lambda", }, { id: "AwsSolutions-L1", reason: "Custom resource singleton lambda", }, ]; const stack = Stack.of(this); // The UUID is a fixed value used by CDK for custom resources const customResourceId = `AWS${AwsCustomResource.PROVIDER_FUNCTION_UUID.split("-").join("")}`; const stackName = stack.stackName; // Define all possible paths that could trigger cdk-nag findings const customResourceSuppressPaths = new Set([ `/${stackName}/${customResourceId}/ServiceRole/Resource`, `/${stackName}/${customResourceId}/Resource`, ]); // Get all existing paths in the stack const allExistingPaths = new Set( stack.node.findAll().map((node) => `/${node.node.path}`), ); // Apply suppressions only to paths that exist for (const path of customResourceSuppressPaths) { if (allExistingPaths.has(path)) { NagSuppressions.addResourceSuppressionsByPath( stack, path, suppressions, true, ); } }

Benefits

This solution:

  • Handles all cdk-nag findings related to the singleton Lambda
  • Works whether you're directly using custom resources or not
  • Prevents errors from trying to suppress non-existent paths
  • Can be implemented centrally in your stack

Additional Resources

If you're using a custom NagPack, you can find implementation examples here.

Published on January 12, 2025

Share:Bluesky Icon
Categories: aws

Related Posts

Jan 11, 2026

Tag log buckets created by AWS CDK for third party tools

Dec 31, 2025

Use a customized CDK bootstrap template

Nov 27, 2025

Granular statement cdk-nag AwsSolutions-IAM5 Suppressions

RSS|

© 2026 Johannes Konings. All rights reserved.