-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhello_world_stack.py
More file actions
184 lines (170 loc) · 8.97 KB
/
hello_world_stack.py
File metadata and controls
184 lines (170 loc) · 8.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
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
from typing import Any, cast
from aws_cdk import CfnOutput, Stack
from aws_cdk import aws_iam as iam
from cdk_nag import NagSuppressions
from constructs import Construct
from hello_world.hello_world_app import HelloWorldApp
from hello_world.nag_utils import (
apply_compliance_aspects,
attach_async_failure_destination,
suppress_cdk_singletons,
)
# CDK-managed singleton Lambda construct IDs. These are derived from CDK's
# own source hashes and have remained stable for years — not from our code,
# so they do not move when the stack is rescoped under a cdk.Stage.
_CDK_SINGLETON_IDS = (
"AWS679f53fac002430cb0da5b7982bd2287", # AwsCustomResource provider Lambda
)
class HelloWorldStack(Stack):
"""Thin wrapper stack composing the :class:`HelloWorldApp` construct.
Per the CDK best practice "model with constructs, deploy with stacks",
the domain logic lives in the ``HelloWorldApp`` construct; this stack only
applies stack-wide compliance Aspects, wires CfnOutputs, and attaches the
stack-level and singleton-scoped cdk-nag suppressions that cannot be
expressed on individual resources.
"""
def __init__(self, scope: Construct, construct_id: str, **kwargs: Any) -> None:
super().__init__(scope, construct_id, **kwargs)
apply_compliance_aspects(self)
self.app = HelloWorldApp(self, "App")
# Expose API URL for consumption by the frontend stack
self.api_url = self.app.api_url
CfnOutput(
self,
"HelloWorldApiOutput",
description="API Gateway endpoint URL for Prod stage",
value=f"{self.app.api.url}hello",
)
CfnOutput(
self,
"HelloWorldFunctionOutput",
description="Hello World Lambda Function ARN",
value=self.app.function.function_arn,
)
CfnOutput(
self,
"HelloWorldFunctionIamRoleOutput",
description="IAM Role created for Hello World function",
value=cast(iam.IRole, self.app.function.role).role_arn,
)
CfnOutput(
self,
"IdempotencyTableName",
description="DynamoDB table used for Lambda idempotency",
value=self.app.idempotency_table.table_name,
)
CfnOutput(
self,
"GreetingParameterName",
description="SSM parameter name for the greeting message",
value=self.app.greeting_param.parameter_name,
)
CfnOutput(
self,
"AppConfigAppName",
description="AppConfig application name for feature flags",
value=self.app.app_config_app.name,
)
CfnOutput(
self,
"CloudWatchDashboardUrl",
description="CloudWatch dashboard URL for this stack",
value=f"https://{self.region}.console.aws.amazon.com/cloudwatch/home#dashboards:name={self.stack_name}",
)
# ── Singleton-scoped cdk-nag suppressions ───────────────────────────────
# CDK-managed singleton Lambdas (currently just the AwsCustomResource
# provider) are created at the stack level as siblings of the construct
# that requested them, not as children. ``suppress_cdk_singletons`` looks
# them up via ``try_find_child`` so the suppressions keep working
# regardless of whether the stack is at the App root or nested inside
# a cdk.Stage. (The LogRetention singleton was eliminated when log
# groups were made explicit via ``log_group=`` everywhere.)
suppress_cdk_singletons(self, _CDK_SINGLETON_IDS)
# ── Async failure destination for the AwsCustomResource provider ────────
# CFN invokes the provider Lambda asynchronously; without an on_failure
# destination, a crash that exhausts Lambda's two automatic retries is
# silently dropped — only the CFN rollback error remains. Capturing the
# failed event envelope to SQS preserves the AWS API response and full
# request payload for post-mortem.
self.cr_provider_dlq = attach_async_failure_destination(
self,
"AWS679f53fac002430cb0da5b7982bd2287",
encryption_key=self.app.encryption_key,
queue_id="AwsCustomResourceProviderDlq",
)
# ── Stack-level cdk-nag suppressions (genuinely stack-wide) ─────────────
NagSuppressions.add_stack_suppressions(
self,
[
# ── AWS Solutions ────────────────────────────────────────────────
{"id": "AwsSolutions-APIG2", "reason": "Request validation not needed for sample app"},
{
"id": "AwsSolutions-APIG3",
"reason": "WAF not attached to API Gateway — applied at CloudFront instead",
},
{"id": "AwsSolutions-APIG4", "reason": "Authorization not needed for sample app"},
{"id": "AwsSolutions-COG4", "reason": "Cognito authorizer not needed for sample app"},
# ── Serverless ───────────────────────────────────────────────────
{
"id": "Serverless-APIGWDefaultThrottling",
"reason": "Custom throttling not configured for sample app",
},
{
"id": "CdkNagValidationFailure",
"reason": "Serverless-APIGWStructuredLogging validation fails due to intrinsic function reference in access log destination — structured JSON logging is configured via logging_format=JSON on the Lambda",
},
# ── NIST 800-53 R5 ──────────────────────────────────────────────
{
"id": "NIST.800.53.R5-APIGWAssociatedWithWAF",
"reason": "WAF not attached to API Gateway — applied at CloudFront instead",
},
{
"id": "NIST.800.53.R5-APIGWSSLEnabled",
"reason": "Client-side SSL certificates not required for sample app",
},
{
"id": "NIST.800.53.R5-APIGWCacheEnabledAndEncrypted",
"reason": (
"API Gateway cache cluster intentionally disabled for cost reasons — the smallest "
"0.5 GB cluster is ~$14/month for a sample app. Caching GET /hello would also serve "
"stale values across SSM parameter and AppConfig feature-flag changes."
),
},
{
"id": "NIST.800.53.R5-DynamoDBInBackupPlan",
"reason": "AWS Backup plan not configured for sample app — PITR is enabled for point-in-time recovery",
},
# ── HIPAA Security ───────────────────────────────────────────────
{
"id": "HIPAA.Security-APIGWSSLEnabled",
"reason": "Client-side SSL certificates not required for sample app",
},
{
"id": "HIPAA.Security-APIGWCacheEnabledAndEncrypted",
"reason": (
"API Gateway cache cluster intentionally disabled for cost reasons — "
"see NIST.800.53.R5-APIGWCacheEnabledAndEncrypted rationale above."
),
},
{
"id": "HIPAA.Security-DynamoDBInBackupPlan",
"reason": "AWS Backup plan not configured for sample app — PITR is enabled for point-in-time recovery",
},
# ── PCI DSS 3.2.1 ────────────────────────────────────────────────
{
"id": "PCI.DSS.321-APIGWAssociatedWithWAF",
"reason": "WAF not attached to API Gateway — applied at CloudFront instead",
},
{
"id": "PCI.DSS.321-APIGWSSLEnabled",
"reason": "Client-side SSL certificates not required for sample app",
},
{
"id": "PCI.DSS.321-APIGWCacheEnabledAndEncrypted",
"reason": (
"API Gateway cache cluster intentionally disabled for cost reasons — "
"see NIST.800.53.R5-APIGWCacheEnabledAndEncrypted rationale above."
),
},
],
)