@intentius/chant-lexicon-aws 0.49.0 → 0.50.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/dist/codegen/generate-lexicon.d.ts +5 -0
- package/dist/codegen/generate-lexicon.d.ts.map +1 -1
- package/dist/components/host-delivery.d.ts +4 -3
- package/dist/components/host-delivery.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +9 -4
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/waw016.d.ts +10 -2
- package/dist/lint/post-synth/waw016.d.ts.map +1 -1
- package/dist/lint/post-synth/waw064.d.ts +17 -0
- package/dist/lint/post-synth/waw064.d.ts.map +1 -0
- package/dist/lint/post-synth/waw065.d.ts +23 -0
- package/dist/lint/post-synth/waw065.d.ts.map +1 -0
- package/dist/lint/post-synth/waw066.d.ts +25 -0
- package/dist/lint/post-synth/waw066.d.ts.map +1 -0
- package/dist/lint/post-synth/waw067.d.ts +24 -0
- package/dist/lint/post-synth/waw067.d.ts.map +1 -0
- package/dist/lint/post-synth/waw068.d.ts +20 -0
- package/dist/lint/post-synth/waw068.d.ts.map +1 -0
- package/dist/manifest.json +1 -1
- package/dist/meta.json +34 -0
- package/dist/okf/index.md +5 -0
- package/dist/okf/rules/WAW064.md +18 -0
- package/dist/okf/rules/WAW065.md +25 -0
- package/dist/okf/rules/WAW066.md +22 -0
- package/dist/okf/rules/WAW067.md +20 -0
- package/dist/okf/rules/WAW068.md +19 -0
- package/dist/okf/types/EC2Route.md +2 -0
- package/dist/okf/types/InternetGateway.md +4 -0
- package/dist/okf/types/Map.md +4 -0
- package/dist/okf/types/NatGateway.md +4 -0
- package/dist/okf/types/RouteTable.md +4 -0
- package/dist/okf/types/Subnet.md +2 -0
- package/dist/okf/types/SubnetRouteTableAssociation.md +5 -0
- package/dist/okf/types/Table.md +3 -0
- package/dist/okf/types/TransitGateway.md +6 -0
- package/dist/okf/types/TransitGatewayAttachment.md +4 -0
- package/dist/okf/types/TransitGatewayPeeringAttachment.md +4 -0
- package/dist/okf/types/TransitGatewayRoute.md +5 -0
- package/dist/okf/types/TransitGatewayRouteTable.md +4 -0
- package/dist/okf/types/TransitGatewayRouteTableAssociation.md +4 -0
- package/dist/okf/types/TransitGatewayRouteTablePropagation.md +4 -0
- package/dist/okf/types/TransitGatewayVpcAttachment.md +4 -0
- package/dist/okf/types/Type.md +5 -0
- package/dist/okf/types/VPNConnection.md +4 -0
- package/dist/okf/types/VPNGateway.md +4 -0
- package/dist/op/builders.d.ts +47 -0
- package/dist/op/builders.d.ts.map +1 -0
- package/dist/rules/waw016.ts +37 -16
- package/dist/rules/waw064.ts +52 -0
- package/dist/rules/waw065.ts +110 -0
- package/dist/rules/waw066.ts +155 -0
- package/dist/rules/waw067.ts +114 -0
- package/dist/rules/waw068.ts +78 -0
- package/dist/spec/parse.d.ts +7 -0
- package/dist/spec/parse.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/codegen/fallback.ts +1 -0
- package/src/codegen/generate-lexicon.ts +6 -0
- package/src/codegen/sam.ts +9 -0
- package/src/components/host-delivery.ts +4 -3
- package/src/generated/lexicon-aws.json +34 -0
- package/src/index.ts +8 -0
- package/src/lint/audit-catalog.ts +10 -0
- package/src/lint/post-synth/index.ts +10 -0
- package/src/lint/post-synth/waw016.test.ts +61 -4
- package/src/lint/post-synth/waw016.ts +37 -16
- package/src/lint/post-synth/waw064.test.ts +79 -0
- package/src/lint/post-synth/waw064.ts +52 -0
- package/src/lint/post-synth/waw065.test.ts +90 -0
- package/src/lint/post-synth/waw065.ts +110 -0
- package/src/lint/post-synth/waw066.test.ts +108 -0
- package/src/lint/post-synth/waw066.ts +155 -0
- package/src/lint/post-synth/waw067.test.ts +79 -0
- package/src/lint/post-synth/waw067.ts +114 -0
- package/src/lint/post-synth/waw068.test.ts +74 -0
- package/src/lint/post-synth/waw068.ts +78 -0
- package/src/lsp/hover.ts +17 -2
- package/src/op/builders.test.ts +61 -0
- package/src/op/builders.ts +72 -0
- package/src/spec/parse.test.ts +74 -0
- package/src/spec/parse.ts +26 -6
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WAW068: VPN Gateway Has A Single VPN Connection
|
|
3
|
+
*
|
|
4
|
+
* An `AWS::EC2::VPNConnection` always gets two tunnels from AWS, but that
|
|
5
|
+
* redundancy only covers the tunnel pair inside one connection — it does
|
|
6
|
+
* nothing if the connection's Customer Gateway, on-prem device, or the
|
|
7
|
+
* single site it terminates at goes down. The reliability boundary that
|
|
8
|
+
* matters is the connection itself: a `AWS::EC2::VPNGateway` (or a Transit
|
|
9
|
+
* Gateway used for VPN attachment) with only one `VPNConnection` attached
|
|
10
|
+
* has no fallback path for hybrid connectivity if that connection's
|
|
11
|
+
* Customer Gateway or on-prem side fails.
|
|
12
|
+
*
|
|
13
|
+
* Cross-resource join: group every declared `VPNConnection` by the gateway
|
|
14
|
+
* (`VpnGatewayId` or `TransitGatewayId`) it attaches to, and flag any
|
|
15
|
+
* gateway left with exactly one.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
19
|
+
import { parseCFTemplate, findResourceRefs, type CFTemplate } from "./cf-refs";
|
|
20
|
+
|
|
21
|
+
export function checkVpnGatewayRedundancy(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
22
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
23
|
+
|
|
24
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
25
|
+
const template: CFTemplate | null = parseCFTemplate(output);
|
|
26
|
+
if (!template?.Resources) continue;
|
|
27
|
+
const resources = template.Resources;
|
|
28
|
+
|
|
29
|
+
// gateway logical id -> the VPNConnection logical ids attached to it.
|
|
30
|
+
const connectionsByGateway = new Map<string, string[]>();
|
|
31
|
+
|
|
32
|
+
for (const [logicalId, resource] of Object.entries(resources)) {
|
|
33
|
+
if (resource.Type !== "AWS::EC2::VPNConnection") continue;
|
|
34
|
+
const props = resource.Properties ?? {};
|
|
35
|
+
|
|
36
|
+
const gatewayProp = props.VpnGatewayId !== undefined ? props.VpnGatewayId : props.TransitGatewayId;
|
|
37
|
+
if (gatewayProp === undefined) continue; // no declared gateway target — can't group, stay quiet
|
|
38
|
+
|
|
39
|
+
const gwRefs = findResourceRefs(gatewayProp);
|
|
40
|
+
if (gwRefs.size !== 1) continue;
|
|
41
|
+
const [gwId] = gwRefs;
|
|
42
|
+
|
|
43
|
+
const gw = resources[gwId];
|
|
44
|
+
if (!gw || (gw.Type !== "AWS::EC2::VPNGateway" && gw.Type !== "AWS::EC2::TransitGateway")) continue;
|
|
45
|
+
|
|
46
|
+
let list = connectionsByGateway.get(gwId);
|
|
47
|
+
if (!list) {
|
|
48
|
+
list = [];
|
|
49
|
+
connectionsByGateway.set(gwId, list);
|
|
50
|
+
}
|
|
51
|
+
list.push(logicalId);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const [gwId, connectionIds] of connectionsByGateway) {
|
|
55
|
+
if (connectionIds.length !== 1) continue;
|
|
56
|
+
|
|
57
|
+
const kind = resources[gwId].Type === "AWS::EC2::VPNGateway" ? "VPN Gateway" : "Transit Gateway";
|
|
58
|
+
diagnostics.push({
|
|
59
|
+
checkId: "WAW068",
|
|
60
|
+
severity: "warning",
|
|
61
|
+
message: `${kind} "${gwId}" has only one VPN connection ("${connectionIds[0]}") attached — no redundant path if this connection, its tunnels, or its Customer Gateway fail; attach a second VPNConnection (ideally to a separate Customer Gateway) for hybrid-connectivity redundancy`,
|
|
62
|
+
entity: gwId,
|
|
63
|
+
lexicon: "aws",
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return diagnostics;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const waw068: PostSynthCheck = {
|
|
72
|
+
id: "WAW068",
|
|
73
|
+
description: "VPN Gateway or Transit Gateway has only one attached VPN Connection — no redundant path for hybrid connectivity",
|
|
74
|
+
|
|
75
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
76
|
+
return checkVpnGatewayRedundancy(ctx);
|
|
77
|
+
},
|
|
78
|
+
};
|
package/dist/spec/parse.d.ts
CHANGED
|
@@ -36,6 +36,13 @@ export interface ParsedResource {
|
|
|
36
36
|
writeOnly: string[];
|
|
37
37
|
primaryIdentifier: string[];
|
|
38
38
|
deprecatedProperties: string[];
|
|
39
|
+
/**
|
|
40
|
+
* The subset of `deprecatedProperties` that no upstream declaration backs.
|
|
41
|
+
* These names come from {@link DEPRECATION_RE} matching the property
|
|
42
|
+
* description, so they are an inference rather than a reading. Kept apart so
|
|
43
|
+
* a consumer can tell the two apart and calibrate what it says (#1701).
|
|
44
|
+
*/
|
|
45
|
+
inferredDeprecations: string[];
|
|
39
46
|
conditionalCreateOnly: string[];
|
|
40
47
|
replacementStrategy?: "delete_then_create" | "create_then_delete";
|
|
41
48
|
tagging?: {
|
package/dist/spec/parse.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/spec/parse.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAGL,kBAAkB,IAAI,sBAAsB,EAE5C,KAAK,mBAAmB,EAIzB,MAAM,sCAAsC,CAAC;AAG9C,YAAY,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/spec/parse.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAGL,kBAAkB,IAAI,sBAAsB,EAE5C,KAAK,mBAAmB,EAIzB,MAAM,sCAAsC,CAAC;AAG9C,YAAY,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAkBhF,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,EAAE,mBAAmB,CAAC;CAClC;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,cAAc,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B;;;;;OAKG;IACH,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,mBAAmB,CAAC,EAAE,oBAAoB,GAAG,oBAAoB,CAAC;IAClE,OAAO,CAAC,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,OAAO,CAAC;QAAC,YAAY,EAAE,OAAO,CAAA;KAAE,CAAC;CAC9E;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,cAAc,CAAC;IACzB,aAAa,EAAE,kBAAkB,EAAE,CAAC;IACpC,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,iBAAiB,CAmJvE;AAiBD,eAAO,MAAM,kBAAkB,+BAAyB,CAAC;AAMzD;;GAEG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGvD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-aws",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.0",
|
|
4
4
|
"description": "AWS CloudFormation lexicon for chant — declarative IaC in TypeScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"typescript": "^5.9.3"
|
|
81
81
|
},
|
|
82
82
|
"peerDependencies": {
|
|
83
|
-
"@intentius/chant": "^0.
|
|
83
|
+
"@intentius/chant": "^0.50.0",
|
|
84
84
|
"typescript": "^5.9.3"
|
|
85
85
|
}
|
|
86
86
|
}
|
package/src/codegen/fallback.ts
CHANGED
|
@@ -26,6 +26,11 @@ export interface LexiconEntry {
|
|
|
26
26
|
writeOnly?: string[];
|
|
27
27
|
primaryIdentifier?: string[];
|
|
28
28
|
deprecatedProperties?: string[];
|
|
29
|
+
/**
|
|
30
|
+
* Subset of `deprecatedProperties` that only a regex over the property
|
|
31
|
+
* description supports; the rest is declared by the Registry schema (#1701).
|
|
32
|
+
*/
|
|
33
|
+
inferredDeprecations?: string[];
|
|
29
34
|
conditionalCreateOnly?: string[];
|
|
30
35
|
replacementStrategy?: "delete_then_create" | "create_then_delete";
|
|
31
36
|
tagging?: { taggable: boolean; tagOnCreate: boolean; tagUpdatable: boolean };
|
|
@@ -72,6 +77,7 @@ export function generateLexiconJSON(
|
|
|
72
77
|
...(r.resource.writeOnly.length > 0 && { writeOnly: r.resource.writeOnly }),
|
|
73
78
|
...(r.resource.primaryIdentifier.length > 0 && { primaryIdentifier: r.resource.primaryIdentifier }),
|
|
74
79
|
...(r.resource.deprecatedProperties?.length && { deprecatedProperties: r.resource.deprecatedProperties }),
|
|
80
|
+
...(r.resource.inferredDeprecations?.length && { inferredDeprecations: r.resource.inferredDeprecations }),
|
|
75
81
|
...(r.resource.conditionalCreateOnly?.length && { conditionalCreateOnly: r.resource.conditionalCreateOnly }),
|
|
76
82
|
...(r.resource.replacementStrategy && { replacementStrategy: r.resource.replacementStrategy }),
|
|
77
83
|
...(r.resource.tagging && { tagging: r.resource.tagging }),
|
package/src/codegen/sam.ts
CHANGED
|
@@ -59,6 +59,7 @@ function samFunction(): SchemaParseResult {
|
|
|
59
59
|
writeOnly: [],
|
|
60
60
|
primaryIdentifier: [],
|
|
61
61
|
deprecatedProperties: [],
|
|
62
|
+
inferredDeprecations: [],
|
|
62
63
|
conditionalCreateOnly: [],
|
|
63
64
|
},
|
|
64
65
|
propertyTypes: [
|
|
@@ -147,6 +148,7 @@ function samApi(): SchemaParseResult {
|
|
|
147
148
|
writeOnly: [],
|
|
148
149
|
primaryIdentifier: [],
|
|
149
150
|
deprecatedProperties: [],
|
|
151
|
+
inferredDeprecations: [],
|
|
150
152
|
conditionalCreateOnly: [],
|
|
151
153
|
},
|
|
152
154
|
propertyTypes: [
|
|
@@ -200,6 +202,7 @@ function samHttpApi(): SchemaParseResult {
|
|
|
200
202
|
writeOnly: [],
|
|
201
203
|
primaryIdentifier: [],
|
|
202
204
|
deprecatedProperties: [],
|
|
205
|
+
inferredDeprecations: [],
|
|
203
206
|
conditionalCreateOnly: [],
|
|
204
207
|
},
|
|
205
208
|
propertyTypes: [
|
|
@@ -236,6 +239,7 @@ function samSimpleTable(): SchemaParseResult {
|
|
|
236
239
|
writeOnly: [],
|
|
237
240
|
primaryIdentifier: [],
|
|
238
241
|
deprecatedProperties: [],
|
|
242
|
+
inferredDeprecations: [],
|
|
239
243
|
conditionalCreateOnly: [],
|
|
240
244
|
},
|
|
241
245
|
propertyTypes: [
|
|
@@ -273,6 +277,7 @@ function samLayerVersion(): SchemaParseResult {
|
|
|
273
277
|
writeOnly: [],
|
|
274
278
|
primaryIdentifier: [],
|
|
275
279
|
deprecatedProperties: [],
|
|
280
|
+
inferredDeprecations: [],
|
|
276
281
|
conditionalCreateOnly: [],
|
|
277
282
|
},
|
|
278
283
|
propertyTypes: [],
|
|
@@ -306,6 +311,7 @@ function samStateMachine(): SchemaParseResult {
|
|
|
306
311
|
writeOnly: [],
|
|
307
312
|
primaryIdentifier: [],
|
|
308
313
|
deprecatedProperties: [],
|
|
314
|
+
inferredDeprecations: [],
|
|
309
315
|
conditionalCreateOnly: [],
|
|
310
316
|
},
|
|
311
317
|
propertyTypes: [
|
|
@@ -339,6 +345,7 @@ function samApplication(): SchemaParseResult {
|
|
|
339
345
|
writeOnly: [],
|
|
340
346
|
primaryIdentifier: [],
|
|
341
347
|
deprecatedProperties: [],
|
|
348
|
+
inferredDeprecations: [],
|
|
342
349
|
conditionalCreateOnly: [],
|
|
343
350
|
},
|
|
344
351
|
propertyTypes: [],
|
|
@@ -360,6 +367,7 @@ function samConnector(): SchemaParseResult {
|
|
|
360
367
|
writeOnly: [],
|
|
361
368
|
primaryIdentifier: [],
|
|
362
369
|
deprecatedProperties: [],
|
|
370
|
+
inferredDeprecations: [],
|
|
363
371
|
conditionalCreateOnly: [],
|
|
364
372
|
},
|
|
365
373
|
propertyTypes: [],
|
|
@@ -397,6 +405,7 @@ function samGraphQLApi(): SchemaParseResult {
|
|
|
397
405
|
writeOnly: [],
|
|
398
406
|
primaryIdentifier: [],
|
|
399
407
|
deprecatedProperties: [],
|
|
408
|
+
inferredDeprecations: [],
|
|
400
409
|
conditionalCreateOnly: [],
|
|
401
410
|
},
|
|
402
411
|
propertyTypes: [],
|
|
@@ -73,9 +73,10 @@ function resolveTargets(
|
|
|
73
73
|
* Deploy a revision to a host (fleet) via AWS CodeDeploy: create the
|
|
74
74
|
* deployment, then wait for a terminal status. Rollback re-invokes
|
|
75
75
|
* `stopAndRollback` against the deployment id this step created (captured in
|
|
76
|
-
* a closure-scoped map keyed by application/deploymentGroup
|
|
77
|
-
*
|
|
78
|
-
*
|
|
76
|
+
* a closure-scoped map keyed by application/deploymentGroup; the driver now
|
|
77
|
+
* also passes `run`'s output as `rollback`'s optional third arg (#1944), but
|
|
78
|
+
* this capability's input-derived key needs neither channel to resolve
|
|
79
|
+
* identity) — riding CodeDeploy's own native automatic rollback to the
|
|
79
80
|
* last known-good revision rather than a chant-scripted compensation, the
|
|
80
81
|
* "auto" rollback style the Neo4j pilot's docs comment contrasts with the
|
|
81
82
|
* ALB/ECS pilot's component-declared one.
|
|
@@ -6222,6 +6222,9 @@
|
|
|
6222
6222
|
"deprecatedProperties": [
|
|
6223
6223
|
"DeleteStorageConnectors"
|
|
6224
6224
|
],
|
|
6225
|
+
"inferredDeprecations": [
|
|
6226
|
+
"DeleteStorageConnectors"
|
|
6227
|
+
],
|
|
6225
6228
|
"tagging": {
|
|
6226
6229
|
"taggable": true,
|
|
6227
6230
|
"tagOnCreate": true,
|
|
@@ -11669,6 +11672,9 @@
|
|
|
11669
11672
|
"deprecatedProperties": [
|
|
11670
11673
|
"AccessControl"
|
|
11671
11674
|
],
|
|
11675
|
+
"inferredDeprecations": [
|
|
11676
|
+
"AccessControl"
|
|
11677
|
+
],
|
|
11672
11678
|
"tagging": {
|
|
11673
11679
|
"taggable": true,
|
|
11674
11680
|
"tagOnCreate": true,
|
|
@@ -14989,6 +14995,9 @@
|
|
|
14989
14995
|
"deprecatedProperties": [
|
|
14990
14996
|
"ComputeModel"
|
|
14991
14997
|
],
|
|
14998
|
+
"inferredDeprecations": [
|
|
14999
|
+
"ComputeModel"
|
|
15000
|
+
],
|
|
14992
15001
|
"replacementStrategy": "delete_then_create",
|
|
14993
15002
|
"tagging": {
|
|
14994
15003
|
"taggable": true,
|
|
@@ -15385,6 +15394,9 @@
|
|
|
15385
15394
|
"deprecatedProperties": [
|
|
15386
15395
|
"ComputeModel"
|
|
15387
15396
|
],
|
|
15397
|
+
"inferredDeprecations": [
|
|
15398
|
+
"ComputeModel"
|
|
15399
|
+
],
|
|
15388
15400
|
"conditionalCreateOnly": [
|
|
15389
15401
|
"DbNodes"
|
|
15390
15402
|
],
|
|
@@ -17085,6 +17097,9 @@
|
|
|
17085
17097
|
"deprecatedProperties": [
|
|
17086
17098
|
"Deprecated"
|
|
17087
17099
|
],
|
|
17100
|
+
"inferredDeprecations": [
|
|
17101
|
+
"Deprecated"
|
|
17102
|
+
],
|
|
17088
17103
|
"tagging": {
|
|
17089
17104
|
"taggable": true,
|
|
17090
17105
|
"tagOnCreate": true,
|
|
@@ -29032,6 +29047,9 @@
|
|
|
29032
29047
|
"TdeCredentialPassword",
|
|
29033
29048
|
"DBSecurityGroups"
|
|
29034
29049
|
],
|
|
29050
|
+
"inferredDeprecations": [
|
|
29051
|
+
"DBSecurityGroups"
|
|
29052
|
+
],
|
|
29035
29053
|
"conditionalCreateOnly": [
|
|
29036
29054
|
"AutoMinorVersionUpgrade",
|
|
29037
29055
|
"AvailabilityZone",
|
|
@@ -33246,6 +33264,9 @@
|
|
|
33246
33264
|
"deprecatedProperties": [
|
|
33247
33265
|
"ImageScanningConfiguration"
|
|
33248
33266
|
],
|
|
33267
|
+
"inferredDeprecations": [
|
|
33268
|
+
"ImageScanningConfiguration"
|
|
33269
|
+
],
|
|
33249
33270
|
"tagging": {
|
|
33250
33271
|
"taggable": true,
|
|
33251
33272
|
"tagOnCreate": true,
|
|
@@ -41749,6 +41770,9 @@
|
|
|
41749
41770
|
"deprecatedProperties": [
|
|
41750
41771
|
"Runtime"
|
|
41751
41772
|
],
|
|
41773
|
+
"inferredDeprecations": [
|
|
41774
|
+
"Runtime"
|
|
41775
|
+
],
|
|
41752
41776
|
"conditionalCreateOnly": [
|
|
41753
41777
|
"DurableConfig"
|
|
41754
41778
|
],
|
|
@@ -49621,6 +49645,10 @@
|
|
|
49621
49645
|
"ElasticGpuSpecifications",
|
|
49622
49646
|
"ElasticInferenceAccelerators"
|
|
49623
49647
|
],
|
|
49648
|
+
"inferredDeprecations": [
|
|
49649
|
+
"ElasticGpuSpecifications",
|
|
49650
|
+
"ElasticInferenceAccelerators"
|
|
49651
|
+
],
|
|
49624
49652
|
"conditionalCreateOnly": [
|
|
49625
49653
|
"AdditionalInfo",
|
|
49626
49654
|
"Affinity",
|
|
@@ -64878,6 +64906,9 @@
|
|
|
64878
64906
|
"deprecatedProperties": [
|
|
64879
64907
|
"DBPort"
|
|
64880
64908
|
],
|
|
64909
|
+
"inferredDeprecations": [
|
|
64910
|
+
"DBPort"
|
|
64911
|
+
],
|
|
64881
64912
|
"tagging": {
|
|
64882
64913
|
"taggable": true,
|
|
64883
64914
|
"tagOnCreate": true,
|
|
@@ -100101,6 +100132,9 @@
|
|
|
100101
100132
|
"deprecatedProperties": [
|
|
100102
100133
|
"State"
|
|
100103
100134
|
],
|
|
100135
|
+
"inferredDeprecations": [
|
|
100136
|
+
"State"
|
|
100137
|
+
],
|
|
100104
100138
|
"tagging": {
|
|
100105
100139
|
"taggable": true,
|
|
100106
100140
|
"tagOnCreate": true,
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,14 @@ export type { AwsEffectReceiptDeclaration } from "./effect-receipt-row";
|
|
|
22
22
|
export { awsReceiptStore, observeReceiptRows, ssmGetParameter, ssmPutParameter } from "./receipt-store";
|
|
23
23
|
export type { AwsReceiptStoreOptions, ReceiptRowObservation } from "./receipt-store";
|
|
24
24
|
|
|
25
|
+
// Typed Op step-builder wrappers (chant #1288 Stage 2) — awsApply/awsDelete/
|
|
26
|
+
// flociUp/flociDown with authoring-time types derived from this lexicon's
|
|
27
|
+
// own *Args interfaces (see ./op/builders.ts's module doc for why these live
|
|
28
|
+
// here rather than in core or the temporal barrel). Opt-in:
|
|
29
|
+
// `@intentius/chant-lexicon-temporal`'s same-named exports are core's
|
|
30
|
+
// original untyped builders, unchanged, for cloud-agnostic authoring.
|
|
31
|
+
export { awsApply, awsDelete, flociUp, flociDown } from "./op/builders";
|
|
32
|
+
|
|
25
33
|
// Serializer
|
|
26
34
|
export { awsSerializer } from "./serializer";
|
|
27
35
|
|
|
@@ -78,4 +78,14 @@ export const awsAuditCatalog: Record<string, RuleMeta> = {
|
|
|
78
78
|
WAW061: auditRule("WAW061", "merge-worthy", "guidance", "Subnet CidrBlock not contained in its VPC's CidrBlock", "Fix the subnet's CidrBlock so it falls within the VPC's CidrBlock range.", { category: "correctness" }),
|
|
79
79
|
WAW062: auditRule("WAW062", "merge-worthy", "guidance", "Duplicate export name or explicit resource name within a template", "Give each duplicated Export/name a distinct literal value.", { category: "correctness" }),
|
|
80
80
|
WAW063: auditRule("WAW063", "merge-worthy", "guidance", "IAM policy denies an action another attached policy on the same role allows", "Remove or narrow the Deny, or move the Allow off this role — the explicit Deny always wins.", { category: "correctness" }),
|
|
81
|
+
|
|
82
|
+
// #826 — network-topology resiliency (Well-Architected Reliability Pillar):
|
|
83
|
+
// TGW route hygiene, private-subnet and NAT-gateway cross-resource joins,
|
|
84
|
+
// VPN redundancy. No live state needed — all determinable from the
|
|
85
|
+
// declared template.
|
|
86
|
+
WAW064: auditRule("WAW064", "report-only", "guidance", "Transit Gateway route table declares a Blackhole route", "Confirm the traffic drop is intentional; remove the route if it isn't.", { category: "best-practice" }),
|
|
87
|
+
WAW065: auditRule("WAW065", "report-only", "guidance", "Transit Gateway route table wiring is incomplete", "Add the missing TransitGatewayRouteTablePropagation, or wire the orphaned attachment into a route table.", { category: "best-practice" }),
|
|
88
|
+
WAW066: auditRule("WAW066", "merge-worthy", "guidance", "Private subnet's route table has no working default route", "Add a 0.0.0.0/0 route to a NAT gateway/Transit Gateway that exists in the template.", { category: "correctness" }),
|
|
89
|
+
WAW067: auditRule("WAW067", "report-only", "guidance", "Single-AZ NAT gateway serves multi-AZ private subnets", "Add one NAT gateway per Availability Zone and point each AZ's subnets at its own.", { category: "best-practice" }),
|
|
90
|
+
WAW068: auditRule("WAW068", "report-only", "guidance", "VPN Gateway or Transit Gateway has only one attached VPN Connection", "Attach a second VPNConnection (ideally to a separate Customer Gateway) for redundancy.", { category: "best-practice" }),
|
|
81
91
|
};
|
|
@@ -55,6 +55,11 @@ import { waw060 } from "./waw060";
|
|
|
55
55
|
import { waw061 } from "./waw061";
|
|
56
56
|
import { waw062 } from "./waw062";
|
|
57
57
|
import { waw063 } from "./waw063";
|
|
58
|
+
import { waw064 } from "./waw064";
|
|
59
|
+
import { waw065 } from "./waw065";
|
|
60
|
+
import { waw066 } from "./waw066";
|
|
61
|
+
import { waw067 } from "./waw067";
|
|
62
|
+
import { waw068 } from "./waw068";
|
|
58
63
|
|
|
59
64
|
export const postSynthChecks: PostSynthCheck[] = [
|
|
60
65
|
cor020,
|
|
@@ -112,4 +117,9 @@ export const postSynthChecks: PostSynthCheck[] = [
|
|
|
112
117
|
waw061,
|
|
113
118
|
waw062,
|
|
114
119
|
waw063,
|
|
120
|
+
waw064,
|
|
121
|
+
waw065,
|
|
122
|
+
waw066,
|
|
123
|
+
waw067,
|
|
124
|
+
waw068,
|
|
115
125
|
];
|
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
import { describe, test, expect } from "vitest";
|
|
2
2
|
import { createPostSynthContext } from "@intentius/chant-test-utils";
|
|
3
|
-
import { waw016, checkDeprecatedProperties } from "./waw016";
|
|
3
|
+
import { waw016, checkDeprecatedProperties, type DeprecationBasis } from "./waw016";
|
|
4
4
|
|
|
5
5
|
function makeCtx(template: object) {
|
|
6
6
|
return createPostSynthContext({ aws: template });
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
function basisMap(entries: Array<[string, DeprecationBasis]>): Map<string, DeprecationBasis> {
|
|
10
|
+
return new Map(entries);
|
|
11
|
+
}
|
|
12
|
+
|
|
9
13
|
/** Synthetic deprecated-property map — no disk dependency. */
|
|
10
|
-
function fakeDeprecated(): Map<string,
|
|
14
|
+
function fakeDeprecated(): Map<string, Map<string, DeprecationBasis>> {
|
|
11
15
|
return new Map([
|
|
12
|
-
[
|
|
13
|
-
|
|
16
|
+
[
|
|
17
|
+
"AWS::S3::Bucket",
|
|
18
|
+
basisMap([
|
|
19
|
+
["AccessControl", "declared"],
|
|
20
|
+
["ObjectLockConfiguration", "declared"],
|
|
21
|
+
]),
|
|
22
|
+
],
|
|
23
|
+
["AWS::Lambda::Function", basisMap([["Code", "declared"]])],
|
|
14
24
|
]);
|
|
15
25
|
}
|
|
16
26
|
|
|
@@ -138,4 +148,51 @@ describe("WAW016: Deprecated Property Usage", () => {
|
|
|
138
148
|
expect(diags[0].entity).toBe("Bucket");
|
|
139
149
|
expect(diags[1].entity).toBe("Func");
|
|
140
150
|
});
|
|
151
|
+
|
|
152
|
+
// --- Deprecation basis (#1701) ---
|
|
153
|
+
|
|
154
|
+
test("an inferred deprecation reports at info and names its basis", () => {
|
|
155
|
+
const ctx = makeCtx({
|
|
156
|
+
Resources: {
|
|
157
|
+
Func: {
|
|
158
|
+
Type: "AWS::Lambda::Function",
|
|
159
|
+
Properties: { Runtime: "nodejs20.x" },
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
const deprecated = new Map([
|
|
164
|
+
["AWS::Lambda::Function", basisMap([["Runtime", "inferred"]])],
|
|
165
|
+
]);
|
|
166
|
+
const diags = checkDeprecatedProperties(ctx, deprecated);
|
|
167
|
+
expect(diags).toHaveLength(1);
|
|
168
|
+
expect(diags[0].checkId).toBe("WAW016");
|
|
169
|
+
expect(diags[0].severity).toBe("info");
|
|
170
|
+
expect(diags[0].message).toContain("Runtime");
|
|
171
|
+
expect(diags[0].message).toContain("does not declare it deprecated");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("declared and inferred deprecations on one resource keep separate severities", () => {
|
|
175
|
+
const ctx = makeCtx({
|
|
176
|
+
Resources: {
|
|
177
|
+
MyBucket: {
|
|
178
|
+
Type: "AWS::S3::Bucket",
|
|
179
|
+
Properties: { AccessControl: "Private", ObjectLockConfiguration: {} },
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
const deprecated = new Map([
|
|
184
|
+
[
|
|
185
|
+
"AWS::S3::Bucket",
|
|
186
|
+
basisMap([
|
|
187
|
+
["AccessControl", "declared"],
|
|
188
|
+
["ObjectLockConfiguration", "inferred"],
|
|
189
|
+
]),
|
|
190
|
+
],
|
|
191
|
+
]);
|
|
192
|
+
const diags = checkDeprecatedProperties(ctx, deprecated);
|
|
193
|
+
expect(diags).toHaveLength(2);
|
|
194
|
+
const bySeverity = new Map(diags.map((d) => [d.severity, d.message]));
|
|
195
|
+
expect(bySeverity.get("warning")).toContain("AccessControl");
|
|
196
|
+
expect(bySeverity.get("info")).toContain("ObjectLockConfiguration");
|
|
197
|
+
});
|
|
141
198
|
});
|
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
* WAW016: Deprecated Property Usage
|
|
3
3
|
*
|
|
4
4
|
* Flags properties marked as deprecated in the CloudFormation Registry.
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
|
+
* Two bases feed this, and the finding says which one it stands on (#1701).
|
|
7
|
+
* A `declared` name comes from the Registry schema's own `deprecatedProperties`
|
|
8
|
+
* array. An `inferred` name comes from a regex over the property description,
|
|
9
|
+
* which also matches descriptions that mention the deprecation of a sibling
|
|
10
|
+
* property, an enum value, or the thing the property configures. Declared is a
|
|
11
|
+
* warning; inferred is reported at info and worded as a guess.
|
|
6
12
|
*/
|
|
7
13
|
|
|
8
14
|
import { readFileSync } from "fs";
|
|
@@ -10,18 +16,23 @@ import { join } from "path";
|
|
|
10
16
|
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
11
17
|
import { parseCFTemplate } from "./cf-refs";
|
|
12
18
|
|
|
19
|
+
/** What a deprecation classification rests on. */
|
|
20
|
+
export type DeprecationBasis = "declared" | "inferred";
|
|
21
|
+
|
|
13
22
|
interface LexiconEntry {
|
|
14
23
|
kind: string;
|
|
15
24
|
resourceType: string;
|
|
16
25
|
deprecatedProperties?: string[];
|
|
26
|
+
inferredDeprecations?: string[];
|
|
17
27
|
[key: string]: unknown;
|
|
18
28
|
}
|
|
19
29
|
|
|
20
30
|
/**
|
|
21
|
-
* Load deprecated properties per resource type from the lexicon JSON
|
|
31
|
+
* Load deprecated properties per resource type from the lexicon JSON, each
|
|
32
|
+
* tagged with the basis it was classified on.
|
|
22
33
|
*/
|
|
23
|
-
function loadDeprecatedProperties(): Map<string,
|
|
24
|
-
const map = new Map<string,
|
|
34
|
+
function loadDeprecatedProperties(): Map<string, Map<string, DeprecationBasis>> {
|
|
35
|
+
const map = new Map<string, Map<string, DeprecationBasis>>();
|
|
25
36
|
try {
|
|
26
37
|
const pkgDir = join(__dirname, "..", "..", "..");
|
|
27
38
|
const lexiconPath = join(pkgDir, "src", "generated", "lexicon-aws.json");
|
|
@@ -29,9 +40,15 @@ function loadDeprecatedProperties(): Map<string, Set<string>> {
|
|
|
29
40
|
const data = JSON.parse(content) as Record<string, LexiconEntry>;
|
|
30
41
|
|
|
31
42
|
for (const [_name, entry] of Object.entries(data)) {
|
|
32
|
-
if (entry.kind
|
|
33
|
-
|
|
43
|
+
if (entry.kind !== "resource" || !entry.resourceType) continue;
|
|
44
|
+
if (!entry.deprecatedProperties?.length) continue;
|
|
45
|
+
|
|
46
|
+
const inferred = new Set(entry.inferredDeprecations ?? []);
|
|
47
|
+
const byProp = new Map<string, DeprecationBasis>();
|
|
48
|
+
for (const propName of entry.deprecatedProperties) {
|
|
49
|
+
byProp.set(propName, inferred.has(propName) ? "inferred" : "declared");
|
|
34
50
|
}
|
|
51
|
+
map.set(entry.resourceType, byProp);
|
|
35
52
|
}
|
|
36
53
|
} catch {
|
|
37
54
|
// Lexicon not available — skip
|
|
@@ -44,7 +61,7 @@ function loadDeprecatedProperties(): Map<string, Set<string>> {
|
|
|
44
61
|
*/
|
|
45
62
|
export function checkDeprecatedProperties(
|
|
46
63
|
ctx: PostSynthContext,
|
|
47
|
-
deprecated: Map<string,
|
|
64
|
+
deprecated: Map<string, Map<string, DeprecationBasis>>,
|
|
48
65
|
): PostSynthDiagnostic[] {
|
|
49
66
|
if (deprecated.size === 0) return [];
|
|
50
67
|
|
|
@@ -60,15 +77,19 @@ export function checkDeprecatedProperties(
|
|
|
60
77
|
|
|
61
78
|
const props = resource.Properties ?? {};
|
|
62
79
|
for (const propName of Object.keys(props)) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
const basis = deprProps.get(propName);
|
|
81
|
+
if (!basis) continue;
|
|
82
|
+
|
|
83
|
+
diagnostics.push({
|
|
84
|
+
checkId: "WAW016",
|
|
85
|
+
severity: basis === "declared" ? "warning" : "info",
|
|
86
|
+
message:
|
|
87
|
+
basis === "declared"
|
|
88
|
+
? `Resource "${logicalId}" (${resource.Type}) uses deprecated property "${propName}" — consider alternatives`
|
|
89
|
+
: `Resource "${logicalId}" (${resource.Type}) uses property "${propName}", whose description reads as deprecated — the CloudFormation Registry does not declare it deprecated, so confirm before changing it`,
|
|
90
|
+
entity: logicalId,
|
|
91
|
+
lexicon: "aws",
|
|
92
|
+
});
|
|
72
93
|
}
|
|
73
94
|
}
|
|
74
95
|
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { createPostSynthContext } from "@intentius/chant-test-utils";
|
|
3
|
+
import { waw064, checkTgwBlackholeRoute } from "./waw064";
|
|
4
|
+
|
|
5
|
+
function makeCtx(template: object) {
|
|
6
|
+
return createPostSynthContext({ aws: template });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe("WAW064: Transit Gateway Route Table Declares A Blackhole Route", () => {
|
|
10
|
+
test("check metadata", () => {
|
|
11
|
+
expect(waw064.id).toBe("WAW064");
|
|
12
|
+
expect(waw064.description).toContain("Blackhole");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("TransitGatewayRoute with Blackhole: true → warning", () => {
|
|
16
|
+
const ctx = makeCtx({
|
|
17
|
+
Resources: {
|
|
18
|
+
QuarantineRoute: {
|
|
19
|
+
Type: "AWS::EC2::TransitGatewayRoute",
|
|
20
|
+
Properties: {
|
|
21
|
+
TransitGatewayRouteTableId: { Ref: "TgwRouteTable" },
|
|
22
|
+
DestinationCidrBlock: "10.99.0.0/16",
|
|
23
|
+
Blackhole: true,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
TgwRouteTable: { Type: "AWS::EC2::TransitGatewayRouteTable", Properties: { TransitGatewayId: { Ref: "Tgw" } } },
|
|
27
|
+
Tgw: { Type: "AWS::EC2::TransitGateway", Properties: {} },
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
const diags = checkTgwBlackholeRoute(ctx);
|
|
31
|
+
expect(diags).toHaveLength(1);
|
|
32
|
+
expect(diags[0].checkId).toBe("WAW064");
|
|
33
|
+
expect(diags[0].severity).toBe("warning");
|
|
34
|
+
expect(diags[0].entity).toBe("QuarantineRoute");
|
|
35
|
+
expect(diags[0].message).toContain("10.99.0.0/16");
|
|
36
|
+
expect(diags[0].lexicon).toBe("aws");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("normal TransitGatewayRoute forwarding to an attachment → no diagnostic", () => {
|
|
40
|
+
const ctx = makeCtx({
|
|
41
|
+
Resources: {
|
|
42
|
+
SpokeRoute: {
|
|
43
|
+
Type: "AWS::EC2::TransitGatewayRoute",
|
|
44
|
+
Properties: {
|
|
45
|
+
TransitGatewayRouteTableId: { Ref: "TgwRouteTable" },
|
|
46
|
+
DestinationCidrBlock: "10.0.1.0/24",
|
|
47
|
+
TransitGatewayAttachmentId: { Ref: "SpokeAttachment" },
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
TgwRouteTable: { Type: "AWS::EC2::TransitGatewayRouteTable", Properties: { TransitGatewayId: { Ref: "Tgw" } } },
|
|
51
|
+
Tgw: { Type: "AWS::EC2::TransitGateway", Properties: {} },
|
|
52
|
+
SpokeAttachment: { Type: "AWS::EC2::TransitGatewayVpcAttachment", Properties: {} },
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
expect(checkTgwBlackholeRoute(ctx)).toHaveLength(0);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("Blackhole: false is not flagged", () => {
|
|
59
|
+
const ctx = makeCtx({
|
|
60
|
+
Resources: {
|
|
61
|
+
SpokeRoute: {
|
|
62
|
+
Type: "AWS::EC2::TransitGatewayRoute",
|
|
63
|
+
Properties: {
|
|
64
|
+
TransitGatewayRouteTableId: { Ref: "TgwRouteTable" },
|
|
65
|
+
DestinationCidrBlock: "10.0.1.0/24",
|
|
66
|
+
TransitGatewayAttachmentId: { Ref: "SpokeAttachment" },
|
|
67
|
+
Blackhole: false,
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
expect(checkTgwBlackholeRoute(ctx)).toHaveLength(0);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("no TransitGatewayRoute resources → no diagnostic", () => {
|
|
76
|
+
const ctx = makeCtx({ Resources: { Vpc: { Type: "AWS::EC2::VPC", Properties: { CidrBlock: "10.0.0.0/16" } } } });
|
|
77
|
+
expect(checkTgwBlackholeRoute(ctx)).toHaveLength(0);
|
|
78
|
+
});
|
|
79
|
+
});
|