NAG Utilities¶
The hello_world.nag_utils module holds the shared cdk-nag suppression
helpers used by the three stacks.
API reference¶
hello_world.nag_utils
¶
Shared cdk-nag helpers.
apply_compliance_aspects applies the full available rule-pack set to a
stack so every stack exercises the same compliance gauntlet. NIST 800-53 R4
is intentionally omitted — R5 supersedes it and running both would duplicate
findings on overlapping controls.
CDK_LAMBDA_SUPPRESSIONS is the canonical suppression list for CDK-managed
singleton Lambdas (AwsCustomResource provider, BucketDeployment, S3AutoDeleteObjects).
Their runtime, memory, tracing, DLQ, VPC, and IAM policies are all managed by
CDK and cannot be configured by the caller. Import it and pass it to
NagSuppressions.add_resource_suppressions_by_path or
NagSuppressions.add_resource_suppressions with apply_to_children=True.
apply_compliance_aspects(stack)
¶
Attach every cdk-nag rule pack this project runs to stack.
Source code in hello_world/nag_utils.py
attach_async_failure_destination(scope, singleton_id, *, encryption_key, queue_id)
¶
Wire an SQS DLQ to a CDK-managed async singleton Lambda.
AwsCustomResource provider Lambdas are invoked asynchronously by CloudFormation during stack lifecycle events. Without an on_failure destination, a provider crash that exhausts Lambda's two automatic async retries is silently dropped — the stack rollback still surfaces a CFN error, but the cause (Python traceback, AWS API error response) is gone unless someone catches it in CloudWatch within the retention window. SQS as the on_failure destination preserves the failed-event envelope (full request payload + responseContext) for post-mortem.
The queue uses the same CMK as the surrounding stack, with 14-day retention (Lambda's max meaningful window — events older than that have already aged past most rollback investigations).
Returns the created queue so callers can attach alarms or outputs;
returns None if the singleton isn't present under scope (which
happens when no AwsCustomResource has been instantiated in this stack).
Source code in hello_world/nag_utils.py
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | |
grant_guardduty_service_to_key(key, *, region, account, partition)
¶
Grant GuardDuty kms:Decrypt on a CMK so Lambda Protection can introspect.
GuardDuty Lambda Protection (and similar foundational-detection features)
needs to read Lambda function configuration — including env vars encrypted
with a customer-managed key. Without this grant the assumed
AWSServiceRoleForAmazonGuardDuty role is denied kms:Decrypt against
the CMK, leaving GuardDuty's coverage of CMK-encrypted resources incomplete
(the original CloudTrail finding that motivated this grant).
Scoped to GuardDuty detectors in this account+region only via
aws:SourceAccount and aws:SourceArn — the cross-account
confused-deputy guard AWS documents for service-principal grants.
Source code in hello_world/nag_utils.py
grant_logs_service_to_key(key, *, region, account, partition)
¶
Add the standard CloudWatch Logs service-principal grant to a CMK.
Three CMKs in this project (backend, frontend, WAF) need the same statement:
a grant to logs.{region}.amazonaws.com for symmetric encrypt/decrypt
operations, conditioned via kms:EncryptionContext:aws:logs:arn so only
log groups in this account+region can request key operations. Defining it
in one place keeps the three call sites in lockstep — pylint's R0801
duplicate-code check correctly flags any drift between them, and the
confused-deputy condition is exactly the kind of thing that's harmful to
forget on one of the three CMKs.
Source code in hello_world/nag_utils.py
suppress_cdk_singletons(scope, singleton_ids)
¶
Apply CDK_LAMBDA_SUPPRESSIONS to any CDK-managed singletons present under scope.
Resolves each ID via node.try_find_child rather than an absolute path
string so suppressions survive being nested in a cdk.Stage. Missing IDs
are tolerated — some singletons only appear when the construct that needs
them is instantiated.