@intentius/chant-lexicon-cpln 0.53.0
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/README.md +85 -0
- package/dist/api.d.ts +77 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/codegen/docs-cli.d.ts +3 -0
- package/dist/codegen/docs-cli.d.ts.map +1 -0
- package/dist/codegen/docs.d.ts +17 -0
- package/dist/codegen/docs.d.ts.map +1 -0
- package/dist/codegen/generate-cli.d.ts +6 -0
- package/dist/codegen/generate-cli.d.ts.map +1 -0
- package/dist/codegen/generate-lexicon.d.ts +36 -0
- package/dist/codegen/generate-lexicon.d.ts.map +1 -0
- package/dist/codegen/generate-typescript.d.ts +11 -0
- package/dist/codegen/generate-typescript.d.ts.map +1 -0
- package/dist/codegen/generate.d.ts +15 -0
- package/dist/codegen/generate.d.ts.map +1 -0
- package/dist/codegen/naming.d.ts +30 -0
- package/dist/codegen/naming.d.ts.map +1 -0
- package/dist/codegen/package.d.ts +17 -0
- package/dist/codegen/package.d.ts.map +1 -0
- package/dist/composites/cron-job.d.ts +60 -0
- package/dist/composites/cron-job.d.ts.map +1 -0
- package/dist/composites/gvc-environment.d.ts +47 -0
- package/dist/composites/gvc-environment.d.ts.map +1 -0
- package/dist/composites/index.d.ts +13 -0
- package/dist/composites/index.d.ts.map +1 -0
- package/dist/composites/public-domain.d.ts +68 -0
- package/dist/composites/public-domain.d.ts.map +1 -0
- package/dist/composites/secret-access.d.ts +78 -0
- package/dist/composites/secret-access.d.ts.map +1 -0
- package/dist/composites/serverless-service.d.ts +78 -0
- package/dist/composites/serverless-service.d.ts.map +1 -0
- package/dist/composites/stateful-service.d.ts +79 -0
- package/dist/composites/stateful-service.d.ts.map +1 -0
- package/dist/coverage.d.ts +59 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/describe-resources.d.ts +71 -0
- package/dist/describe-resources.d.ts.map +1 -0
- package/dist/detect.d.ts +14 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/entity-props.d.ts +34 -0
- package/dist/entity-props.d.ts.map +1 -0
- package/dist/generated/index.d.ts +151 -0
- package/dist/generated/index.d.ts.map +1 -0
- package/dist/generated/runtime.d.ts +5 -0
- package/dist/generated/runtime.d.ts.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/init-templates.d.ts +16 -0
- package/dist/init-templates.d.ts.map +1 -0
- package/dist/integrity.json +35 -0
- package/dist/kinds.d.ts +70 -0
- package/dist/kinds.d.ts.map +1 -0
- package/dist/lint/audit-catalog.d.ts +19 -0
- package/dist/lint/audit-catalog.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl010-unrestricted-outbound.d.ts +12 -0
- package/dist/lint/post-synth/cpl010-unrestricted-outbound.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl011-internal-firewall-scope.d.ts +11 -0
- package/dist/lint/post-synth/cpl011-internal-firewall-scope.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl012-no-literal-credential-env.d.ts +11 -0
- package/dist/lint/post-synth/cpl012-no-literal-credential-env.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl013-qualified-identity-principal.d.ts +11 -0
- package/dist/lint/post-synth/cpl013-qualified-identity-principal.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl014-qualified-secret-ref.d.ts +14 -0
- package/dist/lint/post-synth/cpl014-qualified-secret-ref.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl020-serverless-single-port.d.ts +10 -0
- package/dist/lint/post-synth/cpl020-serverless-single-port.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl021-cron-job-shape.d.ts +11 -0
- package/dist/lint/post-synth/cpl021-cron-job-shape.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl022-container-ports.d.ts +10 -0
- package/dist/lint/post-synth/cpl022-container-ports.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl023-container-resources.d.ts +12 -0
- package/dist/lint/post-synth/cpl023-container-resources.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl024-probe-handler.d.ts +10 -0
- package/dist/lint/post-synth/cpl024-probe-handler.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl025-autoscaling-shape.d.ts +11 -0
- package/dist/lint/post-synth/cpl025-autoscaling-shape.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl026-scale-to-zero.d.ts +11 -0
- package/dist/lint/post-synth/cpl026-scale-to-zero.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl027-capacity-ai-conflict.d.ts +13 -0
- package/dist/lint/post-synth/cpl027-capacity-ai-conflict.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl028-volume-set.d.ts +3 -0
- package/dist/lint/post-synth/cpl028-volume-set.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl029-link-targets.d.ts +3 -0
- package/dist/lint/post-synth/cpl029-link-targets.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl030-domain-routing.d.ts +3 -0
- package/dist/lint/post-synth/cpl030-domain-routing.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl040-pinned-image.d.ts +11 -0
- package/dist/lint/post-synth/cpl040-pinned-image.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl041-image-reference-form.d.ts +12 -0
- package/dist/lint/post-synth/cpl041-image-reference-form.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl042-gvc-placement.d.ts +10 -0
- package/dist/lint/post-synth/cpl042-gvc-placement.d.ts.map +1 -0
- package/dist/lint/post-synth/cpl043-policy-scope.d.ts +16 -0
- package/dist/lint/post-synth/cpl043-policy-scope.d.ts.map +1 -0
- package/dist/lint/post-synth/helpers.d.ts +45 -0
- package/dist/lint/post-synth/helpers.d.ts.map +1 -0
- package/dist/lint/post-synth/index.d.ts +3 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -0
- package/dist/lint/rules/cpl001-no-secret-literals.d.ts +3 -0
- package/dist/lint/rules/cpl001-no-secret-literals.d.ts.map +1 -0
- package/dist/lint/rules/cpl002-prefer-resource-reference.d.ts +3 -0
- package/dist/lint/rules/cpl002-prefer-resource-reference.d.ts.map +1 -0
- package/dist/lint/rules/index.d.ts +6 -0
- package/dist/lint/rules/index.d.ts.map +1 -0
- package/dist/lsp/completions.d.ts +7 -0
- package/dist/lsp/completions.d.ts.map +1 -0
- package/dist/lsp/hover.d.ts +4 -0
- package/dist/lsp/hover.d.ts.map +1 -0
- package/dist/manifest.json +6 -0
- package/dist/meta.json +2110 -0
- package/dist/okf/index.md +39 -0
- package/dist/okf/rules/CPL001.md +22 -0
- package/dist/okf/rules/CPL002.md +22 -0
- package/dist/okf/rules/CPL010.md +15 -0
- package/dist/okf/rules/CPL011.md +15 -0
- package/dist/okf/rules/CPL012.md +15 -0
- package/dist/okf/rules/CPL013.md +15 -0
- package/dist/okf/rules/CPL014.md +16 -0
- package/dist/okf/rules/CPL020.md +11 -0
- package/dist/okf/rules/CPL021.md +15 -0
- package/dist/okf/rules/CPL022.md +15 -0
- package/dist/okf/rules/CPL023.md +15 -0
- package/dist/okf/rules/CPL024.md +15 -0
- package/dist/okf/rules/CPL025.md +15 -0
- package/dist/okf/rules/CPL026.md +15 -0
- package/dist/okf/rules/CPL027.md +15 -0
- package/dist/okf/rules/CPL028.md +15 -0
- package/dist/okf/rules/CPL029.md +15 -0
- package/dist/okf/rules/CPL030.md +16 -0
- package/dist/okf/rules/CPL040.md +15 -0
- package/dist/okf/rules/CPL041.md +15 -0
- package/dist/okf/rules/CPL042.md +11 -0
- package/dist/okf/rules/CPL043.md +15 -0
- package/dist/okf/types/Domain.md +22 -0
- package/dist/okf/types/Gvc.md +21 -0
- package/dist/okf/types/Identity.md +29 -0
- package/dist/okf/types/IpSet.md +21 -0
- package/dist/okf/types/Policy.md +28 -0
- package/dist/okf/types/Secret.md +23 -0
- package/dist/okf/types/VolumeSet.md +22 -0
- package/dist/okf/types/Workload.md +38 -0
- package/dist/ownership.d.ts +23 -0
- package/dist/ownership.d.ts.map +1 -0
- package/dist/package-cli.d.ts +3 -0
- package/dist/package-cli.d.ts.map +1 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/reference-catalog.d.ts +24 -0
- package/dist/reference-catalog.d.ts.map +1 -0
- package/dist/rules/cpl001-no-secret-literals.ts +83 -0
- package/dist/rules/cpl002-prefer-resource-reference.ts +71 -0
- package/dist/rules/cpl010-unrestricted-outbound.ts +41 -0
- package/dist/rules/cpl011-internal-firewall-scope.ts +38 -0
- package/dist/rules/cpl012-no-literal-credential-env.ts +58 -0
- package/dist/rules/cpl013-qualified-identity-principal.ts +45 -0
- package/dist/rules/cpl014-qualified-secret-ref.ts +54 -0
- package/dist/rules/cpl020-serverless-single-port.ts +58 -0
- package/dist/rules/cpl021-cron-job-shape.ts +64 -0
- package/dist/rules/cpl022-container-ports.ts +64 -0
- package/dist/rules/cpl023-container-resources.ts +87 -0
- package/dist/rules/cpl024-probe-handler.ts +51 -0
- package/dist/rules/cpl025-autoscaling-shape.ts +100 -0
- package/dist/rules/cpl026-scale-to-zero.ts +67 -0
- package/dist/rules/cpl027-capacity-ai-conflict.ts +78 -0
- package/dist/rules/cpl028-volume-set.ts +183 -0
- package/dist/rules/cpl029-link-targets.ts +155 -0
- package/dist/rules/cpl030-domain-routing.ts +137 -0
- package/dist/rules/cpl040-pinned-image.ts +48 -0
- package/dist/rules/cpl041-image-reference-form.ts +60 -0
- package/dist/rules/cpl042-gvc-placement.ts +57 -0
- package/dist/rules/cpl043-policy-scope.ts +0 -0
- package/dist/rules/helpers.ts +114 -0
- package/dist/serializer.d.ts +69 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/skills/chant-cpln-secrets.md +110 -0
- package/dist/skills/chant-cpln-workloads.md +78 -0
- package/dist/skills/chant-cpln.md +103 -0
- package/dist/spec/fetch.d.ts +43 -0
- package/dist/spec/fetch.d.ts.map +1 -0
- package/dist/spec/parse.d.ts +79 -0
- package/dist/spec/parse.d.ts.map +1 -0
- package/dist/spec/snapshot-cli.d.ts +18 -0
- package/dist/spec/snapshot-cli.d.ts.map +1 -0
- package/dist/types/index.d.ts +1649 -0
- package/dist/validate-cli.d.ts +3 -0
- package/dist/validate-cli.d.ts.map +1 -0
- package/dist/validate.d.ts +26 -0
- package/dist/validate.d.ts.map +1 -0
- package/package.json +74 -0
- package/src/api.ts +134 -0
- package/src/codegen/docs-cli.ts +7 -0
- package/src/codegen/docs.ts +94 -0
- package/src/codegen/generate-cli.ts +34 -0
- package/src/codegen/generate-lexicon.ts +88 -0
- package/src/codegen/generate-typescript.ts +71 -0
- package/src/codegen/generate.ts +110 -0
- package/src/codegen/naming.ts +61 -0
- package/src/codegen/package.ts +35 -0
- package/src/composites/composites.test.ts +252 -0
- package/src/composites/cron-job.ts +124 -0
- package/src/composites/gvc-environment.ts +104 -0
- package/src/composites/index.ts +17 -0
- package/src/composites/public-domain.ts +144 -0
- package/src/composites/secret-access.ts +136 -0
- package/src/composites/serverless-service.ts +143 -0
- package/src/composites/stateful-service.ts +179 -0
- package/src/coverage.ts +149 -0
- package/src/describe-resources.test.ts +252 -0
- package/src/describe-resources.ts +310 -0
- package/src/detect.ts +58 -0
- package/src/entity-props.ts +98 -0
- package/src/generated/index.d.ts +1649 -0
- package/src/generated/index.ts +156 -0
- package/src/generated/lexicon-cpln.json +2110 -0
- package/src/generated/runtime.ts +4 -0
- package/src/index.ts +31 -0
- package/src/init-templates.ts +131 -0
- package/src/kinds.ts +173 -0
- package/src/lint/audit-catalog.ts +230 -0
- package/src/lint/post-synth/cpl010-unrestricted-outbound.ts +41 -0
- package/src/lint/post-synth/cpl011-internal-firewall-scope.ts +38 -0
- package/src/lint/post-synth/cpl012-no-literal-credential-env.ts +58 -0
- package/src/lint/post-synth/cpl013-qualified-identity-principal.ts +45 -0
- package/src/lint/post-synth/cpl014-qualified-secret-ref.ts +54 -0
- package/src/lint/post-synth/cpl020-serverless-single-port.ts +58 -0
- package/src/lint/post-synth/cpl021-cron-job-shape.ts +64 -0
- package/src/lint/post-synth/cpl022-container-ports.ts +64 -0
- package/src/lint/post-synth/cpl023-container-resources.ts +87 -0
- package/src/lint/post-synth/cpl024-probe-handler.ts +51 -0
- package/src/lint/post-synth/cpl025-autoscaling-shape.ts +100 -0
- package/src/lint/post-synth/cpl026-scale-to-zero.ts +67 -0
- package/src/lint/post-synth/cpl027-capacity-ai-conflict.ts +78 -0
- package/src/lint/post-synth/cpl028-volume-set.ts +183 -0
- package/src/lint/post-synth/cpl029-link-targets.ts +155 -0
- package/src/lint/post-synth/cpl030-domain-routing.ts +137 -0
- package/src/lint/post-synth/cpl040-pinned-image.ts +48 -0
- package/src/lint/post-synth/cpl041-image-reference-form.ts +60 -0
- package/src/lint/post-synth/cpl042-gvc-placement.ts +57 -0
- package/src/lint/post-synth/cpl043-policy-scope.ts +0 -0
- package/src/lint/post-synth/helpers.ts +114 -0
- package/src/lint/post-synth/index.ts +45 -0
- package/src/lint/post-synth/post-synth.test.ts +737 -0
- package/src/lint/rules/cpl001-no-secret-literals.ts +83 -0
- package/src/lint/rules/cpl002-prefer-resource-reference.ts +71 -0
- package/src/lint/rules/index.ts +9 -0
- package/src/lint/rules/rules.test.ts +108 -0
- package/src/lsp/completions.test.ts +44 -0
- package/src/lsp/completions.ts +27 -0
- package/src/lsp/hover.test.ts +53 -0
- package/src/lsp/hover.ts +130 -0
- package/src/ownership.ts +28 -0
- package/src/package-cli.ts +25 -0
- package/src/plugin.test.ts +131 -0
- package/src/plugin.ts +187 -0
- package/src/reference-catalog.ts +110 -0
- package/src/serializer.test.ts +233 -0
- package/src/serializer.ts +253 -0
- package/src/skills/chant-cpln-secrets.md +110 -0
- package/src/skills/chant-cpln-workloads.md +78 -0
- package/src/skills/chant-cpln.md +103 -0
- package/src/spec/cpln-openapi.snapshot.json +10651 -0
- package/src/spec/fetch.ts +77 -0
- package/src/spec/parse.ts +451 -0
- package/src/spec/snapshot-cli.ts +71 -0
- package/src/validate-cli.ts +8 -0
- package/src/validate.ts +102 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Plugin
|
|
2
|
+
export { cplnPlugin } from "./plugin";
|
|
3
|
+
|
|
4
|
+
// Serializer
|
|
5
|
+
export { cplnSerializer, cplnLink } from "./serializer";
|
|
6
|
+
|
|
7
|
+
// The modelled kinds and the link/path helpers built on them. Useful to
|
|
8
|
+
// consumers writing their own tooling against a chant-declared cpln estate.
|
|
9
|
+
export {
|
|
10
|
+
KINDS,
|
|
11
|
+
KIND_NAMES,
|
|
12
|
+
kindByName,
|
|
13
|
+
kindByTypeName,
|
|
14
|
+
kindByClassName,
|
|
15
|
+
isCplnResourceType,
|
|
16
|
+
collectionPath,
|
|
17
|
+
resourcePath,
|
|
18
|
+
NAMESPACE,
|
|
19
|
+
SERVICE,
|
|
20
|
+
} from "./kinds";
|
|
21
|
+
export type { CplnKind } from "./kinds";
|
|
22
|
+
|
|
23
|
+
// Ownership marker convention (cpln `tags` keys).
|
|
24
|
+
export { CPLN_TAG_OWNERSHIP_KEYS } from "./ownership";
|
|
25
|
+
|
|
26
|
+
// Composites — opinionated bundles over the generated resources.
|
|
27
|
+
export * from "./composites";
|
|
28
|
+
|
|
29
|
+
// Generated resources — `Gvc`, `Workload`, `Identity`, `VolumeSet`, `Secret`,
|
|
30
|
+
// `Policy`, `Domain`, `IpSet` and the property types their specs reach.
|
|
31
|
+
export * from "./generated/index";
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant init` scaffolding for cpln projects.
|
|
3
|
+
*
|
|
4
|
+
* Three templates, chosen so the default is the shape most people actually
|
|
5
|
+
* start with and the other two are the two directions it grows in. Each is
|
|
6
|
+
* written to pass this lexicon's own checks as scaffolded — a template that
|
|
7
|
+
* emits CPL findings on first build teaches the wrong lesson about what the
|
|
8
|
+
* findings mean.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { InitTemplateSet } from "@intentius/chant/lexicon";
|
|
12
|
+
|
|
13
|
+
const DEFAULT = `import { GvcEnvironment, ServerlessService } from "@intentius/chant-lexicon-cpln";
|
|
14
|
+
|
|
15
|
+
// A GVC and one public serverless service.
|
|
16
|
+
//
|
|
17
|
+
// Placement lives on the GVC, not the workload — locationLinks is what decides
|
|
18
|
+
// where anything in this environment runs. Set CPLN_ORG to your org name.
|
|
19
|
+
const org = process.env.CPLN_ORG ?? "my-org";
|
|
20
|
+
|
|
21
|
+
export const { gvc } = GvcEnvironment({
|
|
22
|
+
name: "prod",
|
|
23
|
+
org,
|
|
24
|
+
locations: ["aws-us-east-1"],
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Serverless requires exactly one HTTP port, and the external firewall starts
|
|
28
|
+
// closed in both directions — so a public service says so explicitly. Egress is
|
|
29
|
+
// left closed; open it to the hosts this actually calls.
|
|
30
|
+
export const { workload } = ServerlessService({
|
|
31
|
+
name: "web",
|
|
32
|
+
gvc: "prod",
|
|
33
|
+
image: "nginx:1.27",
|
|
34
|
+
port: 8080,
|
|
35
|
+
inboundAllowCidr: ["0.0.0.0/0"],
|
|
36
|
+
});
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
const WITH_SECRETS = `import {
|
|
40
|
+
GvcEnvironment,
|
|
41
|
+
ServerlessService,
|
|
42
|
+
SecretAccess,
|
|
43
|
+
Secret,
|
|
44
|
+
secretRef,
|
|
45
|
+
} from "@intentius/chant-lexicon-cpln";
|
|
46
|
+
|
|
47
|
+
// A service that reads a secret at runtime.
|
|
48
|
+
//
|
|
49
|
+
// Reading a secret takes three things, and missing any one fails *silently* at
|
|
50
|
+
// runtime rather than at apply: an identity on the workload, a policy granting
|
|
51
|
+
// that identity \`reveal\`, and a field-qualified reference. SecretAccess owns
|
|
52
|
+
// the first two; secretRef() is the third.
|
|
53
|
+
const org = process.env.CPLN_ORG ?? "my-org";
|
|
54
|
+
|
|
55
|
+
export const { gvc } = GvcEnvironment({
|
|
56
|
+
name: "prod",
|
|
57
|
+
org,
|
|
58
|
+
locations: ["aws-us-east-1"],
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// The value is a placeholder. Set the real one with \`cpln secret edit\`, or
|
|
62
|
+
// point this at a value read from the environment at build time — never a
|
|
63
|
+
// literal in source (CPL001).
|
|
64
|
+
export const dbPassword = new Secret({
|
|
65
|
+
name: "db-password",
|
|
66
|
+
type: "opaque",
|
|
67
|
+
data: { payload: process.env.DB_PASSWORD ?? "changeme", encoding: "plain" },
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
export const { identity, policy } = SecretAccess({
|
|
71
|
+
name: "web-identity",
|
|
72
|
+
gvc: "prod",
|
|
73
|
+
secrets: ["db-password"],
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export const { workload } = ServerlessService({
|
|
77
|
+
name: "web",
|
|
78
|
+
gvc: "prod",
|
|
79
|
+
image: "nginx:1.27",
|
|
80
|
+
port: 8080,
|
|
81
|
+
inboundAllowCidr: ["0.0.0.0/0"],
|
|
82
|
+
identityLink: "//gvc/prod/identity/web-identity",
|
|
83
|
+
env: {
|
|
84
|
+
DATABASE_PASSWORD: secretRef("db-password", "payload"),
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
`;
|
|
88
|
+
|
|
89
|
+
const STATEFUL = `import { GvcEnvironment, StatefulService } from "@intentius/chant-lexicon-cpln";
|
|
90
|
+
|
|
91
|
+
// A stateful service with persistent storage.
|
|
92
|
+
//
|
|
93
|
+
// Both fileSystemType and performanceClass are immutable — changing either
|
|
94
|
+
// later means delete, recreate, and data loss — so they are worth settling
|
|
95
|
+
// before the first apply. ext4 binds to exactly one stateful workload; use
|
|
96
|
+
// "shared" if several workloads need the same volume.
|
|
97
|
+
const org = process.env.CPLN_ORG ?? "my-org";
|
|
98
|
+
|
|
99
|
+
export const { gvc } = GvcEnvironment({
|
|
100
|
+
name: "prod",
|
|
101
|
+
org,
|
|
102
|
+
locations: ["aws-us-east-1"],
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export const { workload, volumeSet } = StatefulService({
|
|
106
|
+
name: "db",
|
|
107
|
+
gvc: "prod",
|
|
108
|
+
image: "postgres:17",
|
|
109
|
+
mountPath: "/var/lib/postgresql/data",
|
|
110
|
+
capacityGb: 20,
|
|
111
|
+
fileSystemType: "ext4",
|
|
112
|
+
performanceClass: "general-purpose-ssd",
|
|
113
|
+
ports: [{ number: 5432, protocol: "tcp" }],
|
|
114
|
+
env: { POSTGRES_DB: "app" },
|
|
115
|
+
});
|
|
116
|
+
`;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Return the scaffold for a template name. Unknown names fall back to the
|
|
120
|
+
* default rather than erroring — `chant init` treats the name as a hint.
|
|
121
|
+
*/
|
|
122
|
+
export function cplnInitTemplates(template?: string): InitTemplateSet {
|
|
123
|
+
switch (template) {
|
|
124
|
+
case "secrets":
|
|
125
|
+
return { src: { "infra.ts": WITH_SECRETS } };
|
|
126
|
+
case "stateful":
|
|
127
|
+
return { src: { "infra.ts": STATEFUL } };
|
|
128
|
+
default:
|
|
129
|
+
return { src: { "infra.ts": DEFAULT } };
|
|
130
|
+
}
|
|
131
|
+
}
|
package/src/kinds.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Control Plane kinds this lexicon models, and the facts about each that
|
|
3
|
+
* more than one subsystem needs.
|
|
4
|
+
*
|
|
5
|
+
* Codegen, the serializer, observation, the LSP providers, the MCP catalog and
|
|
6
|
+
* the docs generator all need to agree on the same three things: which schema
|
|
7
|
+
* in the OpenAPI document backs a kind, what its chant type name is, and
|
|
8
|
+
* whether it is scoped to a GVC. Each of those was, in an earlier draft, a
|
|
9
|
+
* literal repeated in five files — so a kind added in one place quietly went
|
|
10
|
+
* missing from the others. This table is the single place a kind is declared.
|
|
11
|
+
*
|
|
12
|
+
* Coverage is deliberately the *workload* surface rather than every kind the
|
|
13
|
+
* API exposes. The org-administration kinds (`group`, `serviceaccount`,
|
|
14
|
+
* `cloudaccount`, `auditctx`, `user`, `agent`) and `mk8s` are all modellable
|
|
15
|
+
* from the same spec by adding rows here; they are out of the first pass, not
|
|
16
|
+
* out of reach. `chant cpln coverage` reports the gap rather than hiding it.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** The chant namespace segment for every generated cpln type. */
|
|
20
|
+
export const NAMESPACE = "Cpln";
|
|
21
|
+
|
|
22
|
+
/** The single service segment. The Control Plane Core API is one service. */
|
|
23
|
+
export const SERVICE = "Core";
|
|
24
|
+
|
|
25
|
+
export interface CplnKind {
|
|
26
|
+
/**
|
|
27
|
+
* The `kind` discriminator as it appears in a `cpln apply` manifest and in
|
|
28
|
+
* the API's own responses. Also the REST collection segment — the API is
|
|
29
|
+
* uniform that way, so `/org/{org}/{kind}` is the org-scoped collection and
|
|
30
|
+
* `/org/{org}/gvc/{gvc}/{kind}` the GVC-scoped one.
|
|
31
|
+
*/
|
|
32
|
+
kind: string;
|
|
33
|
+
/** The schema name in `components.schemas` backing this kind. */
|
|
34
|
+
schema: string;
|
|
35
|
+
/** The chant type name, e.g. `Cpln::Core::Workload`. */
|
|
36
|
+
typeName: string;
|
|
37
|
+
/** The generated TypeScript class name, e.g. `Workload`. */
|
|
38
|
+
className: string;
|
|
39
|
+
/**
|
|
40
|
+
* Whether the kind lives inside a GVC. GVC-scoped kinds get a synthetic
|
|
41
|
+
* `gvc` property — see `SYNTHETIC_GVC_PROP` in `spec/parse.ts` for why the
|
|
42
|
+
* spec cannot supply it.
|
|
43
|
+
*/
|
|
44
|
+
gvcScoped: boolean;
|
|
45
|
+
/** One-line summary, used for LSP hover, MCP catalog entries and docs. */
|
|
46
|
+
summary: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const KINDS: CplnKind[] = [
|
|
50
|
+
{
|
|
51
|
+
kind: "gvc",
|
|
52
|
+
schema: "gvc",
|
|
53
|
+
typeName: `${NAMESPACE}::${SERVICE}::Gvc`,
|
|
54
|
+
className: "Gvc",
|
|
55
|
+
gvcScoped: false,
|
|
56
|
+
summary:
|
|
57
|
+
"Global Virtual Cloud — the placement and networking boundary every workload, identity and volume set belongs to.",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
kind: "workload",
|
|
61
|
+
schema: "workload",
|
|
62
|
+
typeName: `${NAMESPACE}::${SERVICE}::Workload`,
|
|
63
|
+
className: "Workload",
|
|
64
|
+
gvcScoped: true,
|
|
65
|
+
summary:
|
|
66
|
+
"A running unit of work — serverless, standard, cron, stateful or vm — with its containers, firewall and autoscaling.",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
kind: "identity",
|
|
70
|
+
schema: "identity",
|
|
71
|
+
typeName: `${NAMESPACE}::${SERVICE}::Identity`,
|
|
72
|
+
className: "Identity",
|
|
73
|
+
gvcScoped: true,
|
|
74
|
+
summary:
|
|
75
|
+
"A workload's identity, carrying its cloud-provider access (AWS/GCP/Azure) and network resource grants.",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
kind: "volumeset",
|
|
79
|
+
schema: "volumeset",
|
|
80
|
+
typeName: `${NAMESPACE}::${SERVICE}::VolumeSet`,
|
|
81
|
+
className: "VolumeSet",
|
|
82
|
+
gvcScoped: true,
|
|
83
|
+
summary: "Persistent storage attached to stateful workloads, with its performance class and snapshot policy.",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
kind: "secret",
|
|
87
|
+
schema: "secret",
|
|
88
|
+
typeName: `${NAMESPACE}::${SERVICE}::Secret`,
|
|
89
|
+
className: "Secret",
|
|
90
|
+
gvcScoped: false,
|
|
91
|
+
summary: "An org-scoped secret — opaque, TLS, keypair, dictionary, or a cloud-provider credential.",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
kind: "policy",
|
|
95
|
+
schema: "policy",
|
|
96
|
+
typeName: `${NAMESPACE}::${SERVICE}::Policy`,
|
|
97
|
+
className: "Policy",
|
|
98
|
+
gvcScoped: false,
|
|
99
|
+
summary: "An access policy binding permissions to principals over a target kind, link set, or query.",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
kind: "domain",
|
|
103
|
+
schema: "domain",
|
|
104
|
+
typeName: `${NAMESPACE}::${SERVICE}::Domain`,
|
|
105
|
+
className: "Domain",
|
|
106
|
+
gvcScoped: false,
|
|
107
|
+
summary: "A custom domain with its TLS, CORS and per-port routing to workloads.",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
kind: "ipset",
|
|
111
|
+
schema: "ipset",
|
|
112
|
+
typeName: `${NAMESPACE}::${SERVICE}::IpSet`,
|
|
113
|
+
className: "IpSet",
|
|
114
|
+
gvcScoped: false,
|
|
115
|
+
summary: "A set of dedicated IP addresses reserved in named locations and bound to a workload.",
|
|
116
|
+
},
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
/** Every modelled kind string, in declaration order. */
|
|
120
|
+
export const KIND_NAMES: string[] = KINDS.map((k) => k.kind);
|
|
121
|
+
|
|
122
|
+
const BY_KIND = new Map(KINDS.map((k) => [k.kind, k] as const));
|
|
123
|
+
const BY_TYPE_NAME = new Map(KINDS.map((k) => [k.typeName, k] as const));
|
|
124
|
+
const BY_CLASS_NAME = new Map(KINDS.map((k) => [k.className, k] as const));
|
|
125
|
+
|
|
126
|
+
/** Look up a kind by its `kind` discriminator (`"workload"`). */
|
|
127
|
+
export function kindByName(kind: string): CplnKind | undefined {
|
|
128
|
+
return BY_KIND.get(kind);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Look up a kind by chant type name (`"Cpln::Core::Workload"`). */
|
|
132
|
+
export function kindByTypeName(typeName: string): CplnKind | undefined {
|
|
133
|
+
return BY_TYPE_NAME.get(typeName);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Look up a kind by generated class name (`"Workload"`). */
|
|
137
|
+
export function kindByClassName(className: string): CplnKind | undefined {
|
|
138
|
+
return BY_CLASS_NAME.get(className);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Whether a chant type name belongs to a modelled cpln resource (not a property type). */
|
|
142
|
+
export function isCplnResourceType(typeName: string): boolean {
|
|
143
|
+
return BY_TYPE_NAME.has(typeName);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The REST collection path for a kind, relative to the API root.
|
|
148
|
+
*
|
|
149
|
+
* GVC-scoped kinds have two: the writable collection nested under their GVC,
|
|
150
|
+
* and an org-wide read-only rollup. Observation uses the rollup (one request
|
|
151
|
+
* per kind instead of one per GVC); apply uses the nested one.
|
|
152
|
+
*/
|
|
153
|
+
export function collectionPath(kind: CplnKind, org: string, gvc?: string): string {
|
|
154
|
+
if (kind.gvcScoped && gvc) return `/org/${org}/gvc/${gvc}/${kind.kind}`;
|
|
155
|
+
return `/org/${org}/${kind.kind}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** The path of a single named resource. */
|
|
159
|
+
export function resourcePath(kind: CplnKind, org: string, name: string, gvc?: string): string {
|
|
160
|
+
return `${collectionPath(kind, org, gvc)}/${name}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Extract the short name from a chant type name: `Cpln::Core::Workload` → `Workload`. */
|
|
164
|
+
export function cplnShortName(typeName: string): string {
|
|
165
|
+
const parts = typeName.split("::");
|
|
166
|
+
return parts[parts.length - 1];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Extract the service segment: `Cpln::Core::Workload` → `Core`. */
|
|
170
|
+
export function cplnServiceName(typeName: string): string {
|
|
171
|
+
const parts = typeName.split("::");
|
|
172
|
+
return parts.length >= 2 ? parts[1] : SERVICE;
|
|
173
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cpln lexicon's `chant audit` catalog — metadata for every CPL check.
|
|
3
|
+
*
|
|
4
|
+
* Every entry carries `yamlBased: false`. That is not an oversight: all of
|
|
5
|
+
* cpln's checks read the chant model (`ctx.entities`) rather than the emitted
|
|
6
|
+
* manifests (`ctx.outputs`), because the facts they need — which GVC a workload
|
|
7
|
+
* is placed in, which volume set a mount names, whether a policy principal is
|
|
8
|
+
* GVC-qualified — are relationships between declared resources rather than text
|
|
9
|
+
* in one document. They fire on `chant build` and cannot fire on an audit of
|
|
10
|
+
* standalone cpln YAML. `auditRule()` hardcodes `yamlBased: true`, so these are
|
|
11
|
+
* constructed directly.
|
|
12
|
+
*
|
|
13
|
+
* A test asserts this catalog covers every check the plugin registers, so a
|
|
14
|
+
* check added without an entry here fails the build rather than contributing
|
|
15
|
+
* nothing to `chant audit` silently.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { Authority, RuleMeta } from "@intentius/chant/audit/catalog";
|
|
19
|
+
|
|
20
|
+
// ── Authorities ────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
const CPLN_WORKLOAD: Authority = {
|
|
23
|
+
name: "Control Plane — Workload reference",
|
|
24
|
+
url: "https://docs.controlplane.com/reference/workload",
|
|
25
|
+
};
|
|
26
|
+
const CPLN_SECRETS: Authority = {
|
|
27
|
+
name: "Control Plane — Secret access model",
|
|
28
|
+
url: "https://docs.controlplane.com/reference/secret",
|
|
29
|
+
};
|
|
30
|
+
const CPLN_POLICY: Authority = {
|
|
31
|
+
name: "Control Plane — Policy reference",
|
|
32
|
+
url: "https://docs.controlplane.com/reference/policy",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* An entry with no external citation.
|
|
37
|
+
*
|
|
38
|
+
* Deliberately unable to take one. Core asserts two invariants over the
|
|
39
|
+
* aggregated catalog — an authority citation always means `security`, and it
|
|
40
|
+
* only attaches to `merge-worthy` — so a citation is not decoration, it is a
|
|
41
|
+
* claim about the finding's weight. Splitting the constructors makes the pair
|
|
42
|
+
* unviolatable here rather than caught later by core's test.
|
|
43
|
+
*/
|
|
44
|
+
function rule(
|
|
45
|
+
id: string,
|
|
46
|
+
tier: RuleMeta["tier"],
|
|
47
|
+
category: RuleMeta["category"],
|
|
48
|
+
title: string,
|
|
49
|
+
remediation: string,
|
|
50
|
+
): RuleMeta {
|
|
51
|
+
return { id, tier, fixKind: "guidance", category, title, remediation, yamlBased: false };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* A merge-worthy security entry, which is the only kind that may cite an
|
|
56
|
+
* authority. The tier and category are not parameters because there is only
|
|
57
|
+
* one legal combination.
|
|
58
|
+
*/
|
|
59
|
+
function securityRule(id: string, title: string, remediation: string, authority: Authority[]): RuleMeta {
|
|
60
|
+
return {
|
|
61
|
+
id,
|
|
62
|
+
tier: "merge-worthy",
|
|
63
|
+
fixKind: "guidance",
|
|
64
|
+
category: "security",
|
|
65
|
+
title,
|
|
66
|
+
remediation,
|
|
67
|
+
authority,
|
|
68
|
+
yamlBased: false,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const cplnAuditCatalog: Record<string, RuleMeta> = {
|
|
73
|
+
// ── Source-level rules ───────────────────────────────────────────
|
|
74
|
+
CPL001: securityRule(
|
|
75
|
+
"CPL001",
|
|
76
|
+
"Literal credential in a cpln declaration",
|
|
77
|
+
"Store the value as a Secret and reference it as cpln://secret/NAME.FIELD.",
|
|
78
|
+
[CPLN_SECRETS],
|
|
79
|
+
),
|
|
80
|
+
CPL002: rule(
|
|
81
|
+
"CPL002",
|
|
82
|
+
"report-only",
|
|
83
|
+
"best-practice",
|
|
84
|
+
"Hand-written Control Plane link where a resource reference would do",
|
|
85
|
+
"Pass the declared resource; the serializer emits the correct link, GVC qualifier included.",
|
|
86
|
+
),
|
|
87
|
+
|
|
88
|
+
// ── Security ─────────────────────────────────────────────────────
|
|
89
|
+
CPL010: securityRule(
|
|
90
|
+
"CPL010",
|
|
91
|
+
"Workload allows outbound traffic to every address",
|
|
92
|
+
"Narrow outboundAllowCIDR, or use outboundAllowHostname with wildcards.",
|
|
93
|
+
[CPLN_WORKLOAD],
|
|
94
|
+
),
|
|
95
|
+
CPL011: rule(
|
|
96
|
+
"CPL011",
|
|
97
|
+
"report-only",
|
|
98
|
+
"security",
|
|
99
|
+
"Internal firewall opened to the whole org",
|
|
100
|
+
'Prefer "same-gvc", or "workload-list" naming the specific workloads.',
|
|
101
|
+
),
|
|
102
|
+
CPL012: securityRule(
|
|
103
|
+
"CPL012",
|
|
104
|
+
"Credential-shaped environment variable set to a literal",
|
|
105
|
+
"Reference a secret: cpln://secret/NAME.FIELD.",
|
|
106
|
+
[CPLN_SECRETS],
|
|
107
|
+
),
|
|
108
|
+
CPL013: securityRule(
|
|
109
|
+
"CPL013",
|
|
110
|
+
"Policy binds an identity by its unqualified link",
|
|
111
|
+
"Use //gvc/<gvc>/identity/<name>; the bare //identity/<name> form is silently ignored.",
|
|
112
|
+
[CPLN_POLICY],
|
|
113
|
+
),
|
|
114
|
+
CPL014: rule(
|
|
115
|
+
"CPL014",
|
|
116
|
+
"merge-worthy",
|
|
117
|
+
"correctness",
|
|
118
|
+
"Secret reference names no field",
|
|
119
|
+
"Qualify the reference: cpln://secret/NAME.payload, .username, .cert, or a dictionary key.",
|
|
120
|
+
),
|
|
121
|
+
|
|
122
|
+
// ── Correctness ──────────────────────────────────────────────────
|
|
123
|
+
CPL020: rule(
|
|
124
|
+
"CPL020",
|
|
125
|
+
"merge-worthy",
|
|
126
|
+
"correctness",
|
|
127
|
+
"Serverless workload does not expose exactly one HTTP port",
|
|
128
|
+
"Expose exactly one http/http2 port, or change the workload to type standard.",
|
|
129
|
+
),
|
|
130
|
+
CPL021: rule(
|
|
131
|
+
"CPL021",
|
|
132
|
+
"merge-worthy",
|
|
133
|
+
"correctness",
|
|
134
|
+
"Cron workload shape is invalid",
|
|
135
|
+
"Cron requires spec.job.schedule and must expose no ports; no other type may set spec.job.",
|
|
136
|
+
),
|
|
137
|
+
CPL022: rule(
|
|
138
|
+
"CPL022",
|
|
139
|
+
"merge-worthy",
|
|
140
|
+
"correctness",
|
|
141
|
+
"Container ports conflict",
|
|
142
|
+
"Use `ports` rather than `port`, and keep port numbers unique across a workload's containers.",
|
|
143
|
+
),
|
|
144
|
+
CPL023: rule(
|
|
145
|
+
"CPL023",
|
|
146
|
+
"merge-worthy",
|
|
147
|
+
"correctness",
|
|
148
|
+
"Container resources violate a platform floor or the memory-to-CPU ratio",
|
|
149
|
+
"CPU ≥ 25m, memory ≥ 32Mi, and memory(MiB)/cpu(m) ≤ 8 — or add the cpln/relaxMemoryToCpuRatio tag.",
|
|
150
|
+
),
|
|
151
|
+
CPL024: rule(
|
|
152
|
+
"CPL024",
|
|
153
|
+
"merge-worthy",
|
|
154
|
+
"correctness",
|
|
155
|
+
"Health-check probe sets no handler, or more than one",
|
|
156
|
+
"Set exactly one of exec, grpc, tcpSocket, httpGet per probe.",
|
|
157
|
+
),
|
|
158
|
+
CPL025: rule(
|
|
159
|
+
"CPL025",
|
|
160
|
+
"merge-worthy",
|
|
161
|
+
"correctness",
|
|
162
|
+
"Autoscaling configuration is not a valid combination",
|
|
163
|
+
"`metric` and `multi` are exclusive; `target` belongs to the single-metric form and caps at 100 for cpu/memory.",
|
|
164
|
+
),
|
|
165
|
+
CPL026: rule(
|
|
166
|
+
"CPL026",
|
|
167
|
+
"merge-worthy",
|
|
168
|
+
"correctness",
|
|
169
|
+
"minScale 0 with a strategy that cannot scale to zero",
|
|
170
|
+
"Serverless needs rps or concurrency; standard and stateful need KEDA, enabled at the GVC.",
|
|
171
|
+
),
|
|
172
|
+
CPL027: rule(
|
|
173
|
+
"CPL027",
|
|
174
|
+
"merge-worthy",
|
|
175
|
+
"correctness",
|
|
176
|
+
"Capacity AI combined with a feature it excludes",
|
|
177
|
+
"Set defaultOptions.capacityAI false, or drop the CPU-utilization scaling, multi-metric scaling, or GPU.",
|
|
178
|
+
),
|
|
179
|
+
CPL028: rule(
|
|
180
|
+
"CPL028",
|
|
181
|
+
"merge-worthy",
|
|
182
|
+
"correctness",
|
|
183
|
+
"Volume set capacity, filesystem binding, or mount paths are invalid",
|
|
184
|
+
"Clear the performance-class capacity floor, mount ext4/xfs only from stateful workloads, and keep mount paths disjoint.",
|
|
185
|
+
),
|
|
186
|
+
CPL029: rule(
|
|
187
|
+
"CPL029",
|
|
188
|
+
"merge-worthy",
|
|
189
|
+
"correctness",
|
|
190
|
+
"Control Plane link does not resolve, or crosses a GVC boundary",
|
|
191
|
+
"Point the link at a declared resource in the right GVC; identities cannot be shared across GVCs.",
|
|
192
|
+
),
|
|
193
|
+
CPL030: rule(
|
|
194
|
+
"CPL030",
|
|
195
|
+
"merge-worthy",
|
|
196
|
+
"correctness",
|
|
197
|
+
"Domain DNS mode, challenge, or routing target is invalid",
|
|
198
|
+
"Apex domains need cname; ns needs dns01; pick exactly one of gvcLink, workloadLink, ports[].routes.",
|
|
199
|
+
),
|
|
200
|
+
|
|
201
|
+
// ── Best practice ────────────────────────────────────────────────
|
|
202
|
+
CPL040: rule(
|
|
203
|
+
"CPL040",
|
|
204
|
+
"report-only",
|
|
205
|
+
"best-practice",
|
|
206
|
+
"Container image is not pinned",
|
|
207
|
+
"Pin a version tag or a digest; a scale-from-zero cold start re-pulls.",
|
|
208
|
+
),
|
|
209
|
+
CPL041: rule(
|
|
210
|
+
"CPL041",
|
|
211
|
+
"merge-worthy",
|
|
212
|
+
"correctness",
|
|
213
|
+
"Image reference uses a registry prefix Control Plane rejects",
|
|
214
|
+
"Drop the docker.io/ prefix; address this org's own images as //image/NAME:TAG.",
|
|
215
|
+
),
|
|
216
|
+
CPL042: rule(
|
|
217
|
+
"CPL042",
|
|
218
|
+
"merge-worthy",
|
|
219
|
+
"correctness",
|
|
220
|
+
"GVC declares no placement",
|
|
221
|
+
"Set spec.staticPlacement.locationLinks, e.g. /org/<org>/location/aws-us-east-1.",
|
|
222
|
+
),
|
|
223
|
+
CPL043: rule(
|
|
224
|
+
"CPL043",
|
|
225
|
+
"merge-worthy",
|
|
226
|
+
"correctness",
|
|
227
|
+
"Policy target kind, scope, or origin is not well formed",
|
|
228
|
+
"Pick exactly one target scope, target a policy-targetable kind, and never set origin by hand.",
|
|
229
|
+
),
|
|
230
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
2
|
+
import { readArray } from "../../entity-props";
|
|
3
|
+
import { WORKLOAD, entitiesOfType } from "./helpers";
|
|
4
|
+
|
|
5
|
+
const ANY_ADDRESS = "0.0.0.0/0";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* CPL010: a workload that may call out to the entire internet.
|
|
9
|
+
*
|
|
10
|
+
* Inbound `0.0.0.0/0` is how a public service is *supposed* to be written, so
|
|
11
|
+
* it is not flagged. Outbound `0.0.0.0/0` is different: it is rarely required,
|
|
12
|
+
* it is the egress path for anything that gets a foothold in the container,
|
|
13
|
+
* and Control Plane supports hostname rules (with wildcards) precisely so it
|
|
14
|
+
* does not have to be all-or-nothing.
|
|
15
|
+
*/
|
|
16
|
+
export const unrestrictedOutboundCheck: PostSynthCheck = {
|
|
17
|
+
id: "CPL010",
|
|
18
|
+
description: "Workloads should not allow outbound traffic to every address",
|
|
19
|
+
|
|
20
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
21
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
22
|
+
|
|
23
|
+
for (const [name, entity] of entitiesOfType(ctx.entities, WORKLOAD)) {
|
|
24
|
+
const outbound = readArray(entity, "spec", "firewallConfig", "external", "outboundAllowCIDR");
|
|
25
|
+
if (!outbound.includes(ANY_ADDRESS)) continue;
|
|
26
|
+
|
|
27
|
+
diagnostics.push({
|
|
28
|
+
checkId: "CPL010",
|
|
29
|
+
severity: "warning",
|
|
30
|
+
message:
|
|
31
|
+
`Workload "${name}" allows outbound traffic to ${ANY_ADDRESS}. Narrow it to the CIDRs it ` +
|
|
32
|
+
`actually calls, or use \`outboundAllowHostname\` (wildcards like "*.amazonaws.com" are ` +
|
|
33
|
+
`supported) so egress is bounded.`,
|
|
34
|
+
entity: name,
|
|
35
|
+
lexicon: "cpln",
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return diagnostics;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
2
|
+
import { readString } from "../../entity-props";
|
|
3
|
+
import { GVC, WORKLOAD, entitiesOfType } from "./helpers";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* CPL011: internal firewall opened to the whole org.
|
|
7
|
+
*
|
|
8
|
+
* `same-org` lets every workload in every GVC reach this one, which crosses the
|
|
9
|
+
* boundary a GVC exists to draw. `same-gvc` and `workload-list` cover almost
|
|
10
|
+
* every real case, and `workload-list` works cross-GVC when that is genuinely
|
|
11
|
+
* needed.
|
|
12
|
+
*/
|
|
13
|
+
export const internalFirewallScopeCheck: PostSynthCheck = {
|
|
14
|
+
id: "CPL011",
|
|
15
|
+
description: "Internal firewall should not be opened to the entire org",
|
|
16
|
+
|
|
17
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
18
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
19
|
+
|
|
20
|
+
for (const [name, entity] of entitiesOfType(ctx.entities, WORKLOAD)) {
|
|
21
|
+
const allowType = readString(entity, "spec", "firewallConfig", "internal", "inboundAllowType");
|
|
22
|
+
if (allowType !== "same-org") continue;
|
|
23
|
+
|
|
24
|
+
diagnostics.push({
|
|
25
|
+
checkId: "CPL011",
|
|
26
|
+
severity: "warning",
|
|
27
|
+
message:
|
|
28
|
+
`Workload "${name}" sets internal inboundAllowType "same-org", so every workload in the org — ` +
|
|
29
|
+
`including other GVCs — can reach it. Prefer "same-gvc", or "workload-list" with the specific ` +
|
|
30
|
+
`workloads (which also works across GVCs).`,
|
|
31
|
+
entity: name,
|
|
32
|
+
lexicon: "cpln",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return diagnostics;
|
|
37
|
+
},
|
|
38
|
+
};
|