@intentius/chant-lexicon-gcp 0.0.15

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 (122) hide show
  1. package/dist/integrity.json +36 -0
  2. package/dist/manifest.json +12 -0
  3. package/dist/meta.json +10919 -0
  4. package/dist/rules/gcp-helpers.ts +117 -0
  5. package/dist/rules/hardcoded-project.ts +58 -0
  6. package/dist/rules/hardcoded-region.ts +56 -0
  7. package/dist/rules/public-iam.ts +43 -0
  8. package/dist/rules/wgc101.ts +56 -0
  9. package/dist/rules/wgc102.ts +35 -0
  10. package/dist/rules/wgc103.ts +45 -0
  11. package/dist/rules/wgc104.ts +42 -0
  12. package/dist/rules/wgc105.ts +46 -0
  13. package/dist/rules/wgc106.ts +36 -0
  14. package/dist/rules/wgc107.ts +39 -0
  15. package/dist/rules/wgc108.ts +41 -0
  16. package/dist/rules/wgc109.ts +39 -0
  17. package/dist/rules/wgc110.ts +38 -0
  18. package/dist/rules/wgc111.ts +54 -0
  19. package/dist/rules/wgc112.ts +56 -0
  20. package/dist/rules/wgc113.ts +42 -0
  21. package/dist/rules/wgc201.ts +36 -0
  22. package/dist/rules/wgc202.ts +39 -0
  23. package/dist/rules/wgc203.ts +44 -0
  24. package/dist/rules/wgc204.ts +39 -0
  25. package/dist/rules/wgc301.ts +34 -0
  26. package/dist/rules/wgc302.ts +34 -0
  27. package/dist/rules/wgc303.ts +37 -0
  28. package/dist/skills/chant-gcp-patterns.md +367 -0
  29. package/dist/skills/chant-gcp-security.md +276 -0
  30. package/dist/skills/chant-gcp.md +108 -0
  31. package/dist/types/index.d.ts +26529 -0
  32. package/package.json +35 -0
  33. package/src/actions/index.ts +52 -0
  34. package/src/codegen/docs-cli.ts +7 -0
  35. package/src/codegen/docs.ts +820 -0
  36. package/src/codegen/generate-cli.ts +24 -0
  37. package/src/codegen/generate.ts +252 -0
  38. package/src/codegen/naming.test.ts +49 -0
  39. package/src/codegen/naming.ts +132 -0
  40. package/src/codegen/package.ts +66 -0
  41. package/src/composites/cloud-function.ts +117 -0
  42. package/src/composites/cloud-run-service.ts +124 -0
  43. package/src/composites/cloud-sql-instance.ts +126 -0
  44. package/src/composites/composites.test.ts +432 -0
  45. package/src/composites/gcs-bucket.ts +111 -0
  46. package/src/composites/gke-cluster.ts +125 -0
  47. package/src/composites/index.ts +20 -0
  48. package/src/composites/managed-certificate.ts +79 -0
  49. package/src/composites/private-service.ts +95 -0
  50. package/src/composites/pubsub-pipeline.ts +102 -0
  51. package/src/composites/secure-project.ts +128 -0
  52. package/src/composites/vpc-network.ts +165 -0
  53. package/src/coverage.test.ts +27 -0
  54. package/src/coverage.ts +51 -0
  55. package/src/default-labels.test.ts +111 -0
  56. package/src/default-labels.ts +93 -0
  57. package/src/generated/index.d.ts +26529 -0
  58. package/src/generated/index.ts +1723 -0
  59. package/src/generated/lexicon-gcp.json +10919 -0
  60. package/src/generated/runtime.ts +4 -0
  61. package/src/import/generator.test.ts +125 -0
  62. package/src/import/generator.ts +82 -0
  63. package/src/import/parser.test.ts +167 -0
  64. package/src/import/parser.ts +80 -0
  65. package/src/import/roundtrip.test.ts +66 -0
  66. package/src/index.ts +54 -0
  67. package/src/lint/post-synth/gcp-helpers.ts +117 -0
  68. package/src/lint/post-synth/index.ts +20 -0
  69. package/src/lint/post-synth/post-synth.test.ts +693 -0
  70. package/src/lint/post-synth/wgc101.ts +56 -0
  71. package/src/lint/post-synth/wgc102.ts +35 -0
  72. package/src/lint/post-synth/wgc103.ts +45 -0
  73. package/src/lint/post-synth/wgc104.ts +42 -0
  74. package/src/lint/post-synth/wgc105.ts +46 -0
  75. package/src/lint/post-synth/wgc106.ts +36 -0
  76. package/src/lint/post-synth/wgc107.ts +39 -0
  77. package/src/lint/post-synth/wgc108.ts +41 -0
  78. package/src/lint/post-synth/wgc109.ts +39 -0
  79. package/src/lint/post-synth/wgc110.ts +38 -0
  80. package/src/lint/post-synth/wgc111.ts +54 -0
  81. package/src/lint/post-synth/wgc112.ts +56 -0
  82. package/src/lint/post-synth/wgc113.ts +42 -0
  83. package/src/lint/post-synth/wgc201.ts +36 -0
  84. package/src/lint/post-synth/wgc202.ts +39 -0
  85. package/src/lint/post-synth/wgc203.ts +44 -0
  86. package/src/lint/post-synth/wgc204.ts +39 -0
  87. package/src/lint/post-synth/wgc301.ts +34 -0
  88. package/src/lint/post-synth/wgc302.ts +34 -0
  89. package/src/lint/post-synth/wgc303.ts +37 -0
  90. package/src/lint/rules/hardcoded-project.ts +58 -0
  91. package/src/lint/rules/hardcoded-region.ts +56 -0
  92. package/src/lint/rules/index.ts +3 -0
  93. package/src/lint/rules/public-iam.ts +43 -0
  94. package/src/lint/rules/rules.test.ts +63 -0
  95. package/src/lsp/completions.test.ts +67 -0
  96. package/src/lsp/completions.ts +17 -0
  97. package/src/lsp/hover.test.ts +66 -0
  98. package/src/lsp/hover.ts +54 -0
  99. package/src/package-cli.ts +24 -0
  100. package/src/plugin.test.ts +250 -0
  101. package/src/plugin.ts +405 -0
  102. package/src/pseudo.test.ts +40 -0
  103. package/src/pseudo.ts +19 -0
  104. package/src/serializer.test.ts +250 -0
  105. package/src/serializer.ts +232 -0
  106. package/src/skills/chant-gcp-patterns.md +367 -0
  107. package/src/skills/chant-gcp-security.md +276 -0
  108. package/src/skills/chant-gcp.md +108 -0
  109. package/src/spec/fetch.test.ts +16 -0
  110. package/src/spec/fetch.ts +121 -0
  111. package/src/spec/parse.test.ts +163 -0
  112. package/src/spec/parse.ts +432 -0
  113. package/src/testdata/compute-instance.yaml +93 -0
  114. package/src/testdata/iam-policy-member.yaml +66 -0
  115. package/src/testdata/manifests/compute-instance.yaml +18 -0
  116. package/src/testdata/manifests/full-app.yaml +34 -0
  117. package/src/testdata/manifests/storage-bucket.yaml +12 -0
  118. package/src/testdata/storage-bucket.yaml +100 -0
  119. package/src/validate-cli.ts +13 -0
  120. package/src/validate.test.ts +38 -0
  121. package/src/validate.ts +30 -0
  122. package/src/variables.ts +15 -0
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Shared helpers for GCP Config Connector post-synthesis lint rules.
3
+ *
4
+ * Provides YAML parsing for multi-document Config Connector manifests
5
+ * and accessor utilities for common manifest fields.
6
+ */
7
+
8
+ import { parseYAML } from "@intentius/chant/yaml";
9
+ export { getPrimaryOutput } from "@intentius/chant/lint/post-synth";
10
+
11
+ /**
12
+ * A parsed Config Connector manifest (loosely typed).
13
+ */
14
+ export interface GcpManifest {
15
+ apiVersion?: string;
16
+ kind?: string;
17
+ metadata?: {
18
+ name?: string;
19
+ namespace?: string;
20
+ labels?: Record<string, string>;
21
+ annotations?: Record<string, string>;
22
+ [key: string]: unknown;
23
+ };
24
+ spec?: Record<string, unknown>;
25
+ [key: string]: unknown;
26
+ }
27
+
28
+ /**
29
+ * Split a multi-document YAML string on `---` boundaries and parse each
30
+ * document into a GcpManifest.
31
+ */
32
+ export function parseGcpManifests(yaml: string): GcpManifest[] {
33
+ const documents = yaml.split(/^---\s*$/m);
34
+ const manifests: GcpManifest[] = [];
35
+
36
+ for (const doc of documents) {
37
+ const trimmed = doc.trim();
38
+ if (trimmed === "" || trimmed === "---") continue;
39
+ try {
40
+ const parsed = parseYAML(trimmed);
41
+ if (typeof parsed === "object" && parsed !== null) {
42
+ manifests.push(parsed as GcpManifest);
43
+ }
44
+ } catch {
45
+ // Skip unparseable documents
46
+ }
47
+ }
48
+
49
+ return manifests;
50
+ }
51
+
52
+ /**
53
+ * Check if a manifest is a Config Connector resource
54
+ * (apiVersion contains cnrm.cloud.google.com).
55
+ */
56
+ export function isConfigConnectorResource(manifest: GcpManifest): boolean {
57
+ return typeof manifest.apiVersion === "string" &&
58
+ manifest.apiVersion.includes("cnrm.cloud.google.com");
59
+ }
60
+
61
+ /**
62
+ * Safely extract the spec from a manifest.
63
+ */
64
+ export function getSpec(manifest: GcpManifest): Record<string, unknown> | undefined {
65
+ return manifest.spec ?? undefined;
66
+ }
67
+
68
+ /**
69
+ * Safely extract annotations from a manifest's metadata.
70
+ */
71
+ export function getAnnotations(manifest: GcpManifest): Record<string, string> | undefined {
72
+ return manifest.metadata?.annotations ?? undefined;
73
+ }
74
+
75
+ /**
76
+ * Get the resource name from metadata.
77
+ */
78
+ export function getResourceName(manifest: GcpManifest): string {
79
+ return manifest.metadata?.name ?? "unknown";
80
+ }
81
+
82
+ /**
83
+ * Recursively walk a spec object looking for keys ending in `Ref`
84
+ * (e.g. `networkRef`, `topicRef`, `clusterRef`) that have a `name`
85
+ * sub-field. Returns the set of referenced names.
86
+ *
87
+ * Skips `external` refs (cross-project references outside the template).
88
+ */
89
+ export function findResourceRefs(obj: unknown): Set<string> {
90
+ const refs = new Set<string>();
91
+ walkForRefs(obj, refs);
92
+ return refs;
93
+ }
94
+
95
+ function walkForRefs(value: unknown, refs: Set<string>): void {
96
+ if (value === null || value === undefined) return;
97
+
98
+ if (Array.isArray(value)) {
99
+ for (const item of value) {
100
+ walkForRefs(item, refs);
101
+ }
102
+ return;
103
+ }
104
+
105
+ if (typeof value === "object") {
106
+ const obj = value as Record<string, unknown>;
107
+ for (const [key, val] of Object.entries(obj)) {
108
+ if (key.endsWith("Ref") && typeof val === "object" && val !== null) {
109
+ const refObj = val as Record<string, unknown>;
110
+ if (typeof refObj.name === "string" && !refObj.external) {
111
+ refs.add(refObj.name);
112
+ }
113
+ }
114
+ walkForRefs(val, refs);
115
+ }
116
+ }
117
+ }
@@ -0,0 +1,58 @@
1
+ import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
2
+ import * as ts from "typescript";
3
+
4
+ /**
5
+ * WGC001: Hardcoded GCP Project ID
6
+ *
7
+ * Detects hardcoded GCP project IDs in code.
8
+ * Project IDs should use GCP.ProjectId pseudo-parameter instead.
9
+ */
10
+ export const hardcodedProjectRule: LintRule = {
11
+ id: "WGC001",
12
+ severity: "warning",
13
+ category: "security",
14
+ description: "Detects hardcoded GCP project IDs — use GCP.ProjectId pseudo-parameter instead",
15
+
16
+ check(context: LintContext): LintDiagnostic[] {
17
+ const { sourceFile } = context;
18
+ const diagnostics: LintDiagnostic[] = [];
19
+
20
+ // GCP project ID pattern: lowercase letters, digits, hyphens, 6-30 chars
21
+ // We look for strings in annotation contexts like "cnrm.cloud.google.com/project-id"
22
+ const projectIdPattern = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/;
23
+
24
+ // Common false positives
25
+ const falsePositives = new Set([
26
+ "chant", "default", "kube-system", "kube-public", "kube-node-lease",
27
+ "config-connector", "cnrm-system", "my-project",
28
+ ]);
29
+
30
+ function visit(node: ts.Node): void {
31
+ if (ts.isPropertyAssignment(node) || ts.isPropertyDeclaration(node)) {
32
+ const propName = node.name?.getText(sourceFile) ?? "";
33
+ // Only flag project-id annotation values
34
+ if (propName.includes("project-id") || propName.includes("projectId") || propName === '"cnrm.cloud.google.com/project-id"') {
35
+ const initializer = ts.isPropertyAssignment(node) ? node.initializer : undefined;
36
+ if (initializer && ts.isStringLiteral(initializer)) {
37
+ const value = initializer.text;
38
+ if (projectIdPattern.test(value) && !falsePositives.has(value)) {
39
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(initializer.getStart());
40
+ diagnostics.push({
41
+ file: sourceFile.fileName,
42
+ line: line + 1,
43
+ column: character + 1,
44
+ ruleId: "WGC001",
45
+ severity: "warning",
46
+ message: `Hardcoded project ID "${value}" detected. Use GCP.ProjectId pseudo-parameter instead.`,
47
+ });
48
+ }
49
+ }
50
+ }
51
+ }
52
+ ts.forEachChild(node, visit);
53
+ }
54
+
55
+ visit(sourceFile);
56
+ return diagnostics;
57
+ },
58
+ };
@@ -0,0 +1,56 @@
1
+ import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
2
+ import * as ts from "typescript";
3
+
4
+ /**
5
+ * WGC002: Hardcoded GCP Region/Zone
6
+ *
7
+ * Detects hardcoded GCP region and zone strings in code.
8
+ * Regions/zones should use GCP.Region or GCP.Zone pseudo-parameters instead.
9
+ */
10
+ export const hardcodedRegionRule: LintRule = {
11
+ id: "WGC002",
12
+ severity: "warning",
13
+ category: "security",
14
+ description: "Detects hardcoded GCP region/zone strings — use GCP.Region/GCP.Zone pseudo-parameters instead",
15
+
16
+ check(context: LintContext): LintDiagnostic[] {
17
+ const { sourceFile } = context;
18
+ const diagnostics: LintDiagnostic[] = [];
19
+
20
+ // GCP region pattern: continent-direction[N] (e.g., us-central1, europe-west4)
21
+ const regionPattern = /^(us|europe|asia|australia|southamerica|northamerica|me|africa)-(central|east|west|south|north|northeast|southeast|southwest|northwest)\d+$/;
22
+ // GCP zone pattern: region-[a-f] (e.g., us-central1-a)
23
+ const zonePattern = /^(us|europe|asia|australia|southamerica|northamerica|me|africa)-(central|east|west|south|north|northeast|southeast|southwest|northwest)\d+-[a-f]$/;
24
+
25
+ function visit(node: ts.Node): void {
26
+ if (ts.isStringLiteral(node)) {
27
+ const value = node.text;
28
+ if (zonePattern.test(value)) {
29
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
30
+ diagnostics.push({
31
+ file: sourceFile.fileName,
32
+ line: line + 1,
33
+ column: character + 1,
34
+ ruleId: "WGC002",
35
+ severity: "warning",
36
+ message: `Hardcoded zone "${value}" detected. Use GCP.Zone pseudo-parameter instead.`,
37
+ });
38
+ } else if (regionPattern.test(value)) {
39
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
40
+ diagnostics.push({
41
+ file: sourceFile.fileName,
42
+ line: line + 1,
43
+ column: character + 1,
44
+ ruleId: "WGC002",
45
+ severity: "warning",
46
+ message: `Hardcoded region "${value}" detected. Use GCP.Region pseudo-parameter instead.`,
47
+ });
48
+ }
49
+ }
50
+ ts.forEachChild(node, visit);
51
+ }
52
+
53
+ visit(sourceFile);
54
+ return diagnostics;
55
+ },
56
+ };
@@ -0,0 +1,43 @@
1
+ import type { LintRule, LintDiagnostic, LintContext } from "@intentius/chant/lint/rule";
2
+ import * as ts from "typescript";
3
+
4
+ /**
5
+ * WGC003: Public IAM Binding
6
+ *
7
+ * Warns on allUsers or allAuthenticatedUsers IAM bindings,
8
+ * which grant public access to GCP resources.
9
+ */
10
+ export const publicIamRule: LintRule = {
11
+ id: "WGC003",
12
+ severity: "warning",
13
+ category: "security",
14
+ description: "Warns on allUsers/allAuthenticatedUsers IAM members — grants public access",
15
+
16
+ check(context: LintContext): LintDiagnostic[] {
17
+ const { sourceFile } = context;
18
+ const diagnostics: LintDiagnostic[] = [];
19
+
20
+ const publicMembers = new Set(["allUsers", "allAuthenticatedUsers"]);
21
+
22
+ function visit(node: ts.Node): void {
23
+ if (ts.isStringLiteral(node)) {
24
+ const value = node.text;
25
+ if (publicMembers.has(value)) {
26
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
27
+ diagnostics.push({
28
+ file: sourceFile.fileName,
29
+ line: line + 1,
30
+ column: character + 1,
31
+ ruleId: "WGC003",
32
+ severity: "warning",
33
+ message: `Public IAM member "${value}" detected. This grants access to everyone${value === "allAuthenticatedUsers" ? " with a Google account" : " on the internet"}.`,
34
+ });
35
+ }
36
+ }
37
+ ts.forEachChild(node, visit);
38
+ }
39
+
40
+ visit(sourceFile);
41
+ return diagnostics;
42
+ },
43
+ };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * WGC101: Missing encryption on StorageBucket or SQLInstance
3
+ */
4
+
5
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
6
+ import { parseYAML } from "@intentius/chant/yaml";
7
+
8
+ export const wgc101: PostSynthCheck = {
9
+ id: "WGC101",
10
+ description: "StorageBucket or SQLInstance without encryption configuration",
11
+
12
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [_lexicon, output] of ctx.outputs) {
16
+ if (typeof output !== "string") continue;
17
+
18
+ const documents = output.split(/^---\s*$/m).filter((d) => d.trim().length > 0);
19
+
20
+ for (const docStr of documents) {
21
+ const doc = parseYAML(docStr) as Record<string, unknown> | null;
22
+ if (!doc) continue;
23
+
24
+ const kind = doc.kind as string | undefined;
25
+ const metadata = doc.metadata as Record<string, unknown> | undefined;
26
+ const spec = doc.spec as Record<string, unknown> | undefined;
27
+ const name = (metadata?.name as string) ?? "unknown";
28
+
29
+ if (kind === "StorageBucket" && spec && !spec.encryption) {
30
+ diagnostics.push({
31
+ checkId: "WGC101",
32
+ severity: "warning",
33
+ message: `StorageBucket "${name}" has no encryption configuration — consider adding spec.encryption.defaultKmsKeyName`,
34
+ entity: name,
35
+ lexicon: "gcp",
36
+ });
37
+ }
38
+
39
+ if (kind === "SQLInstance" && spec) {
40
+ const settings = spec.settings as Record<string, unknown> | undefined;
41
+ if (!settings?.ipConfiguration || !(settings as Record<string, unknown>).backupConfiguration) {
42
+ diagnostics.push({
43
+ checkId: "WGC101",
44
+ severity: "warning",
45
+ message: `SQLInstance "${name}" may be missing encryption or backup configuration`,
46
+ entity: name,
47
+ lexicon: "gcp",
48
+ });
49
+ }
50
+ }
51
+ }
52
+ }
53
+
54
+ return diagnostics;
55
+ },
56
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * WGC102: Public IAM in serialized output
3
+ */
4
+
5
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
6
+
7
+ export const wgc102: PostSynthCheck = {
8
+ id: "WGC102",
9
+ description: "allUsers/allAuthenticatedUsers detected in serialized Config Connector YAML",
10
+
11
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
12
+ const diagnostics: PostSynthDiagnostic[] = [];
13
+
14
+ for (const [_lexicon, output] of ctx.outputs) {
15
+ if (typeof output !== "string") continue;
16
+
17
+ const lines = output.split("\n");
18
+ for (let i = 0; i < lines.length; i++) {
19
+ const line = lines[i];
20
+ if (line.includes("allUsers") || line.includes("allAuthenticatedUsers")) {
21
+ const member = line.includes("allUsers") ? "allUsers" : "allAuthenticatedUsers";
22
+ diagnostics.push({
23
+ checkId: "WGC102",
24
+ severity: "warning",
25
+ message: `Public IAM member "${member}" found in output (line ${i + 1}) — this grants public access`,
26
+ entity: `line:${i + 1}`,
27
+ lexicon: "gcp",
28
+ });
29
+ }
30
+ }
31
+ }
32
+
33
+ return diagnostics;
34
+ },
35
+ };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * WGC103: Missing project annotation
3
+ */
4
+
5
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
6
+ import { parseYAML } from "@intentius/chant/yaml";
7
+
8
+ export const wgc103: PostSynthCheck = {
9
+ id: "WGC103",
10
+ description: "Config Connector resource without cnrm.cloud.google.com/project-id annotation",
11
+
12
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [_lexicon, output] of ctx.outputs) {
16
+ if (typeof output !== "string") continue;
17
+
18
+ const documents = output.split(/^---\s*$/m).filter((d) => d.trim().length > 0);
19
+
20
+ for (const docStr of documents) {
21
+ const doc = parseYAML(docStr) as Record<string, unknown> | null;
22
+ if (!doc) continue;
23
+
24
+ const apiVersion = doc.apiVersion as string | undefined;
25
+ if (!apiVersion?.includes("cnrm.cloud.google.com")) continue;
26
+
27
+ const metadata = doc.metadata as Record<string, unknown> | undefined;
28
+ const annotations = metadata?.annotations as Record<string, unknown> | undefined;
29
+ const name = (metadata?.name as string) ?? "unknown";
30
+
31
+ if (!annotations?.["cnrm.cloud.google.com/project-id"]) {
32
+ diagnostics.push({
33
+ checkId: "WGC103",
34
+ severity: "info",
35
+ message: `Resource "${name}" has no cnrm.cloud.google.com/project-id annotation — will use namespace default`,
36
+ entity: name,
37
+ lexicon: "gcp",
38
+ });
39
+ }
40
+ }
41
+ }
42
+
43
+ return diagnostics;
44
+ },
45
+ };
@@ -0,0 +1,42 @@
1
+ /**
2
+ * WGC104: Missing uniform bucket-level access
3
+ */
4
+
5
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
6
+ import { parseYAML } from "@intentius/chant/yaml";
7
+
8
+ export const wgc104: PostSynthCheck = {
9
+ id: "WGC104",
10
+ description: "StorageBucket without uniformBucketLevelAccess enabled",
11
+
12
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [_lexicon, output] of ctx.outputs) {
16
+ if (typeof output !== "string") continue;
17
+
18
+ const documents = output.split(/^---\s*$/m).filter((d) => d.trim().length > 0);
19
+
20
+ for (const docStr of documents) {
21
+ const doc = parseYAML(docStr) as Record<string, unknown> | null;
22
+ if (!doc || doc.kind !== "StorageBucket") continue;
23
+
24
+ const metadata = doc.metadata as Record<string, unknown> | undefined;
25
+ const spec = doc.spec as Record<string, unknown> | undefined;
26
+ const name = (metadata?.name as string) ?? "unknown";
27
+
28
+ if (spec && spec.uniformBucketLevelAccess !== true) {
29
+ diagnostics.push({
30
+ checkId: "WGC104",
31
+ severity: "warning",
32
+ message: `StorageBucket "${name}" does not have uniformBucketLevelAccess enabled — recommended for consistent access control`,
33
+ entity: name,
34
+ lexicon: "gcp",
35
+ });
36
+ }
37
+ }
38
+ }
39
+
40
+ return diagnostics;
41
+ },
42
+ };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * WGC105: Public Cloud SQL — authorizedNetworks with 0.0.0.0/0
3
+ */
4
+
5
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
6
+ import { parseGcpManifests, getSpec, getResourceName } from "./gcp-helpers";
7
+
8
+ export const wgc105: PostSynthCheck = {
9
+ id: "WGC105",
10
+ description: "Cloud SQL instance with public 0.0.0.0/0 in authorizedNetworks",
11
+
12
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [, output] of ctx.outputs) {
16
+ if (typeof output !== "string") continue;
17
+
18
+ for (const manifest of parseGcpManifests(output)) {
19
+ if (manifest.kind !== "SQLInstance") continue;
20
+
21
+ const spec = getSpec(manifest);
22
+ if (!spec) continue;
23
+
24
+ const settings = spec.settings as Record<string, unknown> | undefined;
25
+ const ipConfig = settings?.ipConfiguration as Record<string, unknown> | undefined;
26
+ const networks = ipConfig?.authorizedNetworks as Array<Record<string, unknown>> | undefined;
27
+
28
+ if (Array.isArray(networks)) {
29
+ for (const net of networks) {
30
+ if (net.value === "0.0.0.0/0") {
31
+ diagnostics.push({
32
+ checkId: "WGC105",
33
+ severity: "warning",
34
+ message: `SQLInstance "${getResourceName(manifest)}" allows connections from 0.0.0.0/0 — this exposes the database to the public internet`,
35
+ entity: getResourceName(manifest),
36
+ lexicon: "gcp",
37
+ });
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+ return diagnostics;
45
+ },
46
+ };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * WGC106: Missing deletion policy annotation
3
+ */
4
+
5
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
6
+ import { parseGcpManifests, isConfigConnectorResource, getAnnotations, getResourceName } from "./gcp-helpers";
7
+
8
+ export const wgc106: PostSynthCheck = {
9
+ id: "WGC106",
10
+ description: "Config Connector resource without cnrm.cloud.google.com/deletion-policy annotation",
11
+
12
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [, output] of ctx.outputs) {
16
+ if (typeof output !== "string") continue;
17
+
18
+ for (const manifest of parseGcpManifests(output)) {
19
+ if (!isConfigConnectorResource(manifest)) continue;
20
+
21
+ const annotations = getAnnotations(manifest);
22
+ if (!annotations?.["cnrm.cloud.google.com/deletion-policy"]) {
23
+ diagnostics.push({
24
+ checkId: "WGC106",
25
+ severity: "info",
26
+ message: `Resource "${getResourceName(manifest)}" has no deletion-policy annotation — defaults to "delete" which removes the GCP resource on kubectl delete`,
27
+ entity: getResourceName(manifest),
28
+ lexicon: "gcp",
29
+ });
30
+ }
31
+ }
32
+ }
33
+
34
+ return diagnostics;
35
+ },
36
+ };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * WGC107: StorageBucket missing versioning
3
+ */
4
+
5
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
6
+ import { parseGcpManifests, getSpec, getResourceName } from "./gcp-helpers";
7
+
8
+ export const wgc107: PostSynthCheck = {
9
+ id: "WGC107",
10
+ description: "StorageBucket without versioning enabled",
11
+
12
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [, output] of ctx.outputs) {
16
+ if (typeof output !== "string") continue;
17
+
18
+ for (const manifest of parseGcpManifests(output)) {
19
+ if (manifest.kind !== "StorageBucket") continue;
20
+
21
+ const spec = getSpec(manifest);
22
+ if (!spec) continue;
23
+
24
+ const versioning = spec.versioning as Record<string, unknown> | undefined;
25
+ if (!versioning || versioning.enabled !== true) {
26
+ diagnostics.push({
27
+ checkId: "WGC107",
28
+ severity: "info",
29
+ message: `StorageBucket "${getResourceName(manifest)}" does not have versioning enabled — consider enabling for data protection`,
30
+ entity: getResourceName(manifest),
31
+ lexicon: "gcp",
32
+ });
33
+ }
34
+ }
35
+ }
36
+
37
+ return diagnostics;
38
+ },
39
+ };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * WGC108: SQLInstance missing backup configuration
3
+ */
4
+
5
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
6
+ import { parseGcpManifests, getSpec, getResourceName } from "./gcp-helpers";
7
+
8
+ export const wgc108: PostSynthCheck = {
9
+ id: "WGC108",
10
+ description: "SQLInstance without backup configuration enabled",
11
+
12
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [, output] of ctx.outputs) {
16
+ if (typeof output !== "string") continue;
17
+
18
+ for (const manifest of parseGcpManifests(output)) {
19
+ if (manifest.kind !== "SQLInstance") continue;
20
+
21
+ const spec = getSpec(manifest);
22
+ if (!spec) continue;
23
+
24
+ const settings = spec.settings as Record<string, unknown> | undefined;
25
+ const backupConfig = settings?.backupConfiguration as Record<string, unknown> | undefined;
26
+
27
+ if (!backupConfig || backupConfig.enabled !== true) {
28
+ diagnostics.push({
29
+ checkId: "WGC108",
30
+ severity: "warning",
31
+ message: `SQLInstance "${getResourceName(manifest)}" does not have backup configuration enabled — data loss risk`,
32
+ entity: getResourceName(manifest),
33
+ lexicon: "gcp",
34
+ });
35
+ }
36
+ }
37
+ }
38
+
39
+ return diagnostics;
40
+ },
41
+ };
@@ -0,0 +1,39 @@
1
+ /**
2
+ * WGC109: ComputeFirewall allowing all sources (0.0.0.0/0)
3
+ */
4
+
5
+ import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
6
+ import { parseGcpManifests, getSpec, getResourceName } from "./gcp-helpers";
7
+
8
+ export const wgc109: PostSynthCheck = {
9
+ id: "WGC109",
10
+ description: "ComputeFirewall with sourceRanges containing 0.0.0.0/0",
11
+
12
+ check(ctx: PostSynthContext): PostSynthDiagnostic[] {
13
+ const diagnostics: PostSynthDiagnostic[] = [];
14
+
15
+ for (const [, output] of ctx.outputs) {
16
+ if (typeof output !== "string") continue;
17
+
18
+ for (const manifest of parseGcpManifests(output)) {
19
+ if (manifest.kind !== "ComputeFirewall") continue;
20
+
21
+ const spec = getSpec(manifest);
22
+ if (!spec) continue;
23
+
24
+ const sourceRanges = spec.sourceRanges as string[] | undefined;
25
+ if (Array.isArray(sourceRanges) && sourceRanges.includes("0.0.0.0/0")) {
26
+ diagnostics.push({
27
+ checkId: "WGC109",
28
+ severity: "warning",
29
+ message: `ComputeFirewall "${getResourceName(manifest)}" allows traffic from 0.0.0.0/0 — this opens the firewall to the entire internet`,
30
+ entity: getResourceName(manifest),
31
+ lexicon: "gcp",
32
+ });
33
+ }
34
+ }
35
+ }
36
+
37
+ return diagnostics;
38
+ },
39
+ };