import * as cdk from "aws-cdk-lib"; import * as kms from "aws-cdk-lib/aws-kms"; import * as iam from "aws-cdk-lib/aws-iam"; import { Construct } from "constructs"; /** * Dedicated customer-managed CMK for encrypting sensitive CloudWatch Logs * groups (audit M-24 / INFRA-96). * * Used by the account CloudTrail log group and the finance/PII Lambda log * groups (exec-aide, payments, po/vendor email processors, qbo). This is a * SEPARATE key from `alias/seahaven-alarm-topics` (SNS alarm topics) and * `alias/cloudtrail-logs` (the CloudTrail S3 log-file CMK) — those have * different service principals and encryption contexts. * * The key policy MUST grant the CloudWatch Logs service principal use of the * key, scoped by the `kms:EncryptionContext:aws:logs:arn` condition, or log * delivery to any CMK-encrypted group silently stops. (AWS docs: "Encrypt log * data in CloudWatch Logs using AWS KMS".) Cross-reviewed 2026-06-08 * (INFRA-96): dropped `Describe*` to the specific `DescribeKey`; CreateGrant is * not required for plain log-group encryption so it is omitted. */ export class LogsKey extends Construct { public readonly key: kms.Key; constructor(scope: Construct, id: string) { super(scope, id); const stack = cdk.Stack.of(this); this.key = new kms.Key(this, "Key", { alias: "seahaven-logs", description: "Encrypts sensitive CloudWatch Logs groups (CloudTrail + finance/PII Lambdas) — M-24/INFRA-96", enableKeyRotation: true, removalPolicy: cdk.RemovalPolicy.RETAIN, }); // CloudWatch Logs service principal must be able to use the key to deliver // (encrypt) and read (decrypt) log events. The encryption-context condition // binds the grant to this account's log groups only, so the key cannot be // used to decrypt arbitrary data under the logs service principal. this.key.addToResourcePolicy( new iam.PolicyStatement({ sid: "AllowCloudWatchLogsUseOfKey", effect: iam.Effect.ALLOW, principals: [ new iam.ServicePrincipal(`logs.${stack.region}.amazonaws.com`), ], actions: [ "kms:Encrypt*", "kms:Decrypt*", "kms:ReEncrypt*", "kms:GenerateDataKey*", "kms:DescribeKey", ], resources: ["*"], conditions: { ArnLike: { "kms:EncryptionContext:aws:logs:arn": `arn:${stack.partition}:logs:${stack.region}:${stack.account}:log-group:*`, }, }, }) ); new cdk.CfnOutput(this, "LogsKeyArn", { value: this.key.keyArn }); new cdk.CfnOutput(this, "LogsKeyAlias", { value: "alias/seahaven-logs" }); } }