feat(slack-bot): codify kms:Decrypt grants for CMK'd DynamoDB readers (INFRA-95) #51

Merged
amoussa1229 merged 2 commits from infra-95-slackbot-cmk-grants into main 2026-06-09 16:33:07 +00:00
3 changed files with 44 additions and 0 deletions

View file

@ -5,6 +5,8 @@ import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as lambdaNodejs from 'aws-cdk-lib/aws-lambda-nodejs';
import * as logs from 'aws-cdk-lib/aws-logs';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as kms from 'aws-cdk-lib/aws-kms';
import * as ssm from 'aws-cdk-lib/aws-ssm';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
import * as bedrock from 'aws-cdk-lib/aws-bedrock';
@ -123,6 +125,19 @@ export class BedrockAgentConstruct extends Construct {
sitesTable.grantReadData(this.woPoLambda);
paymentsTable.grantReadData(this.woPoLambda);
// WorkOrders, WorkOrderComments, purchase-orders and PaymentsDashboard are
// SSE-encrypted with the shared customer-managed CMK (INFRA-95 / M-3). These
// tables are imported by name, so grantReadData does not add KMS perms — this
// cross-stack reader needs kms:Decrypt explicitly or every read on those four
// tables fails with AccessDenied. (verified-sites is NOT CMK-encrypted; it is
// unaffected.) The CMK key policy delegates to IAM via kms:ViaService.
const dynamodbCmk = kms.Key.fromKeyArn(
this,
'DynamoDbCmk',
ssm.StringParameter.valueForStringParameter(this, '/seahaven/dynamodb/cmk-arn'),
);
dynamodbCmk.grantDecrypt(this.woPoLambda);
// ── Bedrock Agent execution role ──────────────────────────────────────────
const agentRole = new iam.Role(this, 'AgentRole', {
roleName: 'AmazonBedrockExecutionRoleForAgents_seahaven',

View file

@ -5,6 +5,8 @@ import * as lambdaNodejs from 'aws-cdk-lib/aws-lambda-nodejs';
import * as logs from 'aws-cdk-lib/aws-logs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as kms from 'aws-cdk-lib/aws-kms';
import * as ssm from 'aws-cdk-lib/aws-ssm';
import * as events from 'aws-cdk-lib/aws-events';
import * as targets from 'aws-cdk-lib/aws-events-targets';
import * as iam from 'aws-cdk-lib/aws-iam';
@ -49,6 +51,17 @@ export class PoSyncConstruct extends Construct {
// Grant read access to the purchase-orders table
poTable.grantReadData(this.syncLambda);
// purchase-orders is SSE-encrypted with the shared customer-managed CMK
// (INFRA-95 / M-3). The table is imported by name, so grantReadData does not
// add KMS perms — grant kms:Decrypt explicitly or scans fail with
// AccessDenied. (CMK key policy delegates to IAM via kms:ViaService.)
const dynamodbCmk = kms.Key.fromKeyArn(
this,
'DynamoDbCmk',
ssm.StringParameter.valueForStringParameter(this, '/seahaven/dynamodb/cmk-arn'),
);
dynamodbCmk.grantDecrypt(this.syncLambda);
// Grant read/write to the KB docs bucket (read to list+delete old, write new)
props.kbDocsBucket.grantReadWrite(this.syncLambda);

View file

@ -5,6 +5,8 @@ import * as lambdaNodejs from 'aws-cdk-lib/aws-lambda-nodejs';
import * as logs from 'aws-cdk-lib/aws-logs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as kms from 'aws-cdk-lib/aws-kms';
import * as ssm from 'aws-cdk-lib/aws-ssm';
import * as events from 'aws-cdk-lib/aws-events';
import * as targets from 'aws-cdk-lib/aws-events-targets';
import * as iam from 'aws-cdk-lib/aws-iam';
@ -54,6 +56,20 @@ export class WorkorderSyncConstruct extends Construct {
workOrdersTable.grantReadData(this.syncLambda);
commentsTable.grantReadData(this.syncLambda);
// WorkOrders + WorkOrderComments are SSE-encrypted with the shared
// customer-managed CMK (INFRA-95 / M-3). Because the tables are imported by
// name (fromTableName), CDK does not know about their encryption key, so
// grantReadData does NOT add the KMS permissions — they must be granted
// explicitly or every read fails with kms:Decrypt AccessDenied. The CMK key
// policy delegates to IAM via kms:ViaService, so this identity grant is what
// authorizes this cross-stack reader.
const dynamodbCmk = kms.Key.fromKeyArn(
this,
'DynamoDbCmk',
ssm.StringParameter.valueForStringParameter(this, '/seahaven/dynamodb/cmk-arn'),
);
dynamodbCmk.grantDecrypt(this.syncLambda);
// Grant read/write to the KB docs bucket (read to list+delete old, write new)
props.kbDocsBucket.grantReadWrite(this.syncLambda);