@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.
- package/dist/integrity.json +36 -0
- package/dist/manifest.json +12 -0
- package/dist/meta.json +10919 -0
- package/dist/rules/gcp-helpers.ts +117 -0
- package/dist/rules/hardcoded-project.ts +58 -0
- package/dist/rules/hardcoded-region.ts +56 -0
- package/dist/rules/public-iam.ts +43 -0
- package/dist/rules/wgc101.ts +56 -0
- package/dist/rules/wgc102.ts +35 -0
- package/dist/rules/wgc103.ts +45 -0
- package/dist/rules/wgc104.ts +42 -0
- package/dist/rules/wgc105.ts +46 -0
- package/dist/rules/wgc106.ts +36 -0
- package/dist/rules/wgc107.ts +39 -0
- package/dist/rules/wgc108.ts +41 -0
- package/dist/rules/wgc109.ts +39 -0
- package/dist/rules/wgc110.ts +38 -0
- package/dist/rules/wgc111.ts +54 -0
- package/dist/rules/wgc112.ts +56 -0
- package/dist/rules/wgc113.ts +42 -0
- package/dist/rules/wgc201.ts +36 -0
- package/dist/rules/wgc202.ts +39 -0
- package/dist/rules/wgc203.ts +44 -0
- package/dist/rules/wgc204.ts +39 -0
- package/dist/rules/wgc301.ts +34 -0
- package/dist/rules/wgc302.ts +34 -0
- package/dist/rules/wgc303.ts +37 -0
- package/dist/skills/chant-gcp-patterns.md +367 -0
- package/dist/skills/chant-gcp-security.md +276 -0
- package/dist/skills/chant-gcp.md +108 -0
- package/dist/types/index.d.ts +26529 -0
- package/package.json +35 -0
- package/src/actions/index.ts +52 -0
- package/src/codegen/docs-cli.ts +7 -0
- package/src/codegen/docs.ts +820 -0
- package/src/codegen/generate-cli.ts +24 -0
- package/src/codegen/generate.ts +252 -0
- package/src/codegen/naming.test.ts +49 -0
- package/src/codegen/naming.ts +132 -0
- package/src/codegen/package.ts +66 -0
- package/src/composites/cloud-function.ts +117 -0
- package/src/composites/cloud-run-service.ts +124 -0
- package/src/composites/cloud-sql-instance.ts +126 -0
- package/src/composites/composites.test.ts +432 -0
- package/src/composites/gcs-bucket.ts +111 -0
- package/src/composites/gke-cluster.ts +125 -0
- package/src/composites/index.ts +20 -0
- package/src/composites/managed-certificate.ts +79 -0
- package/src/composites/private-service.ts +95 -0
- package/src/composites/pubsub-pipeline.ts +102 -0
- package/src/composites/secure-project.ts +128 -0
- package/src/composites/vpc-network.ts +165 -0
- package/src/coverage.test.ts +27 -0
- package/src/coverage.ts +51 -0
- package/src/default-labels.test.ts +111 -0
- package/src/default-labels.ts +93 -0
- package/src/generated/index.d.ts +26529 -0
- package/src/generated/index.ts +1723 -0
- package/src/generated/lexicon-gcp.json +10919 -0
- package/src/generated/runtime.ts +4 -0
- package/src/import/generator.test.ts +125 -0
- package/src/import/generator.ts +82 -0
- package/src/import/parser.test.ts +167 -0
- package/src/import/parser.ts +80 -0
- package/src/import/roundtrip.test.ts +66 -0
- package/src/index.ts +54 -0
- package/src/lint/post-synth/gcp-helpers.ts +117 -0
- package/src/lint/post-synth/index.ts +20 -0
- package/src/lint/post-synth/post-synth.test.ts +693 -0
- package/src/lint/post-synth/wgc101.ts +56 -0
- package/src/lint/post-synth/wgc102.ts +35 -0
- package/src/lint/post-synth/wgc103.ts +45 -0
- package/src/lint/post-synth/wgc104.ts +42 -0
- package/src/lint/post-synth/wgc105.ts +46 -0
- package/src/lint/post-synth/wgc106.ts +36 -0
- package/src/lint/post-synth/wgc107.ts +39 -0
- package/src/lint/post-synth/wgc108.ts +41 -0
- package/src/lint/post-synth/wgc109.ts +39 -0
- package/src/lint/post-synth/wgc110.ts +38 -0
- package/src/lint/post-synth/wgc111.ts +54 -0
- package/src/lint/post-synth/wgc112.ts +56 -0
- package/src/lint/post-synth/wgc113.ts +42 -0
- package/src/lint/post-synth/wgc201.ts +36 -0
- package/src/lint/post-synth/wgc202.ts +39 -0
- package/src/lint/post-synth/wgc203.ts +44 -0
- package/src/lint/post-synth/wgc204.ts +39 -0
- package/src/lint/post-synth/wgc301.ts +34 -0
- package/src/lint/post-synth/wgc302.ts +34 -0
- package/src/lint/post-synth/wgc303.ts +37 -0
- package/src/lint/rules/hardcoded-project.ts +58 -0
- package/src/lint/rules/hardcoded-region.ts +56 -0
- package/src/lint/rules/index.ts +3 -0
- package/src/lint/rules/public-iam.ts +43 -0
- package/src/lint/rules/rules.test.ts +63 -0
- package/src/lsp/completions.test.ts +67 -0
- package/src/lsp/completions.ts +17 -0
- package/src/lsp/hover.test.ts +66 -0
- package/src/lsp/hover.ts +54 -0
- package/src/package-cli.ts +24 -0
- package/src/plugin.test.ts +250 -0
- package/src/plugin.ts +405 -0
- package/src/pseudo.test.ts +40 -0
- package/src/pseudo.ts +19 -0
- package/src/serializer.test.ts +250 -0
- package/src/serializer.ts +232 -0
- package/src/skills/chant-gcp-patterns.md +367 -0
- package/src/skills/chant-gcp-security.md +276 -0
- package/src/skills/chant-gcp.md +108 -0
- package/src/spec/fetch.test.ts +16 -0
- package/src/spec/fetch.ts +121 -0
- package/src/spec/parse.test.ts +163 -0
- package/src/spec/parse.ts +432 -0
- package/src/testdata/compute-instance.yaml +93 -0
- package/src/testdata/iam-policy-member.yaml +66 -0
- package/src/testdata/manifests/compute-instance.yaml +18 -0
- package/src/testdata/manifests/full-app.yaml +34 -0
- package/src/testdata/manifests/storage-bucket.yaml +12 -0
- package/src/testdata/storage-bucket.yaml +100 -0
- package/src/validate-cli.ts +13 -0
- package/src/validate.test.ts +38 -0
- package/src/validate.ts +30 -0
- 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
|
+
});
|