@intentius/chant-lexicon-aws 0.18.11 → 0.18.13

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 (126) hide show
  1. package/dist/composites/agentcore-agent.d.ts +74 -0
  2. package/dist/composites/agentcore-agent.d.ts.map +1 -0
  3. package/dist/composites/agentcore-trust-policy.d.ts +11 -0
  4. package/dist/composites/agentcore-trust-policy.d.ts.map +1 -0
  5. package/dist/composites/index.d.ts +4 -0
  6. package/dist/composites/index.d.ts.map +1 -1
  7. package/dist/composites/microvm-app.d.ts +116 -0
  8. package/dist/composites/microvm-app.d.ts.map +1 -0
  9. package/dist/index.d.ts +2 -2
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/integrity.json +22 -4
  12. package/dist/lint/audit-catalog.d.ts.map +1 -1
  13. package/dist/lint/post-synth/cf-refs.d.ts +14 -0
  14. package/dist/lint/post-synth/cf-refs.d.ts.map +1 -1
  15. package/dist/lint/post-synth/index.d.ts.map +1 -1
  16. package/dist/lint/post-synth/waw038.d.ts +12 -0
  17. package/dist/lint/post-synth/waw038.d.ts.map +1 -0
  18. package/dist/lint/post-synth/waw039.d.ts +11 -0
  19. package/dist/lint/post-synth/waw039.d.ts.map +1 -0
  20. package/dist/lint/post-synth/waw040.d.ts +13 -0
  21. package/dist/lint/post-synth/waw040.d.ts.map +1 -0
  22. package/dist/lint/post-synth/waw041.d.ts +10 -0
  23. package/dist/lint/post-synth/waw041.d.ts.map +1 -0
  24. package/dist/lint/post-synth/waw042.d.ts +12 -0
  25. package/dist/lint/post-synth/waw042.d.ts.map +1 -0
  26. package/dist/lint/post-synth/waw043.d.ts +11 -0
  27. package/dist/lint/post-synth/waw043.d.ts.map +1 -0
  28. package/dist/lint/post-synth/waw044.d.ts +18 -0
  29. package/dist/lint/post-synth/waw044.d.ts.map +1 -0
  30. package/dist/lint/post-synth/waw045.d.ts +11 -0
  31. package/dist/lint/post-synth/waw045.d.ts.map +1 -0
  32. package/dist/lint/post-synth/waw046.d.ts +12 -0
  33. package/dist/lint/post-synth/waw046.d.ts.map +1 -0
  34. package/dist/lint/post-synth/waw047.d.ts +11 -0
  35. package/dist/lint/post-synth/waw047.d.ts.map +1 -0
  36. package/dist/lint/post-synth/waw048.d.ts +11 -0
  37. package/dist/lint/post-synth/waw048.d.ts.map +1 -0
  38. package/dist/lint/post-synth/waw049.d.ts +21 -0
  39. package/dist/lint/post-synth/waw049.d.ts.map +1 -0
  40. package/dist/lint/post-synth/waw050.d.ts +11 -0
  41. package/dist/lint/post-synth/waw050.d.ts.map +1 -0
  42. package/dist/lint/post-synth/waw051.d.ts +11 -0
  43. package/dist/lint/post-synth/waw051.d.ts.map +1 -0
  44. package/dist/lint/post-synth/waw052.d.ts +12 -0
  45. package/dist/lint/post-synth/waw052.d.ts.map +1 -0
  46. package/dist/lint/post-synth/waw053.d.ts +10 -0
  47. package/dist/lint/post-synth/waw053.d.ts.map +1 -0
  48. package/dist/lint/post-synth/waw054.d.ts +11 -0
  49. package/dist/lint/post-synth/waw054.d.ts.map +1 -0
  50. package/dist/lint/post-synth/waw055.d.ts +11 -0
  51. package/dist/lint/post-synth/waw055.d.ts.map +1 -0
  52. package/dist/manifest.json +1 -1
  53. package/dist/op/activities/floci.d.ts +6 -0
  54. package/dist/op/activities/floci.d.ts.map +1 -1
  55. package/dist/plugin.d.ts.map +1 -1
  56. package/dist/rules/cf-refs.ts +23 -0
  57. package/dist/rules/waw038.ts +50 -0
  58. package/dist/rules/waw039.ts +51 -0
  59. package/dist/rules/waw040.ts +54 -0
  60. package/dist/rules/waw041.ts +48 -0
  61. package/dist/rules/waw042.ts +87 -0
  62. package/dist/rules/waw043.ts +57 -0
  63. package/dist/rules/waw044.ts +68 -0
  64. package/dist/rules/waw045.ts +58 -0
  65. package/dist/rules/waw046.ts +59 -0
  66. package/dist/rules/waw047.ts +50 -0
  67. package/dist/rules/waw048.ts +47 -0
  68. package/dist/rules/waw049.ts +100 -0
  69. package/dist/rules/waw050.ts +54 -0
  70. package/dist/rules/waw051.ts +49 -0
  71. package/dist/rules/waw052.ts +51 -0
  72. package/dist/rules/waw053.ts +53 -0
  73. package/dist/rules/waw054.ts +49 -0
  74. package/dist/rules/waw055.ts +49 -0
  75. package/dist/types/index.d.ts +11 -9
  76. package/package.json +3 -2
  77. package/src/composites/agentcore-agent.test.ts +188 -0
  78. package/src/composites/agentcore-agent.ts +195 -0
  79. package/src/composites/agentcore-trust-policy.ts +13 -0
  80. package/src/composites/index.ts +4 -0
  81. package/src/composites/microvm-app.test.ts +234 -0
  82. package/src/composites/microvm-app.ts +436 -0
  83. package/src/generated/index.d.ts +11 -9
  84. package/src/index.ts +2 -0
  85. package/src/lint/audit-catalog.ts +20 -0
  86. package/src/lint/post-synth/cf-refs.ts +23 -0
  87. package/src/lint/post-synth/index.ts +36 -0
  88. package/src/lint/post-synth/waw038.test.ts +78 -0
  89. package/src/lint/post-synth/waw038.ts +50 -0
  90. package/src/lint/post-synth/waw039.test.ts +72 -0
  91. package/src/lint/post-synth/waw039.ts +51 -0
  92. package/src/lint/post-synth/waw040.test.ts +71 -0
  93. package/src/lint/post-synth/waw040.ts +54 -0
  94. package/src/lint/post-synth/waw041.test.ts +72 -0
  95. package/src/lint/post-synth/waw041.ts +48 -0
  96. package/src/lint/post-synth/waw042.test.ts +138 -0
  97. package/src/lint/post-synth/waw042.ts +87 -0
  98. package/src/lint/post-synth/waw043.test.ts +84 -0
  99. package/src/lint/post-synth/waw043.ts +57 -0
  100. package/src/lint/post-synth/waw044.test.ts +112 -0
  101. package/src/lint/post-synth/waw044.ts +68 -0
  102. package/src/lint/post-synth/waw045.test.ts +101 -0
  103. package/src/lint/post-synth/waw045.ts +58 -0
  104. package/src/lint/post-synth/waw046.test.ts +112 -0
  105. package/src/lint/post-synth/waw046.ts +59 -0
  106. package/src/lint/post-synth/waw047.test.ts +64 -0
  107. package/src/lint/post-synth/waw047.ts +50 -0
  108. package/src/lint/post-synth/waw048.test.ts +77 -0
  109. package/src/lint/post-synth/waw048.ts +47 -0
  110. package/src/lint/post-synth/waw049.test.ts +139 -0
  111. package/src/lint/post-synth/waw049.ts +100 -0
  112. package/src/lint/post-synth/waw050.test.ts +75 -0
  113. package/src/lint/post-synth/waw050.ts +54 -0
  114. package/src/lint/post-synth/waw051.test.ts +78 -0
  115. package/src/lint/post-synth/waw051.ts +49 -0
  116. package/src/lint/post-synth/waw052.test.ts +54 -0
  117. package/src/lint/post-synth/waw052.ts +51 -0
  118. package/src/lint/post-synth/waw053.test.ts +59 -0
  119. package/src/lint/post-synth/waw053.ts +53 -0
  120. package/src/lint/post-synth/waw054.test.ts +49 -0
  121. package/src/lint/post-synth/waw054.ts +49 -0
  122. package/src/lint/post-synth/waw055.test.ts +43 -0
  123. package/src/lint/post-synth/waw055.ts +49 -0
  124. package/src/op/activities/floci.test.ts +26 -0
  125. package/src/op/activities/floci.ts +25 -9
  126. package/src/plugin.ts +3 -0
@@ -0,0 +1,78 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { createPostSynthContext } from "@intentius/chant-test-utils";
3
+ import { waw038, checkRdsPubliclyAccessible } from "./waw038";
4
+
5
+ function makeCtx(template: object) {
6
+ return createPostSynthContext({ aws: template });
7
+ }
8
+
9
+ describe("WAW038: RDS Publicly Accessible", () => {
10
+ test("check metadata", () => {
11
+ expect(waw038.id).toBe("WAW038");
12
+ expect(waw038.description).toContain("publicly accessible");
13
+ });
14
+
15
+ test("flags DBInstance with PubliclyAccessible: true", () => {
16
+ const ctx = makeCtx({
17
+ Resources: {
18
+ MyDB: {
19
+ Type: "AWS::RDS::DBInstance",
20
+ Properties: { DBInstanceClass: "db.t3.micro", PubliclyAccessible: true },
21
+ },
22
+ },
23
+ });
24
+ const diags = checkRdsPubliclyAccessible(ctx);
25
+ expect(diags).toHaveLength(1);
26
+ expect(diags[0].checkId).toBe("WAW038");
27
+ expect(diags[0].severity).toBe("error");
28
+ expect(diags[0].entity).toBe("MyDB");
29
+ });
30
+
31
+ test("no diagnostic when PubliclyAccessible: false", () => {
32
+ const ctx = makeCtx({
33
+ Resources: {
34
+ MyDB: {
35
+ Type: "AWS::RDS::DBInstance",
36
+ Properties: { DBInstanceClass: "db.t3.micro", PubliclyAccessible: false },
37
+ },
38
+ },
39
+ });
40
+ expect(checkRdsPubliclyAccessible(ctx)).toHaveLength(0);
41
+ });
42
+
43
+ test("no diagnostic when PubliclyAccessible is unset", () => {
44
+ const ctx = makeCtx({
45
+ Resources: {
46
+ MyDB: {
47
+ Type: "AWS::RDS::DBInstance",
48
+ Properties: { DBInstanceClass: "db.t3.micro" },
49
+ },
50
+ },
51
+ });
52
+ expect(checkRdsPubliclyAccessible(ctx)).toHaveLength(0);
53
+ });
54
+
55
+ test("does not apply to DBCluster (no PubliclyAccessible property)", () => {
56
+ const ctx = makeCtx({
57
+ Resources: {
58
+ MyCluster: {
59
+ Type: "AWS::RDS::DBCluster",
60
+ Properties: { Engine: "aurora-mysql" },
61
+ },
62
+ },
63
+ });
64
+ expect(checkRdsPubliclyAccessible(ctx)).toHaveLength(0);
65
+ });
66
+
67
+ test("skips intrinsic value for PubliclyAccessible", () => {
68
+ const ctx = makeCtx({
69
+ Resources: {
70
+ MyDB: {
71
+ Type: "AWS::RDS::DBInstance",
72
+ Properties: { PubliclyAccessible: { Ref: "PublicParam" } },
73
+ },
74
+ },
75
+ });
76
+ expect(checkRdsPubliclyAccessible(ctx)).toHaveLength(0);
77
+ });
78
+ });
@@ -0,0 +1,50 @@
1
+ /**
2
+ * WAW038: RDS Publicly Accessible
3
+ *
4
+ * Flags RDS instances with PubliclyAccessible: true — the instance gets a
5
+ * public IP/DNS name reachable from the internet instead of staying inside
6
+ * the VPC. (DBCluster has no PubliclyAccessible property; that's set on the
7
+ * member DBInstance.)
8
+ */
9
+
10
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
11
+ import { parseCFTemplate, isIntrinsic } from "./cf-refs";
12
+
13
+ export function checkRdsPubliclyAccessible(ctx: PostSynthContext): PostSynthDiagnostic[] {
14
+ const diagnostics: PostSynthDiagnostic[] = [];
15
+
16
+ for (const [_lexicon, output] of ctx.outputs) {
17
+ const template = parseCFTemplate(output);
18
+ if (!template?.Resources) continue;
19
+
20
+ for (const [logicalId, resource] of Object.entries(template.Resources)) {
21
+ if (resource.Type !== "AWS::RDS::DBInstance") continue;
22
+
23
+ const props = resource.Properties ?? {};
24
+ const publiclyAccessible = props.PubliclyAccessible;
25
+
26
+ if (isIntrinsic(publiclyAccessible)) continue;
27
+
28
+ if (publiclyAccessible === true) {
29
+ diagnostics.push({
30
+ checkId: "WAW038",
31
+ severity: "error",
32
+ message: `RDS instance "${logicalId}" has PubliclyAccessible: true — keep the database inside the VPC, unreachable from the internet`,
33
+ entity: logicalId,
34
+ lexicon: "aws",
35
+ });
36
+ }
37
+ }
38
+ }
39
+
40
+ return diagnostics;
41
+ }
42
+
43
+ export const waw038: PostSynthCheck = {
44
+ id: "WAW038",
45
+ description: "RDS instance is publicly accessible — keep databases inside the VPC",
46
+
47
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
48
+ return checkRdsPubliclyAccessible(ctx);
49
+ },
50
+ };
@@ -0,0 +1,72 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { createPostSynthContext } from "@intentius/chant-test-utils";
3
+ import { waw039, checkRdsBackupRetention } from "./waw039";
4
+
5
+ function makeCtx(template: object) {
6
+ return createPostSynthContext({ aws: template });
7
+ }
8
+
9
+ describe("WAW039: RDS Backup Retention Not Set", () => {
10
+ test("check metadata", () => {
11
+ expect(waw039.id).toBe("WAW039");
12
+ expect(waw039.description).toContain("backups");
13
+ });
14
+
15
+ test("flags DBInstance with BackupRetentionPeriod: 0", () => {
16
+ const ctx = makeCtx({
17
+ Resources: {
18
+ MyDB: {
19
+ Type: "AWS::RDS::DBInstance",
20
+ Properties: { DBInstanceClass: "db.t3.micro", BackupRetentionPeriod: 0 },
21
+ },
22
+ },
23
+ });
24
+ const diags = checkRdsBackupRetention(ctx);
25
+ expect(diags).toHaveLength(1);
26
+ expect(diags[0].checkId).toBe("WAW039");
27
+ expect(diags[0].severity).toBe("error");
28
+ });
29
+
30
+ test("flags DBCluster missing BackupRetentionPeriod", () => {
31
+ const ctx = makeCtx({
32
+ Resources: {
33
+ MyCluster: {
34
+ Type: "AWS::RDS::DBCluster",
35
+ Properties: { Engine: "aurora-mysql" },
36
+ },
37
+ },
38
+ });
39
+ expect(checkRdsBackupRetention(ctx)).toHaveLength(1);
40
+ });
41
+
42
+ test("no diagnostic with a positive BackupRetentionPeriod", () => {
43
+ const ctx = makeCtx({
44
+ Resources: {
45
+ MyDB: {
46
+ Type: "AWS::RDS::DBInstance",
47
+ Properties: { BackupRetentionPeriod: 7 },
48
+ },
49
+ },
50
+ });
51
+ expect(checkRdsBackupRetention(ctx)).toHaveLength(0);
52
+ });
53
+
54
+ test("skips intrinsic value for BackupRetentionPeriod", () => {
55
+ const ctx = makeCtx({
56
+ Resources: {
57
+ MyDB: {
58
+ Type: "AWS::RDS::DBInstance",
59
+ Properties: { BackupRetentionPeriod: { Ref: "RetentionParam" } },
60
+ },
61
+ },
62
+ });
63
+ expect(checkRdsBackupRetention(ctx)).toHaveLength(0);
64
+ });
65
+
66
+ test("no diagnostic for non-RDS resources", () => {
67
+ const ctx = makeCtx({
68
+ Resources: { MyBucket: { Type: "AWS::S3::Bucket", Properties: {} } },
69
+ });
70
+ expect(checkRdsBackupRetention(ctx)).toHaveLength(0);
71
+ });
72
+ });
@@ -0,0 +1,51 @@
1
+ /**
2
+ * WAW039: RDS Backup Retention Not Set
3
+ *
4
+ * Flags RDS instances and clusters without a positive BackupRetentionPeriod —
5
+ * a retention of 0 (or an unset property on a resource that otherwise pins
6
+ * every other property explicitly) disables automated backups entirely.
7
+ */
8
+
9
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
10
+ import { parseCFTemplate, isIntrinsic } from "./cf-refs";
11
+
12
+ const RDS_TYPES = new Set(["AWS::RDS::DBInstance", "AWS::RDS::DBCluster"]);
13
+
14
+ export function checkRdsBackupRetention(ctx: PostSynthContext): PostSynthDiagnostic[] {
15
+ const diagnostics: PostSynthDiagnostic[] = [];
16
+
17
+ for (const [_lexicon, output] of ctx.outputs) {
18
+ const template = parseCFTemplate(output);
19
+ if (!template?.Resources) continue;
20
+
21
+ for (const [logicalId, resource] of Object.entries(template.Resources)) {
22
+ if (!RDS_TYPES.has(resource.Type)) continue;
23
+
24
+ const props = resource.Properties ?? {};
25
+ const retention = props.BackupRetentionPeriod;
26
+
27
+ if (isIntrinsic(retention)) continue;
28
+
29
+ if (retention === undefined || retention === 0) {
30
+ diagnostics.push({
31
+ checkId: "WAW039",
32
+ severity: "error",
33
+ message: `RDS resource "${logicalId}" (${resource.Type}) does not have a positive BackupRetentionPeriod — automated backups are disabled`,
34
+ entity: logicalId,
35
+ lexicon: "aws",
36
+ });
37
+ }
38
+ }
39
+ }
40
+
41
+ return diagnostics;
42
+ }
43
+
44
+ export const waw039: PostSynthCheck = {
45
+ id: "WAW039",
46
+ description: "RDS instance or cluster has automated backups disabled — set a positive BackupRetentionPeriod",
47
+
48
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
49
+ return checkRdsBackupRetention(ctx);
50
+ },
51
+ };
@@ -0,0 +1,71 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { createPostSynthContext } from "@intentius/chant-test-utils";
3
+ import { waw040, checkRdsDeletionProtection } from "./waw040";
4
+
5
+ function makeCtx(template: object, env?: string) {
6
+ return { ...createPostSynthContext({ aws: template }), env };
7
+ }
8
+
9
+ describe("WAW040: RDS Deletion Protection Disabled (full tier)", () => {
10
+ test("check metadata", () => {
11
+ expect(waw040.id).toBe("WAW040");
12
+ expect(waw040.description).toContain("DeletionProtection");
13
+ });
14
+
15
+ const template = {
16
+ Resources: {
17
+ MyDB: {
18
+ Type: "AWS::RDS::DBInstance",
19
+ Properties: { DBInstanceClass: "db.t3.micro" },
20
+ },
21
+ },
22
+ };
23
+
24
+ test("warns (not errors) on the light tier — no env set", () => {
25
+ const diags = checkRdsDeletionProtection(makeCtx(template));
26
+ expect(diags).toHaveLength(1);
27
+ expect(diags[0].checkId).toBe("WAW040");
28
+ expect(diags[0].severity).toBe("warning");
29
+ });
30
+
31
+ test("warns on a non-production env", () => {
32
+ const diags = checkRdsDeletionProtection(makeCtx(template, "dev"));
33
+ expect(diags).toHaveLength(1);
34
+ expect(diags[0].severity).toBe("warning");
35
+ });
36
+
37
+ test("errors on the full/production tier", () => {
38
+ const diags = checkRdsDeletionProtection(makeCtx(template, "prod"));
39
+ expect(diags).toHaveLength(1);
40
+ expect(diags[0].severity).toBe("error");
41
+ });
42
+
43
+ test("errors on env: full", () => {
44
+ const diags = checkRdsDeletionProtection(makeCtx(template, "full"));
45
+ expect(diags[0].severity).toBe("error");
46
+ });
47
+
48
+ test("no diagnostic when DeletionProtection: true, even in prod", () => {
49
+ const protectedTemplate = {
50
+ Resources: {
51
+ MyDB: {
52
+ Type: "AWS::RDS::DBInstance",
53
+ Properties: { DeletionProtection: true },
54
+ },
55
+ },
56
+ };
57
+ expect(checkRdsDeletionProtection(makeCtx(protectedTemplate, "prod"))).toHaveLength(0);
58
+ });
59
+
60
+ test("skips intrinsic value for DeletionProtection", () => {
61
+ const paramTemplate = {
62
+ Resources: {
63
+ MyDB: {
64
+ Type: "AWS::RDS::DBInstance",
65
+ Properties: { DeletionProtection: { Ref: "ProtectParam" } },
66
+ },
67
+ },
68
+ };
69
+ expect(checkRdsDeletionProtection(makeCtx(paramTemplate, "prod"))).toHaveLength(0);
70
+ });
71
+ });
@@ -0,0 +1,54 @@
1
+ /**
2
+ * WAW040: RDS Deletion Protection Disabled (full tier)
3
+ *
4
+ * Flags RDS instances and clusters without DeletionProtection: true. Tier-aware
5
+ * (#894): a relaxed "light" stack (local/Floci, or no --env/ownership.env set)
6
+ * only warns, since local/dev databases are routinely torn down on purpose; the
7
+ * strict "full"/production tier (ctx.env === "prod"/"production"/"full", the
8
+ * existing #201 env seam — see isFullTierEnv) fails the build.
9
+ */
10
+
11
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
12
+ import { parseCFTemplate, isIntrinsic, isFullTierEnv } from "./cf-refs";
13
+
14
+ const RDS_TYPES = new Set(["AWS::RDS::DBInstance", "AWS::RDS::DBCluster"]);
15
+
16
+ export function checkRdsDeletionProtection(ctx: PostSynthContext): PostSynthDiagnostic[] {
17
+ const diagnostics: PostSynthDiagnostic[] = [];
18
+ const severity = isFullTierEnv(ctx.env) ? "error" : "warning";
19
+
20
+ for (const [_lexicon, output] of ctx.outputs) {
21
+ const template = parseCFTemplate(output);
22
+ if (!template?.Resources) continue;
23
+
24
+ for (const [logicalId, resource] of Object.entries(template.Resources)) {
25
+ if (!RDS_TYPES.has(resource.Type)) continue;
26
+
27
+ const props = resource.Properties ?? {};
28
+ const deletionProtection = props.DeletionProtection;
29
+
30
+ if (isIntrinsic(deletionProtection)) continue;
31
+
32
+ if (deletionProtection !== true) {
33
+ diagnostics.push({
34
+ checkId: "WAW040",
35
+ severity,
36
+ message: `RDS resource "${logicalId}" (${resource.Type}) does not have DeletionProtection: true — enable it on the full/production tier`,
37
+ entity: logicalId,
38
+ lexicon: "aws",
39
+ });
40
+ }
41
+ }
42
+ }
43
+
44
+ return diagnostics;
45
+ }
46
+
47
+ export const waw040: PostSynthCheck = {
48
+ id: "WAW040",
49
+ description: "RDS instance or cluster does not have DeletionProtection enabled (full tier)",
50
+
51
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
52
+ return checkRdsDeletionProtection(ctx);
53
+ },
54
+ };
@@ -0,0 +1,72 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { createPostSynthContext } from "@intentius/chant-test-utils";
3
+ import { waw041, checkDbProxyTls } from "./waw041";
4
+
5
+ function makeCtx(template: object) {
6
+ return createPostSynthContext({ aws: template });
7
+ }
8
+
9
+ describe("WAW041: RDS Proxy TLS Not Required", () => {
10
+ test("check metadata", () => {
11
+ expect(waw041.id).toBe("WAW041");
12
+ expect(waw041.description).toContain("TLS");
13
+ });
14
+
15
+ test("flags DBProxy missing RequireTLS", () => {
16
+ const ctx = makeCtx({
17
+ Resources: {
18
+ MyProxy: {
19
+ Type: "AWS::RDS::DBProxy",
20
+ Properties: { DBProxyName: "my-proxy" },
21
+ },
22
+ },
23
+ });
24
+ const diags = checkDbProxyTls(ctx);
25
+ expect(diags).toHaveLength(1);
26
+ expect(diags[0].checkId).toBe("WAW041");
27
+ expect(diags[0].severity).toBe("error");
28
+ });
29
+
30
+ test("flags DBProxy with RequireTLS: false", () => {
31
+ const ctx = makeCtx({
32
+ Resources: {
33
+ MyProxy: {
34
+ Type: "AWS::RDS::DBProxy",
35
+ Properties: { RequireTLS: false },
36
+ },
37
+ },
38
+ });
39
+ expect(checkDbProxyTls(ctx)).toHaveLength(1);
40
+ });
41
+
42
+ test("no diagnostic when RequireTLS: true", () => {
43
+ const ctx = makeCtx({
44
+ Resources: {
45
+ MyProxy: {
46
+ Type: "AWS::RDS::DBProxy",
47
+ Properties: { RequireTLS: true },
48
+ },
49
+ },
50
+ });
51
+ expect(checkDbProxyTls(ctx)).toHaveLength(0);
52
+ });
53
+
54
+ test("skips intrinsic value for RequireTLS", () => {
55
+ const ctx = makeCtx({
56
+ Resources: {
57
+ MyProxy: {
58
+ Type: "AWS::RDS::DBProxy",
59
+ Properties: { RequireTLS: { Ref: "TlsParam" } },
60
+ },
61
+ },
62
+ });
63
+ expect(checkDbProxyTls(ctx)).toHaveLength(0);
64
+ });
65
+
66
+ test("no diagnostic for non-DBProxy resources", () => {
67
+ const ctx = makeCtx({
68
+ Resources: { MyDB: { Type: "AWS::RDS::DBInstance", Properties: {} } },
69
+ });
70
+ expect(checkDbProxyTls(ctx)).toHaveLength(0);
71
+ });
72
+ });
@@ -0,0 +1,48 @@
1
+ /**
2
+ * WAW041: RDS Proxy TLS Not Required
3
+ *
4
+ * Flags RDS Proxy resources without RequireTLS: true — without it, a client
5
+ * can connect to the proxy over plaintext.
6
+ */
7
+
8
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
9
+ import { parseCFTemplate, isIntrinsic } from "./cf-refs";
10
+
11
+ export function checkDbProxyTls(ctx: PostSynthContext): PostSynthDiagnostic[] {
12
+ const diagnostics: PostSynthDiagnostic[] = [];
13
+
14
+ for (const [_lexicon, output] of ctx.outputs) {
15
+ const template = parseCFTemplate(output);
16
+ if (!template?.Resources) continue;
17
+
18
+ for (const [logicalId, resource] of Object.entries(template.Resources)) {
19
+ if (resource.Type !== "AWS::RDS::DBProxy") continue;
20
+
21
+ const props = resource.Properties ?? {};
22
+ const requireTls = props.RequireTLS;
23
+
24
+ if (isIntrinsic(requireTls)) continue;
25
+
26
+ if (requireTls !== true) {
27
+ diagnostics.push({
28
+ checkId: "WAW041",
29
+ severity: "error",
30
+ message: `RDS Proxy "${logicalId}" does not have RequireTLS: true — clients can connect over plaintext`,
31
+ entity: logicalId,
32
+ lexicon: "aws",
33
+ });
34
+ }
35
+ }
36
+ }
37
+
38
+ return diagnostics;
39
+ }
40
+
41
+ export const waw041: PostSynthCheck = {
42
+ id: "WAW041",
43
+ description: "RDS Proxy does not require TLS for client connections",
44
+
45
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
46
+ return checkDbProxyTls(ctx);
47
+ },
48
+ };
@@ -0,0 +1,138 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { createPostSynthContext } from "@intentius/chant-test-utils";
3
+ import { waw042, checkS3TlsOnlyPolicy } from "./waw042";
4
+
5
+ function makeCtx(template: object) {
6
+ return createPostSynthContext({ aws: template });
7
+ }
8
+
9
+ const secureTransportDeny = {
10
+ Effect: "Deny",
11
+ Principal: "*",
12
+ Action: "s3:*",
13
+ Condition: { Bool: { "aws:SecureTransport": "false" } },
14
+ };
15
+
16
+ describe("WAW042: S3 Bucket Missing TLS-Only Policy", () => {
17
+ test("check metadata", () => {
18
+ expect(waw042.id).toBe("WAW042");
19
+ expect(waw042.description).toContain("TLS");
20
+ });
21
+
22
+ test("flags a bucket with no BucketPolicy at all", () => {
23
+ const ctx = makeCtx({
24
+ Resources: {
25
+ MyBucket: { Type: "AWS::S3::Bucket", Properties: {} },
26
+ },
27
+ });
28
+ const diags = checkS3TlsOnlyPolicy(ctx);
29
+ expect(diags).toHaveLength(1);
30
+ expect(diags[0].checkId).toBe("WAW042");
31
+ expect(diags[0].entity).toBe("MyBucket");
32
+ });
33
+
34
+ test("flags a bucket whose BucketPolicy has no SecureTransport deny", () => {
35
+ const ctx = makeCtx({
36
+ Resources: {
37
+ MyBucket: { Type: "AWS::S3::Bucket", Properties: {} },
38
+ MyBucketPolicy: {
39
+ Type: "AWS::S3::BucketPolicy",
40
+ Properties: {
41
+ Bucket: { Ref: "MyBucket" },
42
+ PolicyDocument: {
43
+ Statement: [{ Effect: "Allow", Principal: "*", Action: "s3:GetObject" }],
44
+ },
45
+ },
46
+ },
47
+ },
48
+ });
49
+ expect(checkS3TlsOnlyPolicy(ctx)).toHaveLength(1);
50
+ });
51
+
52
+ test("no diagnostic when the BucketPolicy denies non-TLS requests (Ref)", () => {
53
+ const ctx = makeCtx({
54
+ Resources: {
55
+ MyBucket: { Type: "AWS::S3::Bucket", Properties: {} },
56
+ MyBucketPolicy: {
57
+ Type: "AWS::S3::BucketPolicy",
58
+ Properties: {
59
+ Bucket: { Ref: "MyBucket" },
60
+ PolicyDocument: { Statement: [secureTransportDeny] },
61
+ },
62
+ },
63
+ },
64
+ });
65
+ expect(checkS3TlsOnlyPolicy(ctx)).toHaveLength(0);
66
+ });
67
+
68
+ test("no diagnostic when the BucketPolicy targets the bucket via Fn::GetAtt", () => {
69
+ const ctx = makeCtx({
70
+ Resources: {
71
+ MyBucket: { Type: "AWS::S3::Bucket", Properties: {} },
72
+ MyBucketPolicy: {
73
+ Type: "AWS::S3::BucketPolicy",
74
+ Properties: {
75
+ Bucket: { "Fn::GetAtt": ["MyBucket", "Arn"] },
76
+ PolicyDocument: { Statement: [secureTransportDeny] },
77
+ },
78
+ },
79
+ },
80
+ });
81
+ expect(checkS3TlsOnlyPolicy(ctx)).toHaveLength(0);
82
+ });
83
+
84
+ test("a boolean (non-string) SecureTransport condition also satisfies the check", () => {
85
+ const ctx = makeCtx({
86
+ Resources: {
87
+ MyBucket: { Type: "AWS::S3::Bucket", Properties: {} },
88
+ MyBucketPolicy: {
89
+ Type: "AWS::S3::BucketPolicy",
90
+ Properties: {
91
+ Bucket: { Ref: "MyBucket" },
92
+ PolicyDocument: {
93
+ Statement: [{ Effect: "Deny", Condition: { Bool: { "aws:SecureTransport": false } } }],
94
+ },
95
+ },
96
+ },
97
+ },
98
+ });
99
+ expect(checkS3TlsOnlyPolicy(ctx)).toHaveLength(0);
100
+ });
101
+
102
+ test("does not credit an Allow statement's SecureTransport condition", () => {
103
+ const ctx = makeCtx({
104
+ Resources: {
105
+ MyBucket: { Type: "AWS::S3::Bucket", Properties: {} },
106
+ MyBucketPolicy: {
107
+ Type: "AWS::S3::BucketPolicy",
108
+ Properties: {
109
+ Bucket: { Ref: "MyBucket" },
110
+ PolicyDocument: {
111
+ Statement: [{ Effect: "Allow", Condition: { Bool: { "aws:SecureTransport": "false" } } }],
112
+ },
113
+ },
114
+ },
115
+ },
116
+ });
117
+ expect(checkS3TlsOnlyPolicy(ctx)).toHaveLength(1);
118
+ });
119
+
120
+ test("a policy covering a different bucket does not clear this one", () => {
121
+ const ctx = makeCtx({
122
+ Resources: {
123
+ MyBucket: { Type: "AWS::S3::Bucket", Properties: {} },
124
+ OtherBucket: { Type: "AWS::S3::Bucket", Properties: {} },
125
+ OtherBucketPolicy: {
126
+ Type: "AWS::S3::BucketPolicy",
127
+ Properties: {
128
+ Bucket: { Ref: "OtherBucket" },
129
+ PolicyDocument: { Statement: [secureTransportDeny] },
130
+ },
131
+ },
132
+ },
133
+ });
134
+ const diags = checkS3TlsOnlyPolicy(ctx);
135
+ expect(diags).toHaveLength(1);
136
+ expect(diags[0].entity).toBe("MyBucket");
137
+ });
138
+ });