diff --git a/cdk/po_stack.py b/cdk/po_stack.py index 144d9e9..82a6bd6 100644 --- a/cdk/po_stack.py +++ b/cdk/po_stack.py @@ -8,6 +8,7 @@ from aws_cdk import ( aws_cloudwatch as cloudwatch, aws_cloudwatch_actions as cw_actions, aws_dynamodb as dynamodb, + aws_kms as kms, aws_lambda as lambda_, aws_lambda_event_sources as lambda_event_sources, aws_logs as logs, @@ -18,6 +19,7 @@ from aws_cdk import ( aws_secretsmanager as secretsmanager, aws_sns as sns, aws_sqs as sqs, + aws_ssm as ssm, ) from constructs import Construct @@ -37,6 +39,20 @@ class PoIngestStack(Stack): ], ) + # --- Shared customer-managed CMK for sensitive DynamoDB tables --- + # Owned by the account-baseline app (alias/seahaven-dynamodb, INFRA-95 / + # M-3); ARN published to SSM. The purchase-orders table was migrated to + # SSE-KMS out-of-band, so declaring encryption_key here reconciles the + # drift and — via grant_read_write_data below — propagates the required + # kms:Decrypt/GenerateDataKey/DescribeKey to the consumer roles. + dynamodb_cmk = kms.Key.from_key_arn( + self, + "DynamoDbCmk", + ssm.StringParameter.value_for_string_parameter( + self, "/seahaven/dynamodb/cmk-arn" + ), + ) + # --- 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(). @@ -51,6 +67,8 @@ class PoIngestStack(Stack): billing_mode=dynamodb.BillingMode.PAY_PER_REQUEST, removal_policy=RemovalPolicy.RETAIN, stream=dynamodb.StreamViewType.NEW_IMAGE, + encryption=dynamodb.TableEncryption.CUSTOMER_MANAGED, + encryption_key=dynamodb_cmk, ) # --- Secrets Manager for Anthropic API key --- diff --git a/cdk/wo_stack.py b/cdk/wo_stack.py index 4cba41b..c26313c 100644 --- a/cdk/wo_stack.py +++ b/cdk/wo_stack.py @@ -8,6 +8,7 @@ from aws_cdk import ( aws_cloudwatch as cloudwatch, aws_cloudwatch_actions as cw_actions, aws_dynamodb as dynamodb, + aws_kms as kms, aws_lambda as lambda_, aws_logs as logs, aws_s3 as s3, @@ -17,6 +18,7 @@ from aws_cdk import ( aws_secretsmanager as secretsmanager, aws_sns as sns, aws_sqs as sqs, + aws_ssm as ssm, ) from constructs import Construct @@ -127,6 +129,22 @@ class WorkorderIngestStack(Stack): comments_table.grant_read_write_data(email_processor) anthropic_secret.grant_read(email_processor) + # Pre-emptive KMS grant on the shared DynamoDB CMK (alias/seahaven-dynamodb, + # /seahaven/dynamodb/cmk-arn). The WorkOrders/WorkOrderComments tables are + # NOT yet SSE-KMS encrypted, so this grant is currently unused; it is added + # ahead of the CMK migration so the processor role does not hit AccessDenied + # the moment those tables are migrated. The actual table migration + kebab + # rename is tracked in INFRA-6 (and the set-aside wo_stack CMK WIP); fold the + # web-ui read grant in there. + dynamodb_cmk = kms.Key.from_key_arn( + self, + "DynamoDbCmk", + ssm.StringParameter.value_for_string_parameter( + self, "/seahaven/dynamodb/cmk-arn" + ), + ) + dynamodb_cmk.grant_encrypt_decrypt(email_processor) + # --- Errors alarm (INFRA-41 / audit H-8) --- # ALARM-only (no OK action, per the CloudWatch-alarm preference) to the # shared site-alerts topic (CMK alias/seahaven-alarm-topics lives on the