@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,20 @@
1
+ export { wgc101 } from "./wgc101";
2
+ export { wgc102 } from "./wgc102";
3
+ export { wgc103 } from "./wgc103";
4
+ export { wgc104 } from "./wgc104";
5
+ export { wgc105 } from "./wgc105";
6
+ export { wgc106 } from "./wgc106";
7
+ export { wgc107 } from "./wgc107";
8
+ export { wgc108 } from "./wgc108";
9
+ export { wgc109 } from "./wgc109";
10
+ export { wgc110 } from "./wgc110";
11
+ export { wgc201 } from "./wgc201";
12
+ export { wgc202 } from "./wgc202";
13
+ export { wgc203 } from "./wgc203";
14
+ export { wgc204 } from "./wgc204";
15
+ export { wgc301 } from "./wgc301";
16
+ export { wgc302 } from "./wgc302";
17
+ export { wgc303 } from "./wgc303";
18
+ export { wgc111 } from "./wgc111";
19
+ export { wgc112 } from "./wgc112";
20
+ export { wgc113 } from "./wgc113";
@@ -0,0 +1,693 @@
1
+ import { describe, test, expect } from "bun:test";
2
+ import { wgc101 } from "./wgc101";
3
+ import { wgc102 } from "./wgc102";
4
+ import { wgc103 } from "./wgc103";
5
+ import { wgc104 } from "./wgc104";
6
+ import { wgc105 } from "./wgc105";
7
+ import { wgc106 } from "./wgc106";
8
+ import { wgc107 } from "./wgc107";
9
+ import { wgc108 } from "./wgc108";
10
+ import { wgc109 } from "./wgc109";
11
+ import { wgc110 } from "./wgc110";
12
+ import { wgc201 } from "./wgc201";
13
+ import { wgc202 } from "./wgc202";
14
+ import { wgc203 } from "./wgc203";
15
+ import { wgc204 } from "./wgc204";
16
+ import { wgc301 } from "./wgc301";
17
+ import { wgc302 } from "./wgc302";
18
+ import { wgc303 } from "./wgc303";
19
+ import { wgc111 } from "./wgc111";
20
+ import { wgc112 } from "./wgc112";
21
+ import { wgc113 } from "./wgc113";
22
+
23
+ function makeCtx(yaml: string) {
24
+ return {
25
+ outputs: new Map([["gcp", yaml]]),
26
+ };
27
+ }
28
+
29
+ // ── WGC101: Missing encryption ─────────────────────────────────────
30
+
31
+ describe("WGC101: missing encryption", () => {
32
+ test("flags StorageBucket without encryption", () => {
33
+ const yaml = `apiVersion: storage.cnrm.cloud.google.com/v1beta1
34
+ kind: StorageBucket
35
+ metadata:
36
+ name: my-bucket
37
+ spec:
38
+ location: US
39
+ `;
40
+ const diags = wgc101.check(makeCtx(yaml));
41
+ expect(diags.length).toBeGreaterThanOrEqual(1);
42
+ expect(diags[0].checkId).toBe("WGC101");
43
+ });
44
+
45
+ test("no diagnostic when encryption present", () => {
46
+ const yaml = `apiVersion: storage.cnrm.cloud.google.com/v1beta1
47
+ kind: StorageBucket
48
+ metadata:
49
+ name: my-bucket
50
+ spec:
51
+ location: US
52
+ encryption:
53
+ defaultKmsKeyName: projects/p/locations/l/keyRings/kr/cryptoKeys/k
54
+ `;
55
+ const diags = wgc101.check(makeCtx(yaml));
56
+ const bucketDiags = diags.filter(d => d.entity === "my-bucket");
57
+ expect(bucketDiags).toHaveLength(0);
58
+ });
59
+ });
60
+
61
+ // ── WGC102: Public IAM in output ───────────────────────────────────
62
+
63
+ describe("WGC102: public IAM in output", () => {
64
+ test("flags allUsers in output", () => {
65
+ const yaml = `apiVersion: iam.cnrm.cloud.google.com/v1beta1
66
+ kind: IAMPolicyMember
67
+ metadata:
68
+ name: public-access
69
+ spec:
70
+ member: allUsers
71
+ role: roles/run.invoker
72
+ `;
73
+ const diags = wgc102.check(makeCtx(yaml));
74
+ expect(diags.length).toBeGreaterThanOrEqual(1);
75
+ expect(diags[0].checkId).toBe("WGC102");
76
+ });
77
+
78
+ test("no diagnostic when no public members", () => {
79
+ const yaml = `apiVersion: iam.cnrm.cloud.google.com/v1beta1
80
+ kind: IAMPolicyMember
81
+ metadata:
82
+ name: private-access
83
+ spec:
84
+ member: serviceAccount:sa@project.iam.gserviceaccount.com
85
+ role: roles/viewer
86
+ `;
87
+ const diags = wgc102.check(makeCtx(yaml));
88
+ expect(diags).toHaveLength(0);
89
+ });
90
+ });
91
+
92
+ // ── WGC103: Missing project annotation ─────────────────────────────
93
+
94
+ describe("WGC103: missing project annotation", () => {
95
+ test("flags resource without project annotation", () => {
96
+ const yaml = `apiVersion: storage.cnrm.cloud.google.com/v1beta1
97
+ kind: StorageBucket
98
+ metadata:
99
+ name: my-bucket
100
+ spec:
101
+ location: US
102
+ `;
103
+ const diags = wgc103.check(makeCtx(yaml));
104
+ expect(diags.length).toBeGreaterThanOrEqual(1);
105
+ expect(diags[0].checkId).toBe("WGC103");
106
+ });
107
+
108
+ test("no diagnostic when project annotation present", () => {
109
+ const yaml = `apiVersion: storage.cnrm.cloud.google.com/v1beta1
110
+ kind: StorageBucket
111
+ metadata:
112
+ name: my-bucket
113
+ annotations:
114
+ cnrm.cloud.google.com/project-id: my-project
115
+ spec:
116
+ location: US
117
+ `;
118
+ const diags = wgc103.check(makeCtx(yaml));
119
+ expect(diags).toHaveLength(0);
120
+ });
121
+ });
122
+
123
+ // ── WGC104: Missing uniform bucket access ──────────────────────────
124
+
125
+ describe("WGC104: missing uniform bucket access", () => {
126
+ test("flags StorageBucket without uniformBucketLevelAccess", () => {
127
+ const yaml = `apiVersion: storage.cnrm.cloud.google.com/v1beta1
128
+ kind: StorageBucket
129
+ metadata:
130
+ name: my-bucket
131
+ spec:
132
+ location: US
133
+ `;
134
+ const diags = wgc104.check(makeCtx(yaml));
135
+ expect(diags.length).toBeGreaterThanOrEqual(1);
136
+ expect(diags[0].checkId).toBe("WGC104");
137
+ });
138
+
139
+ test("no diagnostic when uniformBucketLevelAccess is true", () => {
140
+ const yaml = `apiVersion: storage.cnrm.cloud.google.com/v1beta1
141
+ kind: StorageBucket
142
+ metadata:
143
+ name: my-bucket
144
+ spec:
145
+ location: US
146
+ uniformBucketLevelAccess: true
147
+ `;
148
+ const diags = wgc104.check(makeCtx(yaml));
149
+ expect(diags).toHaveLength(0);
150
+ });
151
+ });
152
+
153
+ // ── WGC105: Public Cloud SQL ───────────────────────────────────────
154
+
155
+ describe("WGC105: public Cloud SQL", () => {
156
+ test("flags SQLInstance with 0.0.0.0/0 in authorizedNetworks", () => {
157
+ const yaml = `apiVersion: sql.cnrm.cloud.google.com/v1beta1
158
+ kind: SQLInstance
159
+ metadata:
160
+ name: my-db
161
+ spec:
162
+ databaseVersion: POSTGRES_15
163
+ settings:
164
+ ipConfiguration:
165
+ authorizedNetworks:
166
+ - value: "0.0.0.0/0"
167
+ name: public
168
+ `;
169
+ const diags = wgc105.check(makeCtx(yaml));
170
+ expect(diags.length).toBeGreaterThanOrEqual(1);
171
+ expect(diags[0].checkId).toBe("WGC105");
172
+ });
173
+
174
+ test("no diagnostic with private networks only", () => {
175
+ const yaml = `apiVersion: sql.cnrm.cloud.google.com/v1beta1
176
+ kind: SQLInstance
177
+ metadata:
178
+ name: my-db
179
+ spec:
180
+ databaseVersion: POSTGRES_15
181
+ settings:
182
+ ipConfiguration:
183
+ authorizedNetworks:
184
+ - value: "10.0.0.0/8"
185
+ name: internal
186
+ `;
187
+ const diags = wgc105.check(makeCtx(yaml));
188
+ expect(diags).toHaveLength(0);
189
+ });
190
+ });
191
+
192
+ // ── WGC106: Missing deletion policy annotation ─────────────────────
193
+
194
+ describe("WGC106: missing deletion policy", () => {
195
+ test("flags resource without deletion policy annotation", () => {
196
+ const yaml = `apiVersion: storage.cnrm.cloud.google.com/v1beta1
197
+ kind: StorageBucket
198
+ metadata:
199
+ name: my-bucket
200
+ spec:
201
+ location: US
202
+ `;
203
+ const diags = wgc106.check(makeCtx(yaml));
204
+ expect(diags.length).toBeGreaterThanOrEqual(1);
205
+ expect(diags[0].checkId).toBe("WGC106");
206
+ });
207
+
208
+ test("no diagnostic when deletion policy present", () => {
209
+ const yaml = `apiVersion: storage.cnrm.cloud.google.com/v1beta1
210
+ kind: StorageBucket
211
+ metadata:
212
+ name: my-bucket
213
+ annotations:
214
+ cnrm.cloud.google.com/deletion-policy: abandon
215
+ spec:
216
+ location: US
217
+ `;
218
+ const diags = wgc106.check(makeCtx(yaml));
219
+ expect(diags).toHaveLength(0);
220
+ });
221
+ });
222
+
223
+ // ── WGC107: StorageBucket missing versioning ───────────────────────
224
+
225
+ describe("WGC107: missing versioning", () => {
226
+ test("flags StorageBucket without versioning", () => {
227
+ const yaml = `apiVersion: storage.cnrm.cloud.google.com/v1beta1
228
+ kind: StorageBucket
229
+ metadata:
230
+ name: my-bucket
231
+ spec:
232
+ location: US
233
+ `;
234
+ const diags = wgc107.check(makeCtx(yaml));
235
+ expect(diags.length).toBeGreaterThanOrEqual(1);
236
+ expect(diags[0].checkId).toBe("WGC107");
237
+ });
238
+
239
+ test("no diagnostic when versioning enabled", () => {
240
+ const yaml = `apiVersion: storage.cnrm.cloud.google.com/v1beta1
241
+ kind: StorageBucket
242
+ metadata:
243
+ name: my-bucket
244
+ spec:
245
+ location: US
246
+ versioning:
247
+ enabled: true
248
+ `;
249
+ const diags = wgc107.check(makeCtx(yaml));
250
+ expect(diags).toHaveLength(0);
251
+ });
252
+ });
253
+
254
+ // ── WGC108: SQLInstance missing backup ──────────────────────────────
255
+
256
+ describe("WGC108: missing backup configuration", () => {
257
+ test("flags SQLInstance without backup", () => {
258
+ const yaml = `apiVersion: sql.cnrm.cloud.google.com/v1beta1
259
+ kind: SQLInstance
260
+ metadata:
261
+ name: my-db
262
+ spec:
263
+ databaseVersion: POSTGRES_15
264
+ settings:
265
+ tier: db-f1-micro
266
+ `;
267
+ const diags = wgc108.check(makeCtx(yaml));
268
+ expect(diags.length).toBeGreaterThanOrEqual(1);
269
+ expect(diags[0].checkId).toBe("WGC108");
270
+ });
271
+
272
+ test("no diagnostic when backup enabled", () => {
273
+ const yaml = `apiVersion: sql.cnrm.cloud.google.com/v1beta1
274
+ kind: SQLInstance
275
+ metadata:
276
+ name: my-db
277
+ spec:
278
+ databaseVersion: POSTGRES_15
279
+ settings:
280
+ tier: db-f1-micro
281
+ backupConfiguration:
282
+ enabled: true
283
+ `;
284
+ const diags = wgc108.check(makeCtx(yaml));
285
+ expect(diags).toHaveLength(0);
286
+ });
287
+ });
288
+
289
+ // ── WGC109: ComputeFirewall allowing all sources ───────────────────
290
+
291
+ describe("WGC109: open firewall", () => {
292
+ test("flags ComputeFirewall with 0.0.0.0/0", () => {
293
+ const yaml = `apiVersion: compute.cnrm.cloud.google.com/v1beta1
294
+ kind: ComputeFirewall
295
+ metadata:
296
+ name: allow-all
297
+ spec:
298
+ sourceRanges:
299
+ - "0.0.0.0/0"
300
+ allowed:
301
+ - protocol: tcp
302
+ ports:
303
+ - "80"
304
+ `;
305
+ const diags = wgc109.check(makeCtx(yaml));
306
+ expect(diags.length).toBeGreaterThanOrEqual(1);
307
+ expect(diags[0].checkId).toBe("WGC109");
308
+ });
309
+
310
+ test("no diagnostic with specific source range", () => {
311
+ const yaml = `apiVersion: compute.cnrm.cloud.google.com/v1beta1
312
+ kind: ComputeFirewall
313
+ metadata:
314
+ name: allow-internal
315
+ spec:
316
+ sourceRanges:
317
+ - "10.0.0.0/8"
318
+ allowed:
319
+ - protocol: tcp
320
+ ports:
321
+ - "80"
322
+ `;
323
+ const diags = wgc109.check(makeCtx(yaml));
324
+ expect(diags).toHaveLength(0);
325
+ });
326
+ });
327
+
328
+ // ── WGC110: KMS CryptoKey missing rotation ─────────────────────────
329
+
330
+ describe("WGC110: missing key rotation", () => {
331
+ test("flags KMSCryptoKey without rotation period", () => {
332
+ const yaml = `apiVersion: kms.cnrm.cloud.google.com/v1beta1
333
+ kind: KMSCryptoKey
334
+ metadata:
335
+ name: my-key
336
+ spec:
337
+ purpose: ENCRYPT_DECRYPT
338
+ `;
339
+ const diags = wgc110.check(makeCtx(yaml));
340
+ expect(diags.length).toBeGreaterThanOrEqual(1);
341
+ expect(diags[0].checkId).toBe("WGC110");
342
+ });
343
+
344
+ test("no diagnostic when rotation period set", () => {
345
+ const yaml = `apiVersion: kms.cnrm.cloud.google.com/v1beta1
346
+ kind: KMSCryptoKey
347
+ metadata:
348
+ name: my-key
349
+ spec:
350
+ purpose: ENCRYPT_DECRYPT
351
+ rotationPeriod: 7776000s
352
+ `;
353
+ const diags = wgc110.check(makeCtx(yaml));
354
+ expect(diags).toHaveLength(0);
355
+ });
356
+ });
357
+
358
+ // ── WGC201: Missing managed-by label ───────────────────────────────
359
+
360
+ describe("WGC201: missing managed-by label", () => {
361
+ test("flags resource without managed-by label", () => {
362
+ const yaml = `apiVersion: storage.cnrm.cloud.google.com/v1beta1
363
+ kind: StorageBucket
364
+ metadata:
365
+ name: my-bucket
366
+ spec:
367
+ location: US
368
+ `;
369
+ const diags = wgc201.check(makeCtx(yaml));
370
+ expect(diags.length).toBeGreaterThanOrEqual(1);
371
+ expect(diags[0].checkId).toBe("WGC201");
372
+ });
373
+
374
+ test("no diagnostic when managed-by label present", () => {
375
+ const yaml = `apiVersion: storage.cnrm.cloud.google.com/v1beta1
376
+ kind: StorageBucket
377
+ metadata:
378
+ name: my-bucket
379
+ labels:
380
+ app.kubernetes.io/managed-by: chant
381
+ spec:
382
+ location: US
383
+ `;
384
+ const diags = wgc201.check(makeCtx(yaml));
385
+ expect(diags).toHaveLength(0);
386
+ });
387
+ });
388
+
389
+ // ── WGC202: GKE cluster without workload identity ──────────────────
390
+
391
+ describe("WGC202: missing workload identity", () => {
392
+ test("flags ContainerCluster without workload identity", () => {
393
+ const yaml = `apiVersion: container.cnrm.cloud.google.com/v1beta1
394
+ kind: ContainerCluster
395
+ metadata:
396
+ name: my-cluster
397
+ spec:
398
+ location: us-central1
399
+ `;
400
+ const diags = wgc202.check(makeCtx(yaml));
401
+ expect(diags.length).toBeGreaterThanOrEqual(1);
402
+ expect(diags[0].checkId).toBe("WGC202");
403
+ });
404
+
405
+ test("no diagnostic when workload identity configured", () => {
406
+ const yaml = `apiVersion: container.cnrm.cloud.google.com/v1beta1
407
+ kind: ContainerCluster
408
+ metadata:
409
+ name: my-cluster
410
+ spec:
411
+ location: us-central1
412
+ workloadIdentityConfig:
413
+ workloadPool: my-project.svc.id.goog
414
+ `;
415
+ const diags = wgc202.check(makeCtx(yaml));
416
+ expect(diags).toHaveLength(0);
417
+ });
418
+ });
419
+
420
+ // ── WGC203: Overly broad OAuth scope ───────────────────────────────
421
+
422
+ describe("WGC203: cloud-platform OAuth scope", () => {
423
+ test("flags ContainerNodePool with cloud-platform scope", () => {
424
+ const yaml = `apiVersion: container.cnrm.cloud.google.com/v1beta1
425
+ kind: ContainerNodePool
426
+ metadata:
427
+ name: my-pool
428
+ spec:
429
+ clusterRef:
430
+ name: my-cluster
431
+ nodeConfig:
432
+ oauthScopes:
433
+ - "https://www.googleapis.com/auth/cloud-platform"
434
+ `;
435
+ const diags = wgc203.check(makeCtx(yaml));
436
+ expect(diags.length).toBeGreaterThanOrEqual(1);
437
+ expect(diags[0].checkId).toBe("WGC203");
438
+ });
439
+
440
+ test("no diagnostic with specific scopes", () => {
441
+ const yaml = `apiVersion: container.cnrm.cloud.google.com/v1beta1
442
+ kind: ContainerNodePool
443
+ metadata:
444
+ name: my-pool
445
+ spec:
446
+ clusterRef:
447
+ name: my-cluster
448
+ nodeConfig:
449
+ oauthScopes:
450
+ - "https://www.googleapis.com/auth/logging.write"
451
+ - "https://www.googleapis.com/auth/monitoring"
452
+ `;
453
+ const diags = wgc203.check(makeCtx(yaml));
454
+ expect(diags).toHaveLength(0);
455
+ });
456
+ });
457
+
458
+ // ── WGC204: ComputeInstance without shielded VM ────────────────────
459
+
460
+ describe("WGC204: missing shielded VM config", () => {
461
+ test("flags ComputeInstance without shielded VM", () => {
462
+ const yaml = `apiVersion: compute.cnrm.cloud.google.com/v1beta1
463
+ kind: ComputeInstance
464
+ metadata:
465
+ name: my-vm
466
+ spec:
467
+ machineType: e2-medium
468
+ zone: us-central1-a
469
+ `;
470
+ const diags = wgc204.check(makeCtx(yaml));
471
+ expect(diags.length).toBeGreaterThanOrEqual(1);
472
+ expect(diags[0].checkId).toBe("WGC204");
473
+ });
474
+
475
+ test("no diagnostic when shielded VM configured", () => {
476
+ const yaml = `apiVersion: compute.cnrm.cloud.google.com/v1beta1
477
+ kind: ComputeInstance
478
+ metadata:
479
+ name: my-vm
480
+ spec:
481
+ machineType: e2-medium
482
+ zone: us-central1-a
483
+ shieldedInstanceConfig:
484
+ enableSecureBoot: true
485
+ enableVtpm: true
486
+ enableIntegrityMonitoring: true
487
+ `;
488
+ const diags = wgc204.check(makeCtx(yaml));
489
+ expect(diags).toHaveLength(0);
490
+ });
491
+ });
492
+
493
+ // ── WGC301: No audit logging config ────────────────────────────────
494
+
495
+ describe("WGC301: no audit logging", () => {
496
+ test("flags output without IAMAuditConfig", () => {
497
+ const yaml = `apiVersion: storage.cnrm.cloud.google.com/v1beta1
498
+ kind: StorageBucket
499
+ metadata:
500
+ name: my-bucket
501
+ spec:
502
+ location: US
503
+ `;
504
+ const diags = wgc301.check(makeCtx(yaml));
505
+ expect(diags.length).toBeGreaterThanOrEqual(1);
506
+ expect(diags[0].checkId).toBe("WGC301");
507
+ });
508
+
509
+ test("no diagnostic when IAMAuditConfig present", () => {
510
+ const yaml = `apiVersion: iam.cnrm.cloud.google.com/v1beta1
511
+ kind: IAMAuditConfig
512
+ metadata:
513
+ name: audit-config
514
+ spec:
515
+ service: allServices
516
+ auditLogConfigs:
517
+ - logType: ADMIN_READ
518
+ - logType: DATA_READ
519
+ `;
520
+ const diags = wgc301.check(makeCtx(yaml));
521
+ expect(diags).toHaveLength(0);
522
+ });
523
+ });
524
+
525
+ // ── WGC302: Service API not enabled ────────────────────────────────
526
+
527
+ describe("WGC302: service API not enabled", () => {
528
+ test("flags output without Service resource", () => {
529
+ const yaml = `apiVersion: storage.cnrm.cloud.google.com/v1beta1
530
+ kind: StorageBucket
531
+ metadata:
532
+ name: my-bucket
533
+ spec:
534
+ location: US
535
+ `;
536
+ const diags = wgc302.check(makeCtx(yaml));
537
+ expect(diags.length).toBeGreaterThanOrEqual(1);
538
+ expect(diags[0].checkId).toBe("WGC302");
539
+ });
540
+
541
+ test("no diagnostic when Service resource present", () => {
542
+ const yaml = `apiVersion: serviceusage.cnrm.cloud.google.com/v1beta1
543
+ kind: Service
544
+ metadata:
545
+ name: compute-api
546
+ spec:
547
+ resourceID: compute.googleapis.com
548
+ `;
549
+ const diags = wgc302.check(makeCtx(yaml));
550
+ expect(diags).toHaveLength(0);
551
+ });
552
+ });
553
+
554
+ // ── WGC303: Missing VPC Service Controls ───────────────────────────
555
+
556
+ describe("WGC303: missing VPC Service Controls", () => {
557
+ test("flags output without service perimeter", () => {
558
+ const yaml = `apiVersion: storage.cnrm.cloud.google.com/v1beta1
559
+ kind: StorageBucket
560
+ metadata:
561
+ name: my-bucket
562
+ spec:
563
+ location: US
564
+ `;
565
+ const diags = wgc303.check(makeCtx(yaml));
566
+ expect(diags.length).toBeGreaterThanOrEqual(1);
567
+ expect(diags[0].checkId).toBe("WGC303");
568
+ });
569
+
570
+ test("no diagnostic when service perimeter present", () => {
571
+ const yaml = `apiVersion: accesscontextmanager.cnrm.cloud.google.com/v1beta1
572
+ kind: AccessContextManagerServicePerimeter
573
+ metadata:
574
+ name: my-perimeter
575
+ spec:
576
+ title: my-perimeter
577
+ perimeterType: PERIMETER_TYPE_REGULAR
578
+ `;
579
+ const diags = wgc303.check(makeCtx(yaml));
580
+ expect(diags).toHaveLength(0);
581
+ });
582
+ });
583
+
584
+ // ── WGC111: Dangling resource reference ────────────────────────────
585
+
586
+ describe("WGC111: dangling resource reference", () => {
587
+ test("flags resourceRef pointing to nonexistent name", () => {
588
+ const yaml = `apiVersion: container.cnrm.cloud.google.com/v1beta1
589
+ kind: ContainerNodePool
590
+ metadata:
591
+ name: my-pool
592
+ spec:
593
+ clusterRef:
594
+ name: nonexistent-cluster
595
+ `;
596
+ const diags = wgc111.check(makeCtx(yaml));
597
+ expect(diags.length).toBeGreaterThanOrEqual(1);
598
+ expect(diags[0].checkId).toBe("WGC111");
599
+ });
600
+
601
+ test("no diagnostic when referenced resource exists", () => {
602
+ const yaml = `apiVersion: container.cnrm.cloud.google.com/v1beta1
603
+ kind: ContainerCluster
604
+ metadata:
605
+ name: my-cluster
606
+ spec:
607
+ location: us-central1
608
+ ---
609
+ apiVersion: container.cnrm.cloud.google.com/v1beta1
610
+ kind: ContainerNodePool
611
+ metadata:
612
+ name: my-pool
613
+ spec:
614
+ clusterRef:
615
+ name: my-cluster
616
+ `;
617
+ const diags = wgc111.check(makeCtx(yaml));
618
+ const poolDiags = diags.filter(d => d.entity === "my-pool");
619
+ expect(poolDiags).toHaveLength(0);
620
+ });
621
+ });
622
+
623
+ // ── WGC112: Missing or invalid apiVersion ──────────────────────────
624
+
625
+ describe("WGC112: missing or invalid apiVersion", () => {
626
+ test("flags resource with missing apiVersion", () => {
627
+ const yaml = `kind: StorageBucket
628
+ metadata:
629
+ name: my-bucket
630
+ spec:
631
+ location: US
632
+ `;
633
+ const diags = wgc112.check(makeCtx(yaml));
634
+ expect(diags.length).toBeGreaterThanOrEqual(1);
635
+ expect(diags[0].checkId).toBe("WGC112");
636
+ expect(diags[0].severity).toBe("error");
637
+ });
638
+
639
+ test("flags resource with malformed apiVersion", () => {
640
+ const yaml = `apiVersion: not-a-valid-version
641
+ kind: StorageBucket
642
+ metadata:
643
+ name: my-bucket
644
+ spec:
645
+ location: US
646
+ `;
647
+ const diags = wgc112.check(makeCtx(yaml));
648
+ expect(diags.length).toBeGreaterThanOrEqual(1);
649
+ expect(diags[0].checkId).toBe("WGC112");
650
+ });
651
+
652
+ test("no diagnostic with valid apiVersion", () => {
653
+ const yaml = `apiVersion: storage.cnrm.cloud.google.com/v1beta1
654
+ kind: StorageBucket
655
+ metadata:
656
+ name: my-bucket
657
+ spec:
658
+ location: US
659
+ `;
660
+ const diags = wgc112.check(makeCtx(yaml));
661
+ expect(diags).toHaveLength(0);
662
+ });
663
+ });
664
+
665
+ // ── WGC113: Alpha API version warning ──────────────────────────────
666
+
667
+ describe("WGC113: alpha API version", () => {
668
+ test("flags resource with v1alpha1 apiVersion", () => {
669
+ const yaml = `apiVersion: compute.cnrm.cloud.google.com/v1alpha1
670
+ kind: ComputeInstance
671
+ metadata:
672
+ name: my-vm
673
+ spec:
674
+ machineType: e2-medium
675
+ `;
676
+ const diags = wgc113.check(makeCtx(yaml));
677
+ expect(diags.length).toBeGreaterThanOrEqual(1);
678
+ expect(diags[0].checkId).toBe("WGC113");
679
+ expect(diags[0].severity).toBe("warning");
680
+ });
681
+
682
+ test("no diagnostic with v1beta1 apiVersion", () => {
683
+ const yaml = `apiVersion: compute.cnrm.cloud.google.com/v1beta1
684
+ kind: ComputeInstance
685
+ metadata:
686
+ name: my-vm
687
+ spec:
688
+ machineType: e2-medium
689
+ `;
690
+ const diags = wgc113.check(makeCtx(yaml));
691
+ expect(diags).toHaveLength(0);
692
+ });
693
+ });