diff --git a/lib/cis-monitoring.ts b/lib/cis-monitoring.ts index 0939149..3d9d0fb 100644 --- a/lib/cis-monitoring.ts +++ b/lib/cis-monitoring.ts @@ -148,12 +148,35 @@ export class CisMonitoring extends Construct { constructor(scope: Construct, id: string, props: CisMonitoringProps) { super(scope, id); - // Dedicated topic for security/CIS alarms, encrypted with the AWS-managed - // SNS key (also clears audit L-14 for this topic). + // Customer-managed key for alarm topics (audit L-14). The AWS-managed + // alias/aws/sns key CANNOT be used here: its key policy can't grant + // cloudwatch.amazonaws.com, so CloudWatch alarms silently fail to publish + // to topics it encrypts — which is exactly what these topics receive. + // Also used by the unmanaged site-alerts topic (set via CLI; ARN output below). + const alarmTopicKey = new kms.Key(this, "AlarmTopicKey", { + alias: "seahaven-alarm-topics", + description: + "SSE for SNS alarm topics; grants CloudWatch alarms publish-side usage", + enableKeyRotation: true, + }); + alarmTopicKey.addToResourcePolicy( + new cdk.aws_iam.PolicyStatement({ + sid: "AllowCloudWatchAlarmsUse", + principals: [new cdk.aws_iam.ServicePrincipal("cloudwatch.amazonaws.com")], + actions: ["kms:GenerateDataKey*", "kms:Decrypt", "kms:DescribeKey"], + resources: ["*"], + conditions: { + StringEquals: { "aws:SourceAccount": cdk.Stack.of(this).account }, + }, + }) + ); + new cdk.CfnOutput(this, "AlarmTopicKeyArn", { value: alarmTopicKey.keyArn }); + + // Dedicated topic for security/CIS alarms (audit H-1, L-14). const topic = new sns.Topic(this, "CisAlarmTopic", { topicName: "seahaven-cis-alarms", displayName: "Sea Haven CIS / security alarms", - masterKey: kms.Alias.fromAliasName(this, "SnsKey", "alias/aws/sns"), + masterKey: alarmTopicKey, }); topic.addSubscription(new subscriptions.EmailSubscription(props.alarmEmail));