@composurecdk/wafv2 0.0.0-stage → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +240 -2
- package/dist/commonjs/alarm-config.d.ts +60 -0
- package/dist/commonjs/alarm-config.d.ts.map +1 -0
- package/dist/commonjs/alarm-config.js +3 -0
- package/dist/commonjs/alarm-config.js.map +1 -0
- package/dist/commonjs/alarm-defaults.d.ts +34 -0
- package/dist/commonjs/alarm-defaults.d.ts.map +1 -0
- package/dist/commonjs/alarm-defaults.js +37 -0
- package/dist/commonjs/alarm-defaults.js.map +1 -0
- package/dist/commonjs/alarms.d.ts +18 -0
- package/dist/commonjs/alarms.d.ts.map +1 -0
- package/dist/commonjs/alarms.js +94 -0
- package/dist/commonjs/alarms.js.map +1 -0
- package/dist/commonjs/association.d.ts +21 -0
- package/dist/commonjs/association.d.ts.map +1 -0
- package/dist/commonjs/association.js +61 -0
- package/dist/commonjs/association.js.map +1 -0
- package/dist/commonjs/custom-response.d.ts +52 -0
- package/dist/commonjs/custom-response.d.ts.map +1 -0
- package/dist/commonjs/custom-response.js +137 -0
- package/dist/commonjs/custom-response.js.map +1 -0
- package/dist/commonjs/defaults.d.ts +89 -0
- package/dist/commonjs/defaults.d.ts.map +1 -0
- package/dist/commonjs/defaults.js +90 -0
- package/dist/commonjs/defaults.js.map +1 -0
- package/dist/commonjs/index.d.ts +30 -0
- package/dist/commonjs/index.d.ts.map +1 -0
- package/dist/commonjs/index.js +35 -0
- package/dist/commonjs/index.js.map +1 -0
- package/dist/commonjs/logging-filter.d.ts +27 -0
- package/dist/commonjs/logging-filter.d.ts.map +1 -0
- package/dist/commonjs/logging-filter.js +17 -0
- package/dist/commonjs/logging-filter.js.map +1 -0
- package/dist/commonjs/logging.d.ts +59 -0
- package/dist/commonjs/logging.d.ts.map +1 -0
- package/dist/commonjs/logging.js +91 -0
- package/dist/commonjs/logging.js.map +1 -0
- package/dist/commonjs/metrics.d.ts +58 -0
- package/dist/commonjs/metrics.d.ts.map +1 -0
- package/dist/commonjs/metrics.js +67 -0
- package/dist/commonjs/metrics.js.map +1 -0
- package/dist/commonjs/names.d.ts +46 -0
- package/dist/commonjs/names.d.ts.map +1 -0
- package/dist/commonjs/names.js +140 -0
- package/dist/commonjs/names.js.map +1 -0
- package/dist/commonjs/package.json +3 -0
- package/dist/commonjs/rule-builder.d.ts +123 -0
- package/dist/commonjs/rule-builder.d.ts.map +1 -0
- package/dist/commonjs/rule-builder.js +163 -0
- package/dist/commonjs/rule-builder.js.map +1 -0
- package/dist/commonjs/scope.d.ts +15 -0
- package/dist/commonjs/scope.d.ts.map +1 -0
- package/dist/commonjs/scope.js +35 -0
- package/dist/commonjs/scope.js.map +1 -0
- package/dist/commonjs/web-acl-builder.d.ts +152 -0
- package/dist/commonjs/web-acl-builder.d.ts.map +1 -0
- package/dist/commonjs/web-acl-builder.js +205 -0
- package/dist/commonjs/web-acl-builder.js.map +1 -0
- package/dist/esm/alarm-config.d.ts +60 -0
- package/dist/esm/alarm-config.d.ts.map +1 -0
- package/dist/esm/alarm-config.js +2 -0
- package/dist/esm/alarm-config.js.map +1 -0
- package/dist/esm/alarm-defaults.d.ts +34 -0
- package/dist/esm/alarm-defaults.d.ts.map +1 -0
- package/dist/esm/alarm-defaults.js +34 -0
- package/dist/esm/alarm-defaults.js.map +1 -0
- package/dist/esm/alarms.d.ts +18 -0
- package/dist/esm/alarms.d.ts.map +1 -0
- package/dist/esm/alarms.js +90 -0
- package/dist/esm/alarms.js.map +1 -0
- package/dist/esm/association.d.ts +21 -0
- package/dist/esm/association.d.ts.map +1 -0
- package/dist/esm/association.js +58 -0
- package/dist/esm/association.js.map +1 -0
- package/dist/esm/custom-response.d.ts +52 -0
- package/dist/esm/custom-response.d.ts.map +1 -0
- package/dist/esm/custom-response.js +133 -0
- package/dist/esm/custom-response.js.map +1 -0
- package/dist/esm/defaults.d.ts +89 -0
- package/dist/esm/defaults.d.ts.map +1 -0
- package/dist/esm/defaults.js +87 -0
- package/dist/esm/defaults.js.map +1 -0
- package/dist/esm/index.d.ts +30 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +23 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/logging-filter.d.ts +27 -0
- package/dist/esm/logging-filter.d.ts.map +1 -0
- package/dist/esm/logging-filter.js +14 -0
- package/dist/esm/logging-filter.js.map +1 -0
- package/dist/esm/logging.d.ts +59 -0
- package/dist/esm/logging.d.ts.map +1 -0
- package/dist/esm/logging.js +88 -0
- package/dist/esm/logging.js.map +1 -0
- package/dist/esm/metrics.d.ts +58 -0
- package/dist/esm/metrics.d.ts.map +1 -0
- package/dist/esm/metrics.js +63 -0
- package/dist/esm/metrics.js.map +1 -0
- package/dist/esm/names.d.ts +46 -0
- package/dist/esm/names.d.ts.map +1 -0
- package/dist/esm/names.js +132 -0
- package/dist/esm/names.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/rule-builder.d.ts +123 -0
- package/dist/esm/rule-builder.d.ts.map +1 -0
- package/dist/esm/rule-builder.js +160 -0
- package/dist/esm/rule-builder.js.map +1 -0
- package/dist/esm/scope.d.ts +15 -0
- package/dist/esm/scope.d.ts.map +1 -0
- package/dist/esm/scope.js +32 -0
- package/dist/esm/scope.js.map +1 -0
- package/dist/esm/web-acl-builder.d.ts +152 -0
- package/dist/esm/web-acl-builder.d.ts.map +1 -0
- package/dist/esm/web-acl-builder.js +202 -0
- package/dist/esm/web-acl-builder.js.map +1 -0
- package/package.json +78 -4
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { AlarmConfig } from "@composurecdk/cloudwatch";
|
|
2
|
+
/**
|
|
3
|
+
* Controls the web ACL's recommended alarms. They are per rule — see
|
|
4
|
+
* {@link WebAclRuleAlarmConfig} — so this is the master switch.
|
|
5
|
+
*/
|
|
6
|
+
export interface WebAclAlarmConfig {
|
|
7
|
+
/**
|
|
8
|
+
* Set to `false` to create none of the recommended alarms. Alarms added
|
|
9
|
+
* with `addAlarm()` are unaffected.
|
|
10
|
+
* @default true
|
|
11
|
+
*/
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Controls one rule's recommended alarms, set with the rule builder's
|
|
16
|
+
* `.alarms()`. Set an alarm to `false` to disable it, or to an
|
|
17
|
+
* {@link AlarmConfig} to enable it (if off by default) or tune it. Defaults
|
|
18
|
+
* are in {@link WEB_ACL_RULE_ALARM_DEFAULTS}.
|
|
19
|
+
*
|
|
20
|
+
* Every alarm treats missing data as not breaching: WAF publishes a
|
|
21
|
+
* datapoint only when the count is non-zero.
|
|
22
|
+
*
|
|
23
|
+
* AWS publishes no recommended alarms for WAF, so the thresholds are this
|
|
24
|
+
* library's starting points.
|
|
25
|
+
*/
|
|
26
|
+
export interface WebAclRuleAlarmConfig {
|
|
27
|
+
/**
|
|
28
|
+
* Set to `false` to disable all of this rule's recommended alarms.
|
|
29
|
+
* @default true
|
|
30
|
+
*/
|
|
31
|
+
enabled?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Alarm when the rule blocks requests: `BlockedRequests` (Sum, 5 minutes)
|
|
34
|
+
* above the threshold.
|
|
35
|
+
*
|
|
36
|
+
* On by default for a rule you wrote whose action is Block, where a block
|
|
37
|
+
* is a signal — a throttled client, a request missing its credentials.
|
|
38
|
+
* Off by default for a rule-group rule, whose managed rules block internet
|
|
39
|
+
* background noise all day; pass an {@link AlarmConfig} to turn it on.
|
|
40
|
+
* Never created for a rule that does not block.
|
|
41
|
+
*
|
|
42
|
+
* Default threshold: > 0 blocked requests in one 5-minute period.
|
|
43
|
+
*/
|
|
44
|
+
blockedRequests?: AlarmConfig | false;
|
|
45
|
+
/**
|
|
46
|
+
* Alarm when a pinned managed rule group version is about to expire:
|
|
47
|
+
* `DaysToExpiry` (Minimum, 1 hour) below the threshold. Once a version
|
|
48
|
+
* expires, WAF rejects every update to the web ACL that does not move
|
|
49
|
+
* the group to a current version.
|
|
50
|
+
*
|
|
51
|
+
* On by default for a managed rule group with a `version`; never created
|
|
52
|
+
* otherwise.
|
|
53
|
+
*
|
|
54
|
+
* Default threshold: < 30 days.
|
|
55
|
+
*
|
|
56
|
+
* @see https://docs.aws.amazon.com/waf/latest/developerguide/waf-managed-rule-groups-versioning-expiration.html
|
|
57
|
+
*/
|
|
58
|
+
daysToExpiry?: AlarmConfig | false;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=alarm-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alarm-config.d.ts","sourceRoot":"","sources":["../../src/alarm-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAE5D;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;IAEtC;;;;;;;;;;;;OAYG;IACH,YAAY,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC;CACpC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alarm-config.js","sourceRoot":"","sources":["../../src/alarm-config.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { TreatMissingData } from "aws-cdk-lib/aws-cloudwatch";
|
|
2
|
+
/**
|
|
3
|
+
* Default configuration of each rule's recommended alarms. AWS publishes no
|
|
4
|
+
* recommended alarms for WAF, so these are this library's starting points.
|
|
5
|
+
* Both treat missing data as not breaching, because WAF publishes a
|
|
6
|
+
* datapoint only when its value is non-zero.
|
|
7
|
+
*/
|
|
8
|
+
export declare const WEB_ACL_RULE_ALARM_DEFAULTS: {
|
|
9
|
+
/**
|
|
10
|
+
* Any block in a 5-minute period. A rule you wrote encodes your own
|
|
11
|
+
* contract — a credential every caller should send, a rate no client
|
|
12
|
+
* should reach — so a single block is worth a look. Raise the threshold
|
|
13
|
+
* on a rule that blocks routinely.
|
|
14
|
+
*/
|
|
15
|
+
blockedRequests: {
|
|
16
|
+
threshold: number;
|
|
17
|
+
evaluationPeriods: number;
|
|
18
|
+
datapointsToAlarm: number;
|
|
19
|
+
treatMissingData: TreatMissingData.NOT_BREACHING;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Fewer than 30 days before a pinned version expires, which leaves time to
|
|
23
|
+
* test and move to a newer one.
|
|
24
|
+
*
|
|
25
|
+
* @see https://docs.aws.amazon.com/waf/latest/developerguide/waf-managed-rule-groups-best-practice.html
|
|
26
|
+
*/
|
|
27
|
+
daysToExpiry: {
|
|
28
|
+
threshold: number;
|
|
29
|
+
evaluationPeriods: number;
|
|
30
|
+
datapointsToAlarm: number;
|
|
31
|
+
treatMissingData: TreatMissingData.NOT_BREACHING;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=alarm-defaults.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alarm-defaults.d.ts","sourceRoot":"","sources":["../../src/alarm-defaults.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAG9D;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B;IACtC;;;;;OAKG;;;;;;;IAQH;;;;;OAKG;;;;;;;CAO0C,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { TreatMissingData } from "aws-cdk-lib/aws-cloudwatch";
|
|
2
|
+
/**
|
|
3
|
+
* Default configuration of each rule's recommended alarms. AWS publishes no
|
|
4
|
+
* recommended alarms for WAF, so these are this library's starting points.
|
|
5
|
+
* Both treat missing data as not breaching, because WAF publishes a
|
|
6
|
+
* datapoint only when its value is non-zero.
|
|
7
|
+
*/
|
|
8
|
+
export const WEB_ACL_RULE_ALARM_DEFAULTS = {
|
|
9
|
+
/**
|
|
10
|
+
* Any block in a 5-minute period. A rule you wrote encodes your own
|
|
11
|
+
* contract — a credential every caller should send, a rate no client
|
|
12
|
+
* should reach — so a single block is worth a look. Raise the threshold
|
|
13
|
+
* on a rule that blocks routinely.
|
|
14
|
+
*/
|
|
15
|
+
blockedRequests: {
|
|
16
|
+
threshold: 0,
|
|
17
|
+
evaluationPeriods: 1,
|
|
18
|
+
datapointsToAlarm: 1,
|
|
19
|
+
treatMissingData: TreatMissingData.NOT_BREACHING,
|
|
20
|
+
},
|
|
21
|
+
/**
|
|
22
|
+
* Fewer than 30 days before a pinned version expires, which leaves time to
|
|
23
|
+
* test and move to a newer one.
|
|
24
|
+
*
|
|
25
|
+
* @see https://docs.aws.amazon.com/waf/latest/developerguide/waf-managed-rule-groups-best-practice.html
|
|
26
|
+
*/
|
|
27
|
+
daysToExpiry: {
|
|
28
|
+
threshold: 30,
|
|
29
|
+
evaluationPeriods: 1,
|
|
30
|
+
datapointsToAlarm: 1,
|
|
31
|
+
treatMissingData: TreatMissingData.NOT_BREACHING,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=alarm-defaults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alarm-defaults.js","sourceRoot":"","sources":["../../src/alarm-defaults.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAG9D;;;;;GAKG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC;;;;;OAKG;IACH,eAAe,EAAE;QACf,SAAS,EAAE,CAAC;QACZ,iBAAiB,EAAE,CAAC;QACpB,iBAAiB,EAAE,CAAC;QACpB,gBAAgB,EAAE,gBAAgB,CAAC,aAAa;KACjD;IAED;;;;;OAKG;IACH,YAAY,EAAE;QACZ,SAAS,EAAE,EAAE;QACb,iBAAiB,EAAE,CAAC;QACpB,iBAAiB,EAAE,CAAC;QACpB,gBAAgB,EAAE,gBAAgB,CAAC,aAAa;KACjD;CAC4C,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type AlarmDefinition } from "@composurecdk/cloudwatch";
|
|
2
|
+
import type { WebAclAlarmConfig } from "./alarm-config.js";
|
|
3
|
+
import { type WebAclMetrics } from "./metrics.js";
|
|
4
|
+
import type { BuiltRule } from "./rule-builder.js";
|
|
5
|
+
/**
|
|
6
|
+
* The alarm-key prefix for a rule: its name in camelCase, so `PerIpRate` and
|
|
7
|
+
* `per-ip-rate` both become `perIpRate`.
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export declare function ruleAlarmKey(name: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Resolves each rule's recommended alarms into {@link AlarmDefinition}s.
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveWebAclAlarmDefinitions(owner: string, metrics: WebAclMetrics, rules: readonly BuiltRule[], config: WebAclAlarmConfig | false | undefined): AlarmDefinition[];
|
|
18
|
+
//# sourceMappingURL=alarms.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alarms.d.ts","sourceRoot":"","sources":["../../src/alarms.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,eAAe,EAGrB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAInD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIjD;AAiBD;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,MAAM,EAAE,iBAAiB,GAAG,KAAK,GAAG,SAAS,GAC5C,eAAe,EAAE,CAkBnB"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { Token } from "aws-cdk-lib";
|
|
2
|
+
import { ComparisonOperator } from "aws-cdk-lib/aws-cloudwatch";
|
|
3
|
+
import { resolveAlarmConfig, } from "@composurecdk/cloudwatch";
|
|
4
|
+
import { WEB_ACL_RULE_ALARM_DEFAULTS } from "./alarm-defaults.js";
|
|
5
|
+
import { REQUEST_METRIC_PERIOD, } from "./metrics.js";
|
|
6
|
+
const PERIOD_LABEL = `${String(REQUEST_METRIC_PERIOD.toMinutes())} minutes`;
|
|
7
|
+
/**
|
|
8
|
+
* The alarm-key prefix for a rule: its name in camelCase, so `PerIpRate` and
|
|
9
|
+
* `per-ip-rate` both become `perIpRate`.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export function ruleAlarmKey(name) {
|
|
14
|
+
const parts = name.split(/[-_]+/).filter(Boolean);
|
|
15
|
+
const pascal = parts.map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
16
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
17
|
+
}
|
|
18
|
+
function isOff(config) {
|
|
19
|
+
return config === false || config?.enabled === false;
|
|
20
|
+
}
|
|
21
|
+
/** The managed rule group a statement pins to a version, if any. */
|
|
22
|
+
function pinnedGroup(statement) {
|
|
23
|
+
// The rule builder only takes a `StatementProperty`, never a token.
|
|
24
|
+
const group = statement.managedRuleGroupStatement;
|
|
25
|
+
if (group === undefined || Token.isUnresolved(group))
|
|
26
|
+
return undefined;
|
|
27
|
+
const { vendorName, name, version } = group;
|
|
28
|
+
return version === undefined ? undefined : { vendorName, name, version };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolves each rule's recommended alarms into {@link AlarmDefinition}s.
|
|
32
|
+
*
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
export function resolveWebAclAlarmDefinitions(owner, metrics, rules, config) {
|
|
36
|
+
if (isOff(config))
|
|
37
|
+
return [];
|
|
38
|
+
const keys = new Map();
|
|
39
|
+
return rules.flatMap((rule) => {
|
|
40
|
+
const definitions = resolveRuleAlarms(metrics, rule);
|
|
41
|
+
if (definitions.length === 0)
|
|
42
|
+
return [];
|
|
43
|
+
const { name } = rule.property;
|
|
44
|
+
const key = ruleAlarmKey(name);
|
|
45
|
+
const other = keys.get(key);
|
|
46
|
+
if (other !== undefined) {
|
|
47
|
+
throw new Error(`${owner}: rules "${other}" and "${name}" both have alarm key "${key}", because ` +
|
|
48
|
+
`alarm keys are rule names in camelCase. Rename one of them.`);
|
|
49
|
+
}
|
|
50
|
+
keys.set(key, name);
|
|
51
|
+
return definitions;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function definition(key, metric, comparisonOperator, userConfig, defaults, describe) {
|
|
55
|
+
const { alarmName, threshold, evaluationPeriods, datapointsToAlarm, treatMissingData } = resolveAlarmConfig(userConfig, defaults);
|
|
56
|
+
return {
|
|
57
|
+
key,
|
|
58
|
+
alarmName,
|
|
59
|
+
metric,
|
|
60
|
+
threshold,
|
|
61
|
+
comparisonOperator,
|
|
62
|
+
evaluationPeriods,
|
|
63
|
+
datapointsToAlarm,
|
|
64
|
+
treatMissingData,
|
|
65
|
+
description: describe(threshold),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function resolveRuleAlarms(metrics, rule) {
|
|
69
|
+
const config = rule.alarms;
|
|
70
|
+
if (isOff(config))
|
|
71
|
+
return [];
|
|
72
|
+
const { name } = rule.property;
|
|
73
|
+
const key = ruleAlarmKey(name);
|
|
74
|
+
const definitions = [];
|
|
75
|
+
const blocked = config ? config.blockedRequests : undefined;
|
|
76
|
+
// A rule group's managed rules block background noise, so its alarm is opt-in.
|
|
77
|
+
if (rule.blocks && blocked !== false && (!rule.ruleGroup || blocked !== undefined)) {
|
|
78
|
+
definitions.push(definition(`${key}BlockedRequests`, metrics.rule(name).blockedRequests(), ComparisonOperator.GREATER_THAN_THRESHOLD, blocked, WEB_ACL_RULE_ALARM_DEFAULTS.blockedRequests, (t) => `WAF rule "${name}" is blocking requests. Threshold: > ${String(t)} blocked requests in ${PERIOD_LABEL}.`));
|
|
79
|
+
}
|
|
80
|
+
const group = pinnedGroup(rule.property.statement);
|
|
81
|
+
const expiry = config ? config.daysToExpiry : undefined;
|
|
82
|
+
if (group && expiry !== false) {
|
|
83
|
+
const version = Token.isUnresolved(group.version)
|
|
84
|
+
? "its pinned version"
|
|
85
|
+
: `version ${group.version}`;
|
|
86
|
+
definitions.push(definition(`${key}DaysToExpiry`, metrics.daysToExpiry(group), ComparisonOperator.LESS_THAN_THRESHOLD, expiry, WEB_ACL_RULE_ALARM_DEFAULTS.daysToExpiry, (t) => `WAF rule "${name}" pins ${group.name} at ${version}, which expires in under ${String(t)} days. Move it to a newer version; WAF rejects updates to the web ACL once it expires.`));
|
|
87
|
+
}
|
|
88
|
+
return definitions;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=alarms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alarms.js","sourceRoot":"","sources":["../../src/alarms.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Stage } from "aws-cdk-lib/aws-apigateway";
|
|
2
|
+
import { CfnWebACLAssociation, type CfnWebACLAssociationProps } from "aws-cdk-lib/aws-wafv2";
|
|
3
|
+
import { type IConstruct } from "constructs";
|
|
4
|
+
/**
|
|
5
|
+
* A resource a regional web ACL can protect: an ARN, a CDK reference CDK
|
|
6
|
+
* itself accepts (an Application Load Balancer, an AppSync API, …), or an API
|
|
7
|
+
* Gateway REST API stage.
|
|
8
|
+
*
|
|
9
|
+
* The ARN and reference types are read from CDK's own `resourceArn` prop, so
|
|
10
|
+
* they track the installed `aws-cdk-lib` (ADR-0018).
|
|
11
|
+
*/
|
|
12
|
+
export type WebAclAssociationTarget = NonNullable<CfnWebACLAssociationProps["resourceArn"]> | Pick<Stage, "stageArn" | "node">;
|
|
13
|
+
/**
|
|
14
|
+
* Creates one `AWS::WAFv2::WebACLAssociation` per target, each depending on
|
|
15
|
+
* the one before it: AWS allows one `AssociateWebACL` call every two seconds
|
|
16
|
+
* per account and Region, so parallel associations throttle themselves.
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export declare function associate(scope: IConstruct, id: string, webAclArn: string, targets: WebAclAssociationTarget[]): CfnWebACLAssociation[];
|
|
21
|
+
//# sourceMappingURL=association.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"association.d.ts","sourceRoot":"","sources":["../../src/association.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,KAAK,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAC7F,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAExD;;;;;;;GAOG;AACH,MAAM,MAAM,uBAAuB,GACjC,WAAW,CAAC,yBAAyB,CAAC,aAAa,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAAC,CAAC;AA2B3F;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,UAAU,EACjB,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,uBAAuB,EAAE,GACjC,oBAAoB,EAAE,CAsBxB"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Stack } from "aws-cdk-lib";
|
|
2
|
+
import { CfnWebACLAssociation } from "aws-cdk-lib/aws-wafv2";
|
|
3
|
+
import { Construct } from "constructs";
|
|
4
|
+
function isStage(target) {
|
|
5
|
+
return typeof target === "object" && "stageArn" in target;
|
|
6
|
+
}
|
|
7
|
+
/** 32-bit FNV-1a, as 8 hex digits. */
|
|
8
|
+
function fnv1a(value) {
|
|
9
|
+
let hash = 0x811c9dc5;
|
|
10
|
+
for (let i = 0; i < value.length; i++) {
|
|
11
|
+
hash = Math.imul(hash ^ value.charCodeAt(i), 0x01000193) >>> 0;
|
|
12
|
+
}
|
|
13
|
+
return hash.toString(16).padStart(8, "0");
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A construct id suffix that follows the target, not its position: an id that
|
|
17
|
+
* moved when another target was removed would replace the association, and
|
|
18
|
+
* CloudFormation deletes the old one last, disassociating the resource.
|
|
19
|
+
*/
|
|
20
|
+
function targetKey(scope, target) {
|
|
21
|
+
const identity = Construct.isConstruct(target)
|
|
22
|
+
? target.node.path
|
|
23
|
+
: JSON.stringify(Stack.of(scope).resolve(target));
|
|
24
|
+
return fnv1a(identity);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Creates one `AWS::WAFv2::WebACLAssociation` per target, each depending on
|
|
28
|
+
* the one before it: AWS allows one `AssociateWebACL` call every two seconds
|
|
29
|
+
* per account and Region, so parallel associations throttle themselves.
|
|
30
|
+
*
|
|
31
|
+
* @internal
|
|
32
|
+
*/
|
|
33
|
+
export function associate(scope, id, webAclArn, targets) {
|
|
34
|
+
const associations = [];
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
for (const target of targets) {
|
|
37
|
+
const resourceArn = isStage(target) ? target.stageArn : target;
|
|
38
|
+
const key = targetKey(scope, target);
|
|
39
|
+
if (seen.has(key)) {
|
|
40
|
+
throw new Error(`WebAclBuilder "${id}": associateWith() was given the same resource twice.`);
|
|
41
|
+
}
|
|
42
|
+
seen.add(key);
|
|
43
|
+
const association = new CfnWebACLAssociation(scope, `${id}Association${key}`, {
|
|
44
|
+
webAclArn,
|
|
45
|
+
resourceArn,
|
|
46
|
+
});
|
|
47
|
+
// The ARN references the resource, which already orders the association
|
|
48
|
+
// after it; a construct is depended on explicitly in case it does not.
|
|
49
|
+
if (Construct.isConstruct(target))
|
|
50
|
+
association.node.addDependency(target);
|
|
51
|
+
const previous = associations.at(-1);
|
|
52
|
+
if (previous)
|
|
53
|
+
association.node.addDependency(previous);
|
|
54
|
+
associations.push(association);
|
|
55
|
+
}
|
|
56
|
+
return associations;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=association.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"association.js","sourceRoot":"","sources":["../../src/association.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEpC,OAAO,EAAE,oBAAoB,EAAkC,MAAM,uBAAuB,CAAC;AAC7F,OAAO,EAAE,SAAS,EAAmB,MAAM,YAAY,CAAC;AAaxD,SAAS,OAAO,CAAC,MAA+B;IAC9C,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,UAAU,IAAI,MAAM,CAAC;AAC5D,CAAC;AAED,sCAAsC;AACtC,SAAS,KAAK,CAAC,KAAa;IAC1B,IAAI,IAAI,GAAG,UAAU,CAAC;IACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,SAAS,SAAS,CAAC,KAAiB,EAAE,MAA+B;IACnE,MAAM,QAAQ,GAAG,SAAS,CAAC,WAAW,CAAC,MAAM,CAAC;QAC5C,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI;QAClB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IACpD,OAAO,KAAK,CAAC,QAAQ,CAAC,CAAC;AACzB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CACvB,KAAiB,EACjB,EAAU,EACV,SAAiB,EACjB,OAAkC;IAElC,MAAM,YAAY,GAA2B,EAAE,CAAC;IAChD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC;QAC/D,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACrC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,kBAAkB,EAAE,uDAAuD,CAAC,CAAC;QAC/F,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,MAAM,WAAW,GAAG,IAAI,oBAAoB,CAAC,KAAK,EAAE,GAAG,EAAE,cAAc,GAAG,EAAE,EAAE;YAC5E,SAAS;YACT,WAAW;SACZ,CAAC,CAAC;QACH,wEAAwE;QACxE,uEAAuE;QACvE,IAAI,SAAS,CAAC,WAAW,CAAC,MAAM,CAAC;YAAE,WAAW,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;QAC1E,MAAM,QAAQ,GAAG,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QACrC,IAAI,QAAQ;YAAE,WAAW,CAAC,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QACvD,YAAY,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { CfnWebACL } from "aws-cdk-lib/aws-wafv2";
|
|
2
|
+
/**
|
|
3
|
+
* The body of a custom response. `json` is serialised with `JSON.stringify`
|
|
4
|
+
* unless it is already a string.
|
|
5
|
+
*/
|
|
6
|
+
export type CustomResponseBody = {
|
|
7
|
+
json: unknown;
|
|
8
|
+
} | {
|
|
9
|
+
text: string;
|
|
10
|
+
} | {
|
|
11
|
+
html: string;
|
|
12
|
+
};
|
|
13
|
+
/** A custom response for a Block action: status, optional body and headers. */
|
|
14
|
+
export interface CustomResponse {
|
|
15
|
+
readonly status: number;
|
|
16
|
+
readonly body?: CustomResponseBody;
|
|
17
|
+
readonly headers?: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The status codes WAF accepts in a custom response. `410` is not among them,
|
|
21
|
+
* and `402` is reserved for the Monetize action.
|
|
22
|
+
*
|
|
23
|
+
* @see https://docs.aws.amazon.com/waf/latest/developerguide/customizing-the-response-status-codes.html
|
|
24
|
+
*/
|
|
25
|
+
export declare const SUPPORTED_CUSTOM_RESPONSE_STATUS_CODES: readonly number[];
|
|
26
|
+
/**
|
|
27
|
+
* Collects the custom response bodies of a web ACL's default action and rules
|
|
28
|
+
* into the ACL-level `CustomResponseBodies` map CloudFormation requires.
|
|
29
|
+
* Identical bodies share one key; a body's key is derived from the first rule
|
|
30
|
+
* that uses it.
|
|
31
|
+
*
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
export declare class CustomResponseBodies {
|
|
35
|
+
#private;
|
|
36
|
+
constructor(owner: string);
|
|
37
|
+
/**
|
|
38
|
+
* Validates `response` and turns it into the `CustomResponse` CloudFormation
|
|
39
|
+
* expects, registering its body under a key derived from `keyHint`.
|
|
40
|
+
*
|
|
41
|
+
* @param who - Names the rule or default action in error messages.
|
|
42
|
+
*/
|
|
43
|
+
register(who: string, keyHint: string, response: CustomResponse): CfnWebACL.CustomResponseProperty;
|
|
44
|
+
/**
|
|
45
|
+
* The Block action for a rule or default action, with `response` as its
|
|
46
|
+
* custom response when there is one.
|
|
47
|
+
*/
|
|
48
|
+
block(who: string, keyHint: string, response?: CustomResponse): CfnWebACL.BlockActionProperty;
|
|
49
|
+
/** The map for `CfnWebACLProps.customResponseBodies`, or `undefined` when empty. */
|
|
50
|
+
toProperty(): Record<string, CfnWebACL.CustomResponseBodyProperty> | undefined;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=custom-response.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-response.d.ts","sourceRoot":"","sources":["../../src/custom-response.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvD;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzF,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,kBAAkB,CAAC;IACnC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3C;AAMD;;;;;GAKG;AACH,eAAO,MAAM,sCAAsC,EAAE,SAAS,MAAM,EAGnE,CAAC;AA2DF;;;;;;;GAOG;AACH,qBAAa,oBAAoB;;gBAMnB,KAAK,EAAE,MAAM;IAIzB;;;;;OAKG;IACH,QAAQ,CACN,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,cAAc,GACvB,SAAS,CAAC,sBAAsB;IAgDnC;;;OAGG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,cAAc,GAAG,SAAS,CAAC,mBAAmB;IAI7F,oFAAoF;IACpF,UAAU,IAAI,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,0BAA0B,CAAC,GAAG,SAAS;CAG/E"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Token } from "aws-cdk-lib";
|
|
2
|
+
const SOURCE = "https://docs.aws.amazon.com/waf/latest/developerguide/customizing-the-response-status-codes.html";
|
|
3
|
+
const QUOTAS = "https://docs.aws.amazon.com/waf/latest/developerguide/limits.html";
|
|
4
|
+
/**
|
|
5
|
+
* The status codes WAF accepts in a custom response. `410` is not among them,
|
|
6
|
+
* and `402` is reserved for the Monetize action.
|
|
7
|
+
*
|
|
8
|
+
* @see https://docs.aws.amazon.com/waf/latest/developerguide/customizing-the-response-status-codes.html
|
|
9
|
+
*/
|
|
10
|
+
export const SUPPORTED_CUSTOM_RESPONSE_STATUS_CODES = [
|
|
11
|
+
200, 201, 202, 204, 206, 300, 301, 302, 303, 304, 307, 308, 400, 401, 403, 404, 405, 408, 409,
|
|
12
|
+
411, 412, 413, 414, 415, 416, 421, 429, 500, 501, 502, 503, 504, 505,
|
|
13
|
+
];
|
|
14
|
+
const MAX_BODY_BYTES = 4 * 1024;
|
|
15
|
+
const MAX_BODIES = 50;
|
|
16
|
+
const MAX_TOTAL_BODY_BYTES = 50 * 1024;
|
|
17
|
+
const MAX_HEADERS = 10;
|
|
18
|
+
const HEADER_NAME = /^[a-zA-Z0-9._$-]{1,64}$/;
|
|
19
|
+
const encoder = new TextEncoder();
|
|
20
|
+
const byteLength = (s) => encoder.encode(s).length;
|
|
21
|
+
function toBodyProperty(body) {
|
|
22
|
+
if ("json" in body) {
|
|
23
|
+
return {
|
|
24
|
+
contentType: "APPLICATION_JSON",
|
|
25
|
+
content: typeof body.json === "string" ? body.json : JSON.stringify(body.json),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
if ("text" in body)
|
|
29
|
+
return { contentType: "TEXT_PLAIN", content: body.text };
|
|
30
|
+
return { contentType: "TEXT_HTML", content: body.html };
|
|
31
|
+
}
|
|
32
|
+
function validateStatus(owner, status) {
|
|
33
|
+
if (Token.isUnresolved(status))
|
|
34
|
+
return;
|
|
35
|
+
if (!SUPPORTED_CUSTOM_RESPONSE_STATUS_CODES.includes(status)) {
|
|
36
|
+
throw new Error(`${owner}: AWS WAF does not support the custom response status code ${String(status)}. ` +
|
|
37
|
+
`Supported: ${SUPPORTED_CUSTOM_RESPONSE_STATUS_CODES.join(", ")}. See ${SOURCE}.`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function validateHeaders(owner, headers) {
|
|
41
|
+
const names = Object.keys(headers);
|
|
42
|
+
if (names.length > MAX_HEADERS) {
|
|
43
|
+
throw new Error(`${owner}: a custom response can set at most ${String(MAX_HEADERS)} headers, but ${String(names.length)} were given. See ${QUOTAS}.`);
|
|
44
|
+
}
|
|
45
|
+
const seen = new Set();
|
|
46
|
+
for (const name of names) {
|
|
47
|
+
if (!HEADER_NAME.test(name)) {
|
|
48
|
+
throw new Error(`${owner}: custom response header "${name}" is invalid. Names are 1–64 characters of letters, digits and ._$-. See ${SOURCE}.`);
|
|
49
|
+
}
|
|
50
|
+
const lower = name.toLowerCase();
|
|
51
|
+
if (lower === "content-type") {
|
|
52
|
+
throw new Error(`${owner}: a custom response cannot set content-type; WAF sets it from the body's type. Use { json }, { text } or { html } instead.`);
|
|
53
|
+
}
|
|
54
|
+
if (seen.has(lower)) {
|
|
55
|
+
throw new Error(`${owner}: custom response header "${name}" is set twice.`);
|
|
56
|
+
}
|
|
57
|
+
seen.add(lower);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Collects the custom response bodies of a web ACL's default action and rules
|
|
62
|
+
* into the ACL-level `CustomResponseBodies` map CloudFormation requires.
|
|
63
|
+
* Identical bodies share one key; a body's key is derived from the first rule
|
|
64
|
+
* that uses it.
|
|
65
|
+
*
|
|
66
|
+
* @internal
|
|
67
|
+
*/
|
|
68
|
+
export class CustomResponseBodies {
|
|
69
|
+
#owner;
|
|
70
|
+
#bodies = {};
|
|
71
|
+
#keysByContent = new Map();
|
|
72
|
+
#totalBytes = 0;
|
|
73
|
+
constructor(owner) {
|
|
74
|
+
this.#owner = owner;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Validates `response` and turns it into the `CustomResponse` CloudFormation
|
|
78
|
+
* expects, registering its body under a key derived from `keyHint`.
|
|
79
|
+
*
|
|
80
|
+
* @param who - Names the rule or default action in error messages.
|
|
81
|
+
*/
|
|
82
|
+
register(who, keyHint, response) {
|
|
83
|
+
const owner = `${this.#owner} ${who}`;
|
|
84
|
+
validateStatus(owner, response.status);
|
|
85
|
+
if (response.headers)
|
|
86
|
+
validateHeaders(owner, response.headers);
|
|
87
|
+
const responseHeaders = response.headers
|
|
88
|
+
? Object.entries(response.headers).map(([name, value]) => ({ name, value }))
|
|
89
|
+
: undefined;
|
|
90
|
+
return {
|
|
91
|
+
responseCode: response.status,
|
|
92
|
+
...(response.body && { customResponseBodyKey: this.#keyFor(owner, keyHint, response.body) }),
|
|
93
|
+
...(responseHeaders && { responseHeaders }),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
#keyFor(owner, keyHint, body) {
|
|
97
|
+
const property = toBodyProperty(body);
|
|
98
|
+
const identity = `${property.contentType}\n${property.content}`;
|
|
99
|
+
const existing = this.#keysByContent.get(identity);
|
|
100
|
+
if (existing !== undefined)
|
|
101
|
+
return existing;
|
|
102
|
+
const bytes = byteLength(property.content);
|
|
103
|
+
if (bytes > MAX_BODY_BYTES) {
|
|
104
|
+
throw new Error(`${owner}: the custom response body is ${String(bytes)} bytes; AWS WAF allows at most ${String(MAX_BODY_BYTES)}. See ${QUOTAS}.`);
|
|
105
|
+
}
|
|
106
|
+
if (this.#keysByContent.size >= MAX_BODIES) {
|
|
107
|
+
throw new Error(`${this.#owner}: a web ACL can hold at most ${String(MAX_BODIES)} distinct custom response bodies. See ${QUOTAS}.`);
|
|
108
|
+
}
|
|
109
|
+
this.#totalBytes += bytes;
|
|
110
|
+
if (this.#totalBytes > MAX_TOTAL_BODY_BYTES) {
|
|
111
|
+
throw new Error(`${this.#owner}: the custom response bodies total ${String(this.#totalBytes)} bytes; AWS WAF allows at most ${String(MAX_TOTAL_BODY_BYTES)} per web ACL. See ${QUOTAS}.`);
|
|
112
|
+
}
|
|
113
|
+
let key = keyHint;
|
|
114
|
+
// Keys share the 128-character limit of rule names, so leave room for a suffix.
|
|
115
|
+
for (let n = 2; key in this.#bodies; n++)
|
|
116
|
+
key = `${keyHint.slice(0, 120)}-${String(n)}`;
|
|
117
|
+
this.#bodies[key] = property;
|
|
118
|
+
this.#keysByContent.set(identity, key);
|
|
119
|
+
return key;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* The Block action for a rule or default action, with `response` as its
|
|
123
|
+
* custom response when there is one.
|
|
124
|
+
*/
|
|
125
|
+
block(who, keyHint, response) {
|
|
126
|
+
return response ? { customResponse: this.register(who, keyHint, response) } : {};
|
|
127
|
+
}
|
|
128
|
+
/** The map for `CfnWebACLProps.customResponseBodies`, or `undefined` when empty. */
|
|
129
|
+
toProperty() {
|
|
130
|
+
return this.#keysByContent.size > 0 ? this.#bodies : undefined;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=custom-response.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom-response.js","sourceRoot":"","sources":["../../src/custom-response.ts"],"names":[],"mappings":"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"}
|