Skip to content

Commit 7dfbbc7

Browse files
author
Eugene Cheung
authored
feat(waf-v2): add ability to alarm on blocked requests count and rate (#314)
--- _By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license_
1 parent 79d9fd4 commit 7dfbbc7

File tree

6 files changed

+402
-3
lines changed

6 files changed

+402
-3
lines changed

API.md

Lines changed: 72 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ You can browse the documentation at https://constructs.dev/packages/cdk-monitori
9898
| AWS SNS Topic (`.monitorSnsTopic()`) | Message count, size, failed notifications | Failed notifications, min/max published messages | |
9999
| AWS SQS Queue (`.monitorSqsQueue()`, `.monitorSqsQueueWithDlq()`) | Message count, age, size | Message count, age, DLQ incoming messages | |
100100
| AWS Step Functions (`.monitorStepFunction()`, `.monitorStepFunctionActivity()`, `monitorStepFunctionLambdaIntegration()`, `.monitorStepFunctionServiceIntegration()`) | Execution count and breakdown per state | Duration, failed, failed rate, aborted, throttled, timed out executions | |
101-
| AWS Web Application Firewall (`.monitorWebApplicationFirewallAcl()`) | Allowed/blocked requests | | |
101+
| AWS Web Application Firewall (`.monitorWebApplicationFirewallAcl()`) | Allowed/blocked requests | Blocked requests count/rate | |
102102
| Custom metrics (`.monitorCustom()`) | Addition of custom metrics into the dashboard (each group is a widget) | | Supports anomaly detection |
103103

104104

lib/common/monitoring/alarms/ErrorAlarmFactory.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export enum ErrorType {
1919
WRITE_ERROR = "WriteError",
2020
EXPIRED = "Expired",
2121
KILLED = "Killed",
22+
BLOCKED = "Blocked",
2223
}
2324

2425
export interface ErrorCountThreshold extends CustomAlarmThreshold {

lib/monitoring/aws-wafv2/WafV2Monitoring.ts

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
1-
import { GraphWidget, IWidget } from "aws-cdk-lib/aws-cloudwatch";
1+
import {
2+
GraphWidget,
3+
HorizontalAnnotation,
4+
IWidget,
5+
} from "aws-cdk-lib/aws-cloudwatch";
26
import {
37
WafV2MetricFactory,
48
WafV2MetricFactoryProps,
59
} from "./WafV2MetricFactory";
610
import {
11+
AlarmFactory,
712
BaseMonitoringProps,
813
CountAxisFromZero,
914
DefaultGraphWidgetHeight,
1015
DefaultSummaryWidgetHeight,
16+
ErrorAlarmFactory,
17+
ErrorCountThreshold,
18+
ErrorRateThreshold,
19+
ErrorType,
1120
MetricWithAlarmSupport,
1221
Monitoring,
1322
MonitoringScope,
@@ -23,7 +32,10 @@ export interface WafV2MonitoringOptions extends BaseMonitoringProps {}
2332

2433
export interface WafV2MonitoringProps
2534
extends WafV2MetricFactoryProps,
26-
WafV2MonitoringOptions {}
35+
WafV2MonitoringOptions {
36+
readonly addBlockedRequestsCountAlarm?: Record<string, ErrorCountThreshold>;
37+
readonly addBlockedRequestsRateAlarm?: Record<string, ErrorRateThreshold>;
38+
}
2739

2840
/**
2941
* Monitoring for AWS Web Application Firewall.
@@ -33,6 +45,12 @@ export interface WafV2MonitoringProps
3345
export class WafV2Monitoring extends Monitoring {
3446
readonly humanReadableName: string;
3547

48+
readonly alarmFactory: AlarmFactory;
49+
readonly errorAlarmFactory: ErrorAlarmFactory;
50+
51+
readonly errorCountAnnotations: HorizontalAnnotation[];
52+
readonly errorRateAnnotations: HorizontalAnnotation[];
53+
3654
readonly allowedRequestsMetric: MetricWithAlarmSupport;
3755
readonly blockedRequestsMetric: MetricWithAlarmSupport;
3856
readonly blockedRequestsRateMetric: MetricWithAlarmSupport;
@@ -46,6 +64,15 @@ export class WafV2Monitoring extends Monitoring {
4664
});
4765
this.humanReadableName = namingStrategy.resolveHumanReadableName();
4866

67+
this.alarmFactory = this.createAlarmFactory(
68+
namingStrategy.resolveAlarmFriendlyName()
69+
);
70+
71+
this.errorAlarmFactory = new ErrorAlarmFactory(this.alarmFactory);
72+
73+
this.errorCountAnnotations = [];
74+
this.errorRateAnnotations = [];
75+
4976
const metricFactory = new WafV2MetricFactory(
5077
scope.createMetricFactory(),
5178
props
@@ -54,6 +81,31 @@ export class WafV2Monitoring extends Monitoring {
5481
this.allowedRequestsMetric = metricFactory.metricAllowedRequests();
5582
this.blockedRequestsMetric = metricFactory.metricBlockedRequests();
5683
this.blockedRequestsRateMetric = metricFactory.metricBlockedRequestsRate();
84+
85+
for (const disambiguator in props.addBlockedRequestsCountAlarm) {
86+
const alarmProps = props.addBlockedRequestsCountAlarm[disambiguator];
87+
const createdAlarm = this.errorAlarmFactory.addErrorCountAlarm(
88+
this.blockedRequestsMetric,
89+
ErrorType.BLOCKED,
90+
alarmProps,
91+
disambiguator
92+
);
93+
this.errorCountAnnotations.push(createdAlarm.annotation);
94+
this.addAlarm(createdAlarm);
95+
}
96+
for (const disambiguator in props.addBlockedRequestsRateAlarm) {
97+
const alarmProps = props.addBlockedRequestsRateAlarm[disambiguator];
98+
const createdAlarm = this.errorAlarmFactory.addErrorRateAlarm(
99+
this.blockedRequestsRateMetric,
100+
ErrorType.BLOCKED,
101+
alarmProps,
102+
disambiguator
103+
);
104+
this.errorRateAnnotations.push(createdAlarm.annotation);
105+
this.addAlarm(createdAlarm);
106+
}
107+
108+
props.useCreatedAlarms?.consume(this.createdAlarms());
57109
}
58110

59111
summaryWidgets(): IWidget[] {
@@ -103,6 +155,7 @@ export class WafV2Monitoring extends Monitoring {
103155
height,
104156
title: "Blocked Requests",
105157
left: [this.blockedRequestsMetric],
158+
leftAnnotations: this.errorCountAnnotations,
106159
leftYAxis: CountAxisFromZero,
107160
});
108161
}
@@ -113,6 +166,7 @@ export class WafV2Monitoring extends Monitoring {
113166
height,
114167
title: "Blocked Requests (rate)",
115168
left: [this.blockedRequestsRateMetric],
169+
leftAnnotations: this.errorRateAnnotations,
116170
leftYAxis: RateAxisFromZero,
117171
});
118172
}

test/monitoring/aws-wafv2/WafV2Monitoring.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,43 @@ test("snapshot test: no alarms", () => {
2525
addMonitoringDashboardsToStack(stack, monitoring);
2626
expect(Template.fromStack(stack)).toMatchSnapshot();
2727
});
28+
29+
test("snapshot test: all alarms", () => {
30+
const stack = new Stack();
31+
const acl = new CfnWebACL(stack, "DummyAcl", {
32+
defaultAction: { allow: {} },
33+
scope: "REGIONAL",
34+
visibilityConfig: {
35+
sampledRequestsEnabled: true,
36+
cloudWatchMetricsEnabled: true,
37+
metricName: "DummyMetricName",
38+
},
39+
});
40+
41+
const scope = new TestMonitoringScope(stack, "Scope");
42+
43+
let numAlarmsCreated = 0;
44+
45+
const monitoring = new WafV2Monitoring(scope, {
46+
acl,
47+
addBlockedRequestsCountAlarm: {
48+
Warning: {
49+
maxErrorCount: 5,
50+
},
51+
},
52+
addBlockedRequestsRateAlarm: {
53+
Warning: {
54+
maxErrorRate: 0.05,
55+
},
56+
},
57+
useCreatedAlarms: {
58+
consume(alarms) {
59+
numAlarmsCreated = alarms.length;
60+
},
61+
},
62+
});
63+
64+
addMonitoringDashboardsToStack(stack, monitoring);
65+
expect(numAlarmsCreated).toStrictEqual(2);
66+
expect(Template.fromStack(stack)).toMatchSnapshot();
67+
});

0 commit comments

Comments
 (0)