diff --git a/cdk/po_stack.py b/cdk/po_stack.py index 1cfb51c..668b9cf 100644 --- a/cdk/po_stack.py +++ b/cdk/po_stack.py @@ -57,6 +57,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(). @@ -90,6 +102,22 @@ class PoIngestStack(Stack): # interim CLI-created po-email-processor-dlq (removed post-deploy). email_processor_dlq = common.make_processor_dlq(self, "EmailProcessorDlq") + # --- 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, @@ -134,7 +162,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", @@ -372,6 +400,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", @@ -409,7 +446,7 @@ class PoIngestStack(Stack): ), timeout=Duration.seconds(60), memory_size=256, - log_retention=logs.RetentionDays.TWO_MONTHS, + log_group=web_ui_log_group, environment={ "PO_TABLE": "purchase-orders", # Defense-in-depth shared secret for the web UI handler. The @@ -464,6 +501,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", @@ -483,7 +529,7 @@ class PoIngestStack(Stack): ), 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 28756a2..b709dad 100644 --- a/cdk/wo_stack.py +++ b/cdk/wo_stack.py @@ -88,6 +88,33 @@ class WorkorderIngestStack(Stack): # interim CLI-created workorder-email-processor-dlq (removed post-deploy). email_processor_dlq = common.make_processor_dlq(self, "EmailProcessorDlq") + # --- 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, @@ -119,7 +146,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, @@ -304,6 +331,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", @@ -340,7 +376,7 @@ class WorkorderIngestStack(Stack): ), 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,