seahaven-account-baseline/lib/alarm-topic-stack.ts

69 lines
2.9 KiB
TypeScript
Raw Normal View History

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 * as sns from "aws-cdk-lib/aws-sns";
import { Construct } from "constructs";
/**
* Shared CloudWatch-alarm SNS topic (`site-alerts`) + its CMK for a member
* account. First tenant: seahaven-prod, for the procurement-ingest migration
* (its stacks import the topic by constructed ARN `site-alerts` in-account).
*
* mgmt's equivalent topic is unmanaged (created via CLI, acknowledged debt in
* cis-monitoring.ts) - this stack codifies the same working pattern instead of
* replicating the debt:
* - CMK `alias/seahaven-alarm-topics`, NOT alias/aws/sns: the AWS-managed SNS
* key's policy cannot grant cloudwatch.amazonaws.com, so alarms silently
* fail to publish through it.
* - Key policy grants cloudwatch.amazonaws.com only (SourceAccount-scoped).
* Subscribers (AWS Chatbot -> Slack) need no KMS grant: SNS decrypts at
* delivery. Verified live by mgmt's site-alerts + Chatbot wiring.
* - Chatbot workspace auth + channel config are console-only (per-account)
* and deliberately out of scope here; verify delivery post-deploy with
* `aws sns publish` + a Slack message check.
*/
export class AlarmTopicStack extends cdk.Stack {
public readonly topic: sns.Topic;
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
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,
removalPolicy: cdk.RemovalPolicy.RETAIN,
});
// GenerateDataKey* is the publish-side envelope-encryption call CloudWatch
// makes when writing to an encrypted topic; Decrypt covers retried
// deliveries re-reading its own envelope. Both are required for alarms to
// publish at all.
alarmTopicKey.addToResourcePolicy(
new iam.PolicyStatement({
sid: "AllowCloudWatchAlarmsUse",
principals: [new iam.ServicePrincipal("cloudwatch.amazonaws.com")],
actions: ["kms:GenerateDataKey*", "kms:Decrypt", "kms:DescribeKey"],
resources: ["*"],
conditions: {
StringEquals: { "aws:SourceAccount": this.account },
},
}),
);
this.topic = new sns.Topic(this, "SiteAlertsTopic", {
topicName: "site-alerts",
displayName: "Sea Haven operational alarms",
masterKey: alarmTopicKey,
});
cdk.Tags.of(this).add("Project", "account-baseline");
cdk.Tags.of(this).add("Owner", "adam@seahavenind.com");
cdk.Tags.of(this).add("Environment", "prod");
cdk.Tags.of(this).add("ManagedBy", "cdk");
new cdk.CfnOutput(this, "SiteAlertsTopicArn", { value: this.topic.topicArn });
new cdk.CfnOutput(this, "AlarmTopicKeyArn", { value: alarmTopicKey.keyArn });
}
}