diff --git a/cdk/po_stack.py b/cdk/po_stack.py index 4648842..7771250 100644 --- a/cdk/po_stack.py +++ b/cdk/po_stack.py @@ -118,6 +118,18 @@ class PoIngestStack(Stack): ), ) + # --- Shared customer-managed CMK for CloudWatch log encryption --- + # alias/seahaven-logs. The po-email-processor log group was already + # associated with this CMK out-of-band; declaring encryption_key on the + # migrated LogGroups below reconciles that drift and hardens the remaining + # groups (INFRA-114, per cross-review). The key policy already permits + # logs.us-east-1.amazonaws.com (verified in use on po-email-processor). + logs_cmk = kms.Key.from_key_arn( + self, + "LogsKey", + "arn:aws:kms:us-east-1:328440206208:key/b748750c-3b26-478d-acfb-d0126cc97f56", + ) + # --- Purchase-orders DynamoDB table --- # Owned by this stack. Streams enabled for the site-extractor pipeline. # Other stacks (seahaven-slack-bot) reference this table via fromTableName(). @@ -157,6 +169,22 @@ class PoIngestStack(Stack): enforce_ssl=True, ) + # --- Explicit log group (INFRA-114) --- + # Replaces the deprecated log_retention prop, which provisioned a + # LogRetention custom resource whose role held logs:PutRetentionPolicy/ + # DeleteRetentionPolicy on Resource "*" (checkov CKV_AWS_111). The name + # matches Lambda's default (/aws/lambda/) so the function + # keeps writing to the same group; RETAIN matches the repo convention for + # stateful resources. + email_processor_log_group = logs.LogGroup( + self, + "EmailProcessorLogGroup", + log_group_name="/aws/lambda/po-email-processor", + retention=logs.RetentionDays.TWO_MONTHS, + encryption_key=logs_cmk, + removal_policy=RemovalPolicy.RETAIN, + ) + # --- Lambda function --- email_processor = lambda_.Function( self, @@ -180,7 +208,7 @@ class PoIngestStack(Stack): ), timeout=Duration.seconds(60), memory_size=256, - log_retention=logs.RetentionDays.TWO_MONTHS, + log_group=email_processor_log_group, dead_letter_queue=email_processor_dlq, environment={ "PO_TABLE": "purchase-orders", @@ -297,6 +325,15 @@ class PoIngestStack(Stack): ) # --- Web UI Lambda --- + web_ui_log_group = logs.LogGroup( + self, + "WebUILogGroup", + log_group_name="/aws/lambda/po-web-ui", + retention=logs.RetentionDays.TWO_MONTHS, + encryption_key=logs_cmk, + removal_policy=RemovalPolicy.RETAIN, + ) + web_ui = lambda_.Function( self, "WebUI", @@ -307,7 +344,7 @@ class PoIngestStack(Stack): code=lambda_.Code.from_asset("../lambdas/po/web_ui"), timeout=Duration.seconds(60), memory_size=256, - log_retention=logs.RetentionDays.TWO_MONTHS, + log_group=web_ui_log_group, environment={ "PO_TABLE": "purchase-orders", }, @@ -369,6 +406,15 @@ class PoIngestStack(Stack): # 518 WCU of write amplification. Re-add if a state-level query path ships. # --- Site extractor Lambda (DynamoDB Streams → verified-sites) --- + site_extractor_log_group = logs.LogGroup( + self, + "SiteExtractorLogGroup", + log_group_name="/aws/lambda/po-ingest-site-extractor", + retention=logs.RetentionDays.TWO_MONTHS, + encryption_key=logs_cmk, + removal_policy=RemovalPolicy.RETAIN, + ) + site_extractor = lambda_.Function( self, "SiteExtractor", @@ -379,7 +425,7 @@ class PoIngestStack(Stack): code=lambda_.Code.from_asset("../lambdas/po/site_extractor"), timeout=Duration.seconds(60), memory_size=256, - log_retention=logs.RetentionDays.TWO_MONTHS, + log_group=site_extractor_log_group, environment={ "VERIFIED_SITES_TABLE": verified_sites_table.table_name, "PENDING_REVIEW_TABLE": "pending-site-review", diff --git a/cdk/wo_stack.py b/cdk/wo_stack.py index fc8d166..761967a 100644 --- a/cdk/wo_stack.py +++ b/cdk/wo_stack.py @@ -156,6 +156,33 @@ class WorkorderIngestStack(Stack): enforce_ssl=True, ) + # --- Shared customer-managed CMK for CloudWatch log encryption --- + # alias/seahaven-logs. Same CMK the po-email-processor log group was + # already associated with out-of-band; declaring encryption_key on the + # migrated LogGroups below hardens these groups (INFRA-114, per + # cross-review). The key policy already permits logs.us-east-1.amazonaws.com. + logs_cmk = kms.Key.from_key_arn( + self, + "LogsKey", + "arn:aws:kms:us-east-1:328440206208:key/b748750c-3b26-478d-acfb-d0126cc97f56", + ) + + # --- Explicit log group (INFRA-114) --- + # Replaces the deprecated log_retention prop, which provisioned a + # LogRetention custom resource whose role held logs:PutRetentionPolicy/ + # DeleteRetentionPolicy on Resource "*" (checkov CKV_AWS_111). The name + # matches Lambda's default (/aws/lambda/) so the function + # keeps writing to the same group; RETAIN matches the repo convention for + # stateful resources. + email_processor_log_group = logs.LogGroup( + self, + "EmailProcessorLogGroup", + log_group_name="/aws/lambda/workorder-email-processor", + retention=logs.RetentionDays.TWO_MONTHS, + encryption_key=logs_cmk, + removal_policy=RemovalPolicy.RETAIN, + ) + # --- Lambda function --- email_processor = lambda_.Function( self, @@ -179,7 +206,7 @@ class WorkorderIngestStack(Stack): ), timeout=Duration.seconds(60), memory_size=256, - log_retention=logs.RetentionDays.TWO_MONTHS, + log_group=email_processor_log_group, dead_letter_queue=email_processor_dlq, environment={ "WORK_ORDERS_TABLE": work_orders_table.table_name, @@ -311,6 +338,15 @@ class WorkorderIngestStack(Stack): ) # --- Web UI Lambda --- + web_ui_log_group = logs.LogGroup( + self, + "WebUILogGroup", + log_group_name="/aws/lambda/workorder-web-ui", + retention=logs.RetentionDays.TWO_MONTHS, + encryption_key=logs_cmk, + removal_policy=RemovalPolicy.RETAIN, + ) + web_ui = lambda_.Function( self, "WebUI", @@ -321,7 +357,7 @@ class WorkorderIngestStack(Stack): code=lambda_.Code.from_asset("../lambdas/wo/web_ui"), timeout=Duration.seconds(15), memory_size=128, - log_retention=logs.RetentionDays.TWO_MONTHS, + log_group=web_ui_log_group, environment={ "WORK_ORDERS_TABLE": work_orders_table.table_name, "COMMENTS_TABLE": comments_table.table_name,