fix(po): grant KMS on seahaven-dynamodb CMK to purchase-orders consumers (INFRA-104) (#56)
Some checks are pending
Deploy / deploy (push) Waiting to run

The purchase-orders table was migrated to SSE-KMS (alias/seahaven-dynamodb,
INFRA-95/M-3) out-of-band, but po_stack never declared the key, so
grant_read_write_data did not propagate kms perms. po-email-processor failed
~99.6% of invocations with kms:Decrypt AccessDeniedException, a data-loss
outage on the PO ingestion write path.

- po_stack: declare encryption_key on purchase-orders (reconciles SSE drift;
  no-op against the already-encrypted live table) so the existing grants add
  kms:Decrypt/GenerateDataKey/DescribeKey to EmailProcessor, WebUI, SiteExtractor.
- wo_stack: pre-emptive grant_encrypt_decrypt on the WO processor role ahead of
  the WorkOrders CMK migration (INFRA-6); tables left unencrypted, no table change.

GPT-4.1 cross-review: no blockers.
This commit is contained in:
Adam Moussa 2026-06-10 19:31:55 -04:00 • committed by GitHub
parent d217caac13
commit 47fa35688d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 36 additions and 0 deletions

View file

@ -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 ---

View file

@ -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