@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,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
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WGC110: KMS CryptoKey missing rotation period
|
|
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 wgc110: PostSynthCheck = {
|
|
9
|
+
id: "WGC110",
|
|
10
|
+
description: "KMS CryptoKey without rotation period configured",
|
|
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 !== "KMSCryptoKey") continue;
|
|
20
|
+
|
|
21
|
+
const spec = getSpec(manifest);
|
|
22
|
+
if (!spec) continue;
|
|
23
|
+
|
|
24
|
+
if (!spec.rotationPeriod) {
|
|
25
|
+
diagnostics.push({
|
|
26
|
+
checkId: "WGC110",
|
|
27
|
+
severity: "warning",
|
|
28
|
+
message: `KMSCryptoKey "${getResourceName(manifest)}" has no rotation period configured — consider setting rotationPeriod for key hygiene`,
|
|
29
|
+
entity: getResourceName(manifest),
|
|
30
|
+
lexicon: "gcp",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return diagnostics;
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WGC111: Dangling resource reference
|
|
3
|
+
*
|
|
4
|
+
* Checks that every `resourceRef.name` in a Config Connector resource's spec
|
|
5
|
+
* references a `metadata.name` that exists in the output.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
9
|
+
import { parseGcpManifests, isConfigConnectorResource, getResourceName, findResourceRefs } from "./gcp-helpers";
|
|
10
|
+
|
|
11
|
+
export const wgc111: PostSynthCheck = {
|
|
12
|
+
id: "WGC111",
|
|
13
|
+
description: "Resource reference points to a name not defined in the output",
|
|
14
|
+
|
|
15
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
16
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
17
|
+
|
|
18
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
19
|
+
if (typeof output !== "string") continue;
|
|
20
|
+
|
|
21
|
+
const manifests = parseGcpManifests(output);
|
|
22
|
+
|
|
23
|
+
// Collect all defined resource names
|
|
24
|
+
const definedNames = new Set<string>();
|
|
25
|
+
for (const manifest of manifests) {
|
|
26
|
+
if (isConfigConnectorResource(manifest)) {
|
|
27
|
+
const name = manifest.metadata?.name;
|
|
28
|
+
if (name) definedNames.add(name);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check each resource's refs
|
|
33
|
+
for (const manifest of manifests) {
|
|
34
|
+
if (!isConfigConnectorResource(manifest)) continue;
|
|
35
|
+
const resourceName = getResourceName(manifest);
|
|
36
|
+
const refs = findResourceRefs(manifest.spec);
|
|
37
|
+
|
|
38
|
+
for (const refName of refs) {
|
|
39
|
+
if (!definedNames.has(refName)) {
|
|
40
|
+
diagnostics.push({
|
|
41
|
+
checkId: "WGC111",
|
|
42
|
+
severity: "warning",
|
|
43
|
+
message: `Resource "${resourceName}" references "${refName}" via resourceRef, but no resource with that metadata.name exists in the output`,
|
|
44
|
+
entity: resourceName,
|
|
45
|
+
lexicon: "gcp",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return diagnostics;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WGC112: Missing or invalid apiVersion
|
|
3
|
+
*
|
|
4
|
+
* Every Config Connector resource must have an apiVersion matching
|
|
5
|
+
* `<service>.cnrm.cloud.google.com/v<version>`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
9
|
+
import { parseGcpManifests, getResourceName } from "./gcp-helpers";
|
|
10
|
+
|
|
11
|
+
const VALID_API_VERSION = /^[a-z]+\.cnrm\.cloud\.google\.com\/v\d+(?:alpha\d+|beta\d+)?$/;
|
|
12
|
+
|
|
13
|
+
export const wgc112: PostSynthCheck = {
|
|
14
|
+
id: "WGC112",
|
|
15
|
+
description: "Config Connector resource has missing or invalid apiVersion",
|
|
16
|
+
|
|
17
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
18
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
19
|
+
|
|
20
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
21
|
+
if (typeof output !== "string") continue;
|
|
22
|
+
|
|
23
|
+
const manifests = parseGcpManifests(output);
|
|
24
|
+
|
|
25
|
+
for (const manifest of manifests) {
|
|
26
|
+
// Only check resources that look like Config Connector (have a kind)
|
|
27
|
+
if (!manifest.kind) continue;
|
|
28
|
+
|
|
29
|
+
const resourceName = getResourceName(manifest);
|
|
30
|
+
|
|
31
|
+
if (!manifest.apiVersion) {
|
|
32
|
+
diagnostics.push({
|
|
33
|
+
checkId: "WGC112",
|
|
34
|
+
severity: "error",
|
|
35
|
+
message: `Resource "${resourceName}" (kind: ${manifest.kind}) is missing apiVersion`,
|
|
36
|
+
entity: resourceName,
|
|
37
|
+
lexicon: "gcp",
|
|
38
|
+
});
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!VALID_API_VERSION.test(manifest.apiVersion)) {
|
|
43
|
+
diagnostics.push({
|
|
44
|
+
checkId: "WGC112",
|
|
45
|
+
severity: "error",
|
|
46
|
+
message: `Resource "${resourceName}" has invalid apiVersion "${manifest.apiVersion}" — expected format: <service>.cnrm.cloud.google.com/v<version>`,
|
|
47
|
+
entity: resourceName,
|
|
48
|
+
lexicon: "gcp",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return diagnostics;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WGC113: Alpha API version warning
|
|
3
|
+
*
|
|
4
|
+
* Warns when a Config Connector resource uses an alpha API version
|
|
5
|
+
* (e.g. v1alpha1). Alpha APIs are unstable — prefer v1beta1 or v1.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
9
|
+
import { parseGcpManifests, isConfigConnectorResource, getResourceName } from "./gcp-helpers";
|
|
10
|
+
|
|
11
|
+
export const wgc113: PostSynthCheck = {
|
|
12
|
+
id: "WGC113",
|
|
13
|
+
description: "Config Connector resource uses an alpha API version",
|
|
14
|
+
|
|
15
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
16
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
17
|
+
|
|
18
|
+
for (const [_lexicon, output] of ctx.outputs) {
|
|
19
|
+
if (typeof output !== "string") continue;
|
|
20
|
+
|
|
21
|
+
const manifests = parseGcpManifests(output);
|
|
22
|
+
|
|
23
|
+
for (const manifest of manifests) {
|
|
24
|
+
if (!isConfigConnectorResource(manifest)) continue;
|
|
25
|
+
|
|
26
|
+
const apiVersion = manifest.apiVersion!;
|
|
27
|
+
if (/alpha\d+/.test(apiVersion)) {
|
|
28
|
+
const resourceName = getResourceName(manifest);
|
|
29
|
+
diagnostics.push({
|
|
30
|
+
checkId: "WGC113",
|
|
31
|
+
severity: "warning",
|
|
32
|
+
message: `Resource "${resourceName}" uses alpha API version "${apiVersion}" — alpha APIs are unstable, prefer v1beta1 or v1`,
|
|
33
|
+
entity: resourceName,
|
|
34
|
+
lexicon: "gcp",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return diagnostics;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WGC201: Missing managed-by label
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
6
|
+
import { parseGcpManifests, isConfigConnectorResource, getResourceName } from "./gcp-helpers";
|
|
7
|
+
|
|
8
|
+
export const wgc201: PostSynthCheck = {
|
|
9
|
+
id: "WGC201",
|
|
10
|
+
description: "Config Connector resource without app.kubernetes.io/managed-by label",
|
|
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 labels = manifest.metadata?.labels;
|
|
22
|
+
if (!labels?.["app.kubernetes.io/managed-by"]) {
|
|
23
|
+
diagnostics.push({
|
|
24
|
+
checkId: "WGC201",
|
|
25
|
+
severity: "info",
|
|
26
|
+
message: `Resource "${getResourceName(manifest)}" has no app.kubernetes.io/managed-by label — consider using defaultLabels() for consistent labeling`,
|
|
27
|
+
entity: getResourceName(manifest),
|
|
28
|
+
lexicon: "gcp",
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return diagnostics;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WGC202: GKE cluster without workload identity
|
|
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 wgc202: PostSynthCheck = {
|
|
9
|
+
id: "WGC202",
|
|
10
|
+
description: "ContainerCluster without workload identity configuration",
|
|
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 !== "ContainerCluster") continue;
|
|
20
|
+
|
|
21
|
+
const spec = getSpec(manifest);
|
|
22
|
+
if (!spec) continue;
|
|
23
|
+
|
|
24
|
+
const workloadIdentity = spec.workloadIdentityConfig as Record<string, unknown> | undefined;
|
|
25
|
+
if (!workloadIdentity?.workloadPool) {
|
|
26
|
+
diagnostics.push({
|
|
27
|
+
checkId: "WGC202",
|
|
28
|
+
severity: "warning",
|
|
29
|
+
message: `ContainerCluster "${getResourceName(manifest)}" does not have workload identity configured — recommended for secure pod-to-GCP authentication`,
|
|
30
|
+
entity: getResourceName(manifest),
|
|
31
|
+
lexicon: "gcp",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return diagnostics;
|
|
38
|
+
},
|
|
39
|
+
};
|