Skip to content

Commit

Permalink
feat(waf-v2): add ability to alarm on blocked requests count and rate (
Browse files Browse the repository at this point in the history
…#314)

---

_By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license_
  • Loading branch information
echeung-amzn authored Jan 27, 2023
1 parent 79d9fd4 commit 7dfbbc7
Show file tree
Hide file tree
Showing 6 changed files with 402 additions and 3 deletions.
72 changes: 72 additions & 0 deletions API.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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


Expand Down
1 change: 1 addition & 0 deletions lib/common/monitoring/alarms/ErrorAlarmFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export enum ErrorType {
WRITE_ERROR = "WriteError",
EXPIRED = "Expired",
KILLED = "Killed",
BLOCKED = "Blocked",
}

export interface ErrorCountThreshold extends CustomAlarmThreshold {
Expand Down
58 changes: 56 additions & 2 deletions lib/monitoring/aws-wafv2/WafV2Monitoring.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import { GraphWidget, IWidget } from "aws-cdk-lib/aws-cloudwatch";
import {
GraphWidget,
HorizontalAnnotation,
IWidget,
} from "aws-cdk-lib/aws-cloudwatch";
import {
WafV2MetricFactory,
WafV2MetricFactoryProps,
} from "./WafV2MetricFactory";
import {
AlarmFactory,
BaseMonitoringProps,
CountAxisFromZero,
DefaultGraphWidgetHeight,
DefaultSummaryWidgetHeight,
ErrorAlarmFactory,
ErrorCountThreshold,
ErrorRateThreshold,
ErrorType,
MetricWithAlarmSupport,
Monitoring,
MonitoringScope,
Expand All @@ -23,7 +32,10 @@ export interface WafV2MonitoringOptions extends BaseMonitoringProps {}

export interface WafV2MonitoringProps
extends WafV2MetricFactoryProps,
WafV2MonitoringOptions {}
WafV2MonitoringOptions {
readonly addBlockedRequestsCountAlarm?: Record<string, ErrorCountThreshold>;
readonly addBlockedRequestsRateAlarm?: Record<string, ErrorRateThreshold>;
}

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

readonly alarmFactory: AlarmFactory;
readonly errorAlarmFactory: ErrorAlarmFactory;

readonly errorCountAnnotations: HorizontalAnnotation[];
readonly errorRateAnnotations: HorizontalAnnotation[];

readonly allowedRequestsMetric: MetricWithAlarmSupport;
readonly blockedRequestsMetric: MetricWithAlarmSupport;
readonly blockedRequestsRateMetric: MetricWithAlarmSupport;
Expand All @@ -46,6 +64,15 @@ export class WafV2Monitoring extends Monitoring {
});
this.humanReadableName = namingStrategy.resolveHumanReadableName();

this.alarmFactory = this.createAlarmFactory(
namingStrategy.resolveAlarmFriendlyName()
);

this.errorAlarmFactory = new ErrorAlarmFactory(this.alarmFactory);

this.errorCountAnnotations = [];
this.errorRateAnnotations = [];

const metricFactory = new WafV2MetricFactory(
scope.createMetricFactory(),
props
Expand All @@ -54,6 +81,31 @@ export class WafV2Monitoring extends Monitoring {
this.allowedRequestsMetric = metricFactory.metricAllowedRequests();
this.blockedRequestsMetric = metricFactory.metricBlockedRequests();
this.blockedRequestsRateMetric = metricFactory.metricBlockedRequestsRate();

for (const disambiguator in props.addBlockedRequestsCountAlarm) {
const alarmProps = props.addBlockedRequestsCountAlarm[disambiguator];
const createdAlarm = this.errorAlarmFactory.addErrorCountAlarm(
this.blockedRequestsMetric,
ErrorType.BLOCKED,
alarmProps,
disambiguator
);
this.errorCountAnnotations.push(createdAlarm.annotation);
this.addAlarm(createdAlarm);
}
for (const disambiguator in props.addBlockedRequestsRateAlarm) {
const alarmProps = props.addBlockedRequestsRateAlarm[disambiguator];
const createdAlarm = this.errorAlarmFactory.addErrorRateAlarm(
this.blockedRequestsRateMetric,
ErrorType.BLOCKED,
alarmProps,
disambiguator
);
this.errorRateAnnotations.push(createdAlarm.annotation);
this.addAlarm(createdAlarm);
}

props.useCreatedAlarms?.consume(this.createdAlarms());
}

summaryWidgets(): IWidget[] {
Expand Down Expand Up @@ -103,6 +155,7 @@ export class WafV2Monitoring extends Monitoring {
height,
title: "Blocked Requests",
left: [this.blockedRequestsMetric],
leftAnnotations: this.errorCountAnnotations,
leftYAxis: CountAxisFromZero,
});
}
Expand All @@ -113,6 +166,7 @@ export class WafV2Monitoring extends Monitoring {
height,
title: "Blocked Requests (rate)",
left: [this.blockedRequestsRateMetric],
leftAnnotations: this.errorRateAnnotations,
leftYAxis: RateAxisFromZero,
});
}
Expand Down
40 changes: 40 additions & 0 deletions test/monitoring/aws-wafv2/WafV2Monitoring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,43 @@ test("snapshot test: no alarms", () => {
addMonitoringDashboardsToStack(stack, monitoring);
expect(Template.fromStack(stack)).toMatchSnapshot();
});

test("snapshot test: all alarms", () => {
const stack = new Stack();
const acl = new CfnWebACL(stack, "DummyAcl", {
defaultAction: { allow: {} },
scope: "REGIONAL",
visibilityConfig: {
sampledRequestsEnabled: true,
cloudWatchMetricsEnabled: true,
metricName: "DummyMetricName",
},
});

const scope = new TestMonitoringScope(stack, "Scope");

let numAlarmsCreated = 0;

const monitoring = new WafV2Monitoring(scope, {
acl,
addBlockedRequestsCountAlarm: {
Warning: {
maxErrorCount: 5,
},
},
addBlockedRequestsRateAlarm: {
Warning: {
maxErrorRate: 0.05,
},
},
useCreatedAlarms: {
consume(alarms) {
numAlarmsCreated = alarms.length;
},
},
});

addMonitoringDashboardsToStack(stack, monitoring);
expect(numAlarmsCreated).toStrictEqual(2);
expect(Template.fromStack(stack)).toMatchSnapshot();
});
Loading

0 comments on commit 7dfbbc7

Please sign in to comment.