@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.
Files changed (117) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +240 -2
  3. package/dist/commonjs/alarm-config.d.ts +60 -0
  4. package/dist/commonjs/alarm-config.d.ts.map +1 -0
  5. package/dist/commonjs/alarm-config.js +3 -0
  6. package/dist/commonjs/alarm-config.js.map +1 -0
  7. package/dist/commonjs/alarm-defaults.d.ts +34 -0
  8. package/dist/commonjs/alarm-defaults.d.ts.map +1 -0
  9. package/dist/commonjs/alarm-defaults.js +37 -0
  10. package/dist/commonjs/alarm-defaults.js.map +1 -0
  11. package/dist/commonjs/alarms.d.ts +18 -0
  12. package/dist/commonjs/alarms.d.ts.map +1 -0
  13. package/dist/commonjs/alarms.js +94 -0
  14. package/dist/commonjs/alarms.js.map +1 -0
  15. package/dist/commonjs/association.d.ts +21 -0
  16. package/dist/commonjs/association.d.ts.map +1 -0
  17. package/dist/commonjs/association.js +61 -0
  18. package/dist/commonjs/association.js.map +1 -0
  19. package/dist/commonjs/custom-response.d.ts +52 -0
  20. package/dist/commonjs/custom-response.d.ts.map +1 -0
  21. package/dist/commonjs/custom-response.js +137 -0
  22. package/dist/commonjs/custom-response.js.map +1 -0
  23. package/dist/commonjs/defaults.d.ts +89 -0
  24. package/dist/commonjs/defaults.d.ts.map +1 -0
  25. package/dist/commonjs/defaults.js +90 -0
  26. package/dist/commonjs/defaults.js.map +1 -0
  27. package/dist/commonjs/index.d.ts +30 -0
  28. package/dist/commonjs/index.d.ts.map +1 -0
  29. package/dist/commonjs/index.js +35 -0
  30. package/dist/commonjs/index.js.map +1 -0
  31. package/dist/commonjs/logging-filter.d.ts +27 -0
  32. package/dist/commonjs/logging-filter.d.ts.map +1 -0
  33. package/dist/commonjs/logging-filter.js +17 -0
  34. package/dist/commonjs/logging-filter.js.map +1 -0
  35. package/dist/commonjs/logging.d.ts +59 -0
  36. package/dist/commonjs/logging.d.ts.map +1 -0
  37. package/dist/commonjs/logging.js +91 -0
  38. package/dist/commonjs/logging.js.map +1 -0
  39. package/dist/commonjs/metrics.d.ts +58 -0
  40. package/dist/commonjs/metrics.d.ts.map +1 -0
  41. package/dist/commonjs/metrics.js +67 -0
  42. package/dist/commonjs/metrics.js.map +1 -0
  43. package/dist/commonjs/names.d.ts +46 -0
  44. package/dist/commonjs/names.d.ts.map +1 -0
  45. package/dist/commonjs/names.js +140 -0
  46. package/dist/commonjs/names.js.map +1 -0
  47. package/dist/commonjs/package.json +3 -0
  48. package/dist/commonjs/rule-builder.d.ts +123 -0
  49. package/dist/commonjs/rule-builder.d.ts.map +1 -0
  50. package/dist/commonjs/rule-builder.js +163 -0
  51. package/dist/commonjs/rule-builder.js.map +1 -0
  52. package/dist/commonjs/scope.d.ts +15 -0
  53. package/dist/commonjs/scope.d.ts.map +1 -0
  54. package/dist/commonjs/scope.js +35 -0
  55. package/dist/commonjs/scope.js.map +1 -0
  56. package/dist/commonjs/web-acl-builder.d.ts +152 -0
  57. package/dist/commonjs/web-acl-builder.d.ts.map +1 -0
  58. package/dist/commonjs/web-acl-builder.js +205 -0
  59. package/dist/commonjs/web-acl-builder.js.map +1 -0
  60. package/dist/esm/alarm-config.d.ts +60 -0
  61. package/dist/esm/alarm-config.d.ts.map +1 -0
  62. package/dist/esm/alarm-config.js +2 -0
  63. package/dist/esm/alarm-config.js.map +1 -0
  64. package/dist/esm/alarm-defaults.d.ts +34 -0
  65. package/dist/esm/alarm-defaults.d.ts.map +1 -0
  66. package/dist/esm/alarm-defaults.js +34 -0
  67. package/dist/esm/alarm-defaults.js.map +1 -0
  68. package/dist/esm/alarms.d.ts +18 -0
  69. package/dist/esm/alarms.d.ts.map +1 -0
  70. package/dist/esm/alarms.js +90 -0
  71. package/dist/esm/alarms.js.map +1 -0
  72. package/dist/esm/association.d.ts +21 -0
  73. package/dist/esm/association.d.ts.map +1 -0
  74. package/dist/esm/association.js +58 -0
  75. package/dist/esm/association.js.map +1 -0
  76. package/dist/esm/custom-response.d.ts +52 -0
  77. package/dist/esm/custom-response.d.ts.map +1 -0
  78. package/dist/esm/custom-response.js +133 -0
  79. package/dist/esm/custom-response.js.map +1 -0
  80. package/dist/esm/defaults.d.ts +89 -0
  81. package/dist/esm/defaults.d.ts.map +1 -0
  82. package/dist/esm/defaults.js +87 -0
  83. package/dist/esm/defaults.js.map +1 -0
  84. package/dist/esm/index.d.ts +30 -0
  85. package/dist/esm/index.d.ts.map +1 -0
  86. package/dist/esm/index.js +23 -0
  87. package/dist/esm/index.js.map +1 -0
  88. package/dist/esm/logging-filter.d.ts +27 -0
  89. package/dist/esm/logging-filter.d.ts.map +1 -0
  90. package/dist/esm/logging-filter.js +14 -0
  91. package/dist/esm/logging-filter.js.map +1 -0
  92. package/dist/esm/logging.d.ts +59 -0
  93. package/dist/esm/logging.d.ts.map +1 -0
  94. package/dist/esm/logging.js +88 -0
  95. package/dist/esm/logging.js.map +1 -0
  96. package/dist/esm/metrics.d.ts +58 -0
  97. package/dist/esm/metrics.d.ts.map +1 -0
  98. package/dist/esm/metrics.js +63 -0
  99. package/dist/esm/metrics.js.map +1 -0
  100. package/dist/esm/names.d.ts +46 -0
  101. package/dist/esm/names.d.ts.map +1 -0
  102. package/dist/esm/names.js +132 -0
  103. package/dist/esm/names.js.map +1 -0
  104. package/dist/esm/package.json +3 -0
  105. package/dist/esm/rule-builder.d.ts +123 -0
  106. package/dist/esm/rule-builder.d.ts.map +1 -0
  107. package/dist/esm/rule-builder.js +160 -0
  108. package/dist/esm/rule-builder.js.map +1 -0
  109. package/dist/esm/scope.d.ts +15 -0
  110. package/dist/esm/scope.d.ts.map +1 -0
  111. package/dist/esm/scope.js +32 -0
  112. package/dist/esm/scope.js.map +1 -0
  113. package/dist/esm/web-acl-builder.d.ts +152 -0
  114. package/dist/esm/web-acl-builder.d.ts.map +1 -0
  115. package/dist/esm/web-acl-builder.js +202 -0
  116. package/dist/esm/web-acl-builder.js.map +1 -0
  117. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=alarm-config.js.map
@@ -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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"}