Automating secure CloudWatch Alarms with CDK Monitoring Constructs and encrypted SNS
The AWS Well-Architected Framework describes how to implement cloud applications. For example, the Reliability Pillar advises architects and developers to set up proper monitoring, such as using CloudWatch Alarms. The Security Pillar defines how to build secure applications. Since both need to be combined in cloud applications, this blog post describes how to configure secure CloudWatch Alarms. This blog post is based on AWS CDK as an Infrastructure as Code solution. The example project uses a Lambda function that will be enhanced with CloudWatch Alarms. The Alarms are created using cdk-monitoring-constructs, a construct that can automatically set up monitoring for CDK projects. To ensure a secure configuration, it uses cdk-nag, which throws error messages for insecure AWS resources. Initial project setup This example is based on a new TypeScript CDK project. First, install cdk-monitoring-constructs and cdk-nag to enable these features. npm install cdk-monitoring-constructs npm install cdk-nag A simple Lambda function is added. For this Lambda function, we will create CloudWatch Alarms later. new nodeLambda.NodejsFunction(this, 'MyFunction', { runtime: lambda.Runtime.NODEJS_22_X, entry: join(__dirname, '../src/my-lambda.ts'), handler: 'handler', }); Monitoring configuration The main setup is done for cdk-monitoring-constructs. The SNS Topic will send email notifications when an alarm is triggered. const alarmTopic = new sns.Topic(this, 'AlarmTopic'); alarmTopic.addSubscription(new subscription.EmailSubscription('example@example.org')); The MonitoringFacade defines the base configuration for cdk-monitoring-constructs. In this example, the CloudWatch dashboard isn't used, so createDashboard is set to false. The properties in alarmFactoryDefaults are set to very sensitive values - so alarms are triggered quickly. const monitoring = new MonitoringFacade(this, "Monitoring", { dashboardFactory: new DefaultDashboardFactory(this, 'Dashboard', { createDashboard: false, dashboardNamePrefix: '', }), alarmFactoryDefaults: { alarmNamePrefix: 'MyApp', actionsEnabled: true, action: new SnsAlarmActionStrategy({ onAlarmTopic: alarmTopic, }), evaluationPeriods: 1, datapointsToAlarm: 1, }, }); Now there are two options. It is possible to create alarms for a specific resource, such as the Lambda function. monitoring.monitorLambdaFunction({ lambdaFunction: myLambda, addFaultCountAlarm: { Critical: { maxErrorCount: 0, }, }, }); Or, alarms can be created for all resource of a specific type created in the project. The configuration also defines the alarm metric and threshold. monitoring.monitorScope(this, { lambda: { props: { addFaultCountAlarm: { Critical: { maxErrorCount: 0, }, }, }, }, }); Check and improve security It's time to verify that the application is secure. This is done be done using cdk-nag. This line in the CDK application (bin folder) enables the cdk-nag AWS Solutions Checks cdk.Aspects.of(app).add(new AwsSolutionsChecks()); When running cdk synth, cdk-nag displays an error: The SNS theme does not require publishers to use SSL. So let's enable enforceSSL to have a secure configuration. const alarmTopic = new sns.Topic(this, 'AlarmTopic', { enforceSSL: true, }); To test the alarm, change the Lambda function so that it always fails, for example, upload invalid code. Then run the lambda function and wait for the alarm to become active. The alarm history will show state transitions and errors while the alarm action is triggered. The error messages indicates that it can't publish messages to the SNS topic. Enabling enforceSSL has set a deny action for traffic without SSL encryption on the SNS Topic. CloudWatch can't publish messages anymore because no permissions were granted. So explicitly grant the publish permissions: alarmTopic.grantPublish(new iam.ServicePrincipal('cloudwatch')); After this change, SNS notifications will be sent to all subscribers. Let's increase the security even more. SNS Topics also support KMS encryption, so specify a KMS key as masterKey. const alarmTopic = new sns.Topic(this, 'AlarmTopic', { enforceSSL: true, masterKey: kmsKey, }); Alarm notifications will fail again, see the alarm history. Similar to the previous error, permissions for the CloudWatch service are missing in the KMS Key. So let's grant the encrypt/decrypt permissions. kmsKey.grantEncryptDecrypt(new iam.ServicePrincipal('cloudwatch')); Another test shows that alarm notifications can be successfully sent to the KMS-encrypted SNS topic - everything works! Triggering CloudWatch Alarms manually Sometimes it is also helpful to trigger CloudWatch alarms manually. You don't need to implement a wrong lambda function to trigger an alarm, just use this CLI command. aws cloudwatch set-a

The AWS Well-Architected Framework describes how to implement cloud applications. For example, the Reliability Pillar advises architects and developers to set up proper monitoring, such as using CloudWatch Alarms. The Security Pillar defines how to build secure applications. Since both need to be combined in cloud applications, this blog post describes how to configure secure CloudWatch Alarms.
This blog post is based on AWS CDK as an Infrastructure as Code solution. The example project uses a Lambda function that will be enhanced with CloudWatch Alarms. The Alarms are created using cdk-monitoring-constructs, a construct that can automatically set up monitoring for CDK projects. To ensure a secure configuration, it uses cdk-nag, which throws error messages for insecure AWS resources.
Initial project setup
This example is based on a new TypeScript CDK project. First, install cdk-monitoring-constructs and cdk-nag to enable these features.
npm install cdk-monitoring-constructs
npm install cdk-nag
A simple Lambda function is added. For this Lambda function, we will create CloudWatch Alarms later.
new nodeLambda.NodejsFunction(this, 'MyFunction', {
runtime: lambda.Runtime.NODEJS_22_X,
entry: join(__dirname, '../src/my-lambda.ts'),
handler: 'handler',
});
Monitoring configuration
The main setup is done for cdk-monitoring-constructs. The SNS Topic will send email notifications when an alarm is triggered.
const alarmTopic = new sns.Topic(this, 'AlarmTopic');
alarmTopic.addSubscription(new subscription.EmailSubscription('example@example.org'));
The MonitoringFacade defines the base configuration for cdk-monitoring-constructs. In this example, the CloudWatch dashboard isn't used, so createDashboard
is set to false
. The properties in alarmFactoryDefaults
are set to very sensitive values - so alarms are triggered quickly.
const monitoring = new MonitoringFacade(this, "Monitoring", {
dashboardFactory: new DefaultDashboardFactory(this, 'Dashboard', {
createDashboard: false,
dashboardNamePrefix: '',
}),
alarmFactoryDefaults: {
alarmNamePrefix: 'MyApp',
actionsEnabled: true,
action: new SnsAlarmActionStrategy({
onAlarmTopic: alarmTopic,
}),
evaluationPeriods: 1,
datapointsToAlarm: 1,
},
});
Now there are two options. It is possible to create alarms for a specific resource, such as the Lambda function.
monitoring.monitorLambdaFunction({
lambdaFunction: myLambda,
addFaultCountAlarm: {
Critical: { maxErrorCount: 0, },
},
});
Or, alarms can be created for all resource of a specific type created in the project. The configuration also defines the alarm metric and threshold.
monitoring.monitorScope(this, {
lambda: {
props: {
addFaultCountAlarm: {
Critical: { maxErrorCount: 0, },
},
},
},
});
Check and improve security
It's time to verify that the application is secure. This is done be done using cdk-nag. This line in the CDK application (bin folder) enables the cdk-nag AWS Solutions Checks
cdk.Aspects.of(app).add(new AwsSolutionsChecks());
When running cdk synth
, cdk-nag displays an error: The SNS theme does not require publishers to use SSL
. So let's enable enforceSSL
to have a secure configuration.
const alarmTopic = new sns.Topic(this, 'AlarmTopic', {
enforceSSL: true,
});
To test the alarm, change the Lambda function so that it always fails, for example, upload invalid code. Then run the lambda function and wait for the alarm to become active. The alarm history will show state transitions and errors while the alarm action is triggered. The error messages indicates that it can't publish messages to the SNS topic.
Enabling enforceSSL
has set a deny action for traffic without SSL encryption on the SNS Topic. CloudWatch can't publish messages anymore because no permissions were granted. So explicitly grant the publish permissions:
alarmTopic.grantPublish(new iam.ServicePrincipal('cloudwatch'));
After this change, SNS notifications will be sent to all subscribers. Let's increase the security even more. SNS Topics also support KMS encryption, so specify a KMS key as masterKey
.
const alarmTopic = new sns.Topic(this, 'AlarmTopic', {
enforceSSL: true,
masterKey: kmsKey,
});
Alarm notifications will fail again, see the alarm history.
Similar to the previous error, permissions for the CloudWatch service are missing in the KMS Key. So let's grant the encrypt/decrypt permissions.
kmsKey.grantEncryptDecrypt(new iam.ServicePrincipal('cloudwatch'));
Another test shows that alarm notifications can be successfully sent to the KMS-encrypted SNS topic - everything works!
Triggering CloudWatch Alarms manually
Sometimes it is also helpful to trigger CloudWatch alarms manually. You don't need to implement a wrong lambda function to trigger an alarm, just use this CLI command.
aws cloudwatch set-alarm-state \
--alarm-name "MyApp-MyFunction-Fault-Count-Critical" \
--state-value ALARM --state-reason "Alarm test"
Instead of the ALARM
value, you can also specify OK
to reset the alarm.
Summary and lessons learned
You've learned how to create CloudWatch Alarms with cdk-monitoring-constructs - and how to implement a secure configuration:
- Enforce SSL in the SNS topic and specify a KMS key.
- Grant permissions for the CloudWatch service in the KMS topic and KMS key.
If you encounter problems with alarm actions, always check the alarm history in the CloudWatch console. It shows the error message that occurred when the alarm was delivered.