@intentius/chant-lexicon-cedar 0.44.8
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 +190 -0
- package/dist/avp/ambient.d.ts +54 -0
- package/dist/avp/ambient.d.ts.map +1 -0
- package/dist/avp/client.d.ts +127 -0
- package/dist/avp/client.d.ts.map +1 -0
- package/dist/avp/describe-resources.d.ts +42 -0
- package/dist/avp/describe-resources.d.ts.map +1 -0
- package/dist/avp/embed.d.ts +120 -0
- package/dist/avp/embed.d.ts.map +1 -0
- package/dist/avp/live-export.d.ts +94 -0
- package/dist/avp/live-export.d.ts.map +1 -0
- package/dist/avp/ownership.d.ts +97 -0
- package/dist/avp/ownership.d.ts.map +1 -0
- package/dist/avp/statement.d.ts +22 -0
- package/dist/avp/statement.d.ts.map +1 -0
- package/dist/avp/store.d.ts +82 -0
- package/dist/avp/store.d.ts.map +1 -0
- package/dist/avp/testdata/mock-transport.d.ts +55 -0
- package/dist/avp/testdata/mock-transport.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 +26 -0
- package/dist/codegen/docs.d.ts.map +1 -0
- package/dist/codegen/emit.d.ts +85 -0
- package/dist/codegen/emit.d.ts.map +1 -0
- package/dist/codegen/generate-cli.d.ts +3 -0
- package/dist/codegen/generate-cli.d.ts.map +1 -0
- package/dist/codegen/generate.d.ts +37 -0
- package/dist/codegen/generate.d.ts.map +1 -0
- package/dist/codegen/naming.d.ts +48 -0
- package/dist/codegen/naming.d.ts.map +1 -0
- package/dist/codegen/package.d.ts +10 -0
- package/dist/codegen/package.d.ts.map +1 -0
- package/dist/composites/deny-by-default-set.d.ts +58 -0
- package/dist/composites/deny-by-default-set.d.ts.map +1 -0
- package/dist/composites/index.d.ts +12 -0
- package/dist/composites/index.d.ts.map +1 -0
- package/dist/composites/owner-can-manage.d.ts +48 -0
- package/dist/composites/owner-can-manage.d.ts.map +1 -0
- package/dist/config.d.ts +102 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/coverage.d.ts +59 -0
- package/dist/coverage.d.ts.map +1 -0
- package/dist/detect.d.ts +18 -0
- package/dist/detect.d.ts.map +1 -0
- package/dist/generated/index.d.ts +251 -0
- package/dist/generated/index.d.ts.map +1 -0
- package/dist/generated/runtime.d.ts +2 -0
- package/dist/generated/runtime.d.ts.map +1 -0
- package/dist/import/adapter.d.ts +21 -0
- package/dist/import/adapter.d.ts.map +1 -0
- package/dist/import/clause-text.d.ts +38 -0
- package/dist/import/clause-text.d.ts.map +1 -0
- package/dist/import/generator.d.ts +53 -0
- package/dist/import/generator.d.ts.map +1 -0
- package/dist/import/parser.d.ts +57 -0
- package/dist/import/parser.d.ts.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/init-templates.d.ts +32 -0
- package/dist/init-templates.d.ts.map +1 -0
- package/dist/integrity.json +22 -0
- package/dist/lint/audit-catalog.d.ts +29 -0
- package/dist/lint/audit-catalog.d.ts.map +1 -0
- package/dist/lint/post-synth/cedar-helpers.d.ts +97 -0
- package/dist/lint/post-synth/cedar-helpers.d.ts.map +1 -0
- package/dist/lint/post-synth/cedc010.d.ts +17 -0
- package/dist/lint/post-synth/cedc010.d.ts.map +1 -0
- package/dist/lint/post-synth/cedc011.d.ts +17 -0
- package/dist/lint/post-synth/cedc011.d.ts.map +1 -0
- package/dist/lint/post-synth/cedc012.d.ts +21 -0
- package/dist/lint/post-synth/cedc012.d.ts.map +1 -0
- package/dist/lint/post-synth/cedc013.d.ts +20 -0
- package/dist/lint/post-synth/cedc013.d.ts.map +1 -0
- package/dist/lint/post-synth/cedc014.d.ts +19 -0
- package/dist/lint/post-synth/cedc014.d.ts.map +1 -0
- package/dist/lint/post-synth/cede010.d.ts +34 -0
- package/dist/lint/post-synth/cede010.d.ts.map +1 -0
- package/dist/lint/post-synth/cede011.d.ts +22 -0
- package/dist/lint/post-synth/cede011.d.ts.map +1 -0
- package/dist/lint/post-synth/ceds010.d.ts +24 -0
- package/dist/lint/post-synth/ceds010.d.ts.map +1 -0
- package/dist/lint/post-synth/ceds011.d.ts +19 -0
- package/dist/lint/post-synth/ceds011.d.ts.map +1 -0
- package/dist/lint/post-synth/ceds012.d.ts +17 -0
- package/dist/lint/post-synth/ceds012.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/post-synth/wasm-helpers.d.ts +108 -0
- package/dist/lint/post-synth/wasm-helpers.d.ts.map +1 -0
- package/dist/lint/rules/index.d.ts +10 -0
- package/dist/lint/rules/index.d.ts.map +1 -0
- package/dist/lint/rules/policy-shape.d.ts +31 -0
- package/dist/lint/rules/policy-shape.d.ts.map +1 -0
- package/dist/lsp/completions.d.ts +24 -0
- package/dist/lsp/completions.d.ts.map +1 -0
- package/dist/lsp/hover.d.ts +11 -0
- package/dist/lsp/hover.d.ts.map +1 -0
- package/dist/lsp/registry.d.ts +45 -0
- package/dist/lsp/registry.d.ts.map +1 -0
- package/dist/manifest.json +8 -0
- package/dist/mcp/index.d.ts +34 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/policy-coverage.d.ts +81 -0
- package/dist/mcp/policy-coverage.d.ts.map +1 -0
- package/dist/meta.json +699 -0
- package/dist/okf/index.md +37 -0
- package/dist/okf/rules/CEDC001.md +11 -0
- package/dist/okf/rules/CEDC010.md +11 -0
- package/dist/okf/rules/CEDC011.md +15 -0
- package/dist/okf/rules/CEDC012.md +11 -0
- package/dist/okf/rules/CEDC013.md +15 -0
- package/dist/okf/rules/CEDC014.md +15 -0
- package/dist/okf/rules/CEDE010.md +15 -0
- package/dist/okf/rules/CEDE011.md +15 -0
- package/dist/okf/rules/CEDS010.md +15 -0
- package/dist/okf/rules/CEDS011.md +11 -0
- package/dist/okf/rules/CEDS012.md +15 -0
- package/dist/okf/types/AdminAction.md +14 -0
- package/dist/okf/types/Application.md +14 -0
- package/dist/okf/types/ApproveAction.md +13 -0
- package/dist/okf/types/CommentAction.md +14 -0
- package/dist/okf/types/CreateAction.md +14 -0
- package/dist/okf/types/DeleteAction.md +14 -0
- package/dist/okf/types/Document.md +18 -0
- package/dist/okf/types/Folder.md +15 -0
- package/dist/okf/types/Group.md +14 -0
- package/dist/okf/types/ListAction.md +14 -0
- package/dist/okf/types/Policy.md +29 -0
- package/dist/okf/types/ReadAction.md +14 -0
- package/dist/okf/types/ServiceAccount.md +15 -0
- package/dist/okf/types/ShareAction.md +14 -0
- package/dist/okf/types/Team.md +14 -0
- package/dist/okf/types/User.md +18 -0
- package/dist/okf/types/WriteAction.md +14 -0
- package/dist/package-cli.d.ts +3 -0
- package/dist/package-cli.d.ts.map +1 -0
- package/dist/plugin.d.ts +8 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/rules/cedar-helpers.ts +209 -0
- package/dist/rules/cedc010.ts +80 -0
- package/dist/rules/cedc011.ts +47 -0
- package/dist/rules/cedc012.ts +64 -0
- package/dist/rules/cedc013.ts +68 -0
- package/dist/rules/cedc014.ts +73 -0
- package/dist/rules/cede010.ts +89 -0
- package/dist/rules/cede011.ts +58 -0
- package/dist/rules/ceds010.ts +57 -0
- package/dist/rules/ceds011.ts +42 -0
- package/dist/rules/ceds012.ts +50 -0
- package/dist/rules/policy-shape.ts +148 -0
- package/dist/rules/wasm-helpers.ts +315 -0
- package/dist/serializer.d.ts +138 -0
- package/dist/serializer.d.ts.map +1 -0
- package/dist/spec/fetch.d.ts +71 -0
- package/dist/spec/fetch.d.ts.map +1 -0
- package/dist/spec/parse.d.ts +113 -0
- package/dist/spec/parse.d.ts.map +1 -0
- package/dist/spec/pin.d.ts +116 -0
- package/dist/spec/pin.d.ts.map +1 -0
- package/dist/spec/pinned-names.json +18 -0
- package/dist/spec/wasm.d.ts +143 -0
- package/dist/spec/wasm.d.ts.map +1 -0
- package/dist/types/index.d.ts +219 -0
- package/dist/validate-cli.d.ts +3 -0
- package/dist/validate-cli.d.ts.map +1 -0
- package/dist/validate.d.ts +25 -0
- package/dist/validate.d.ts.map +1 -0
- package/package.json +75 -0
- package/src/avp/OWNERSHIP.md +125 -0
- package/src/avp/ambient.test.ts +113 -0
- package/src/avp/ambient.ts +124 -0
- package/src/avp/client.ts +310 -0
- package/src/avp/describe-resources.test.ts +316 -0
- package/src/avp/describe-resources.ts +215 -0
- package/src/avp/embed.test.ts +114 -0
- package/src/avp/embed.ts +190 -0
- package/src/avp/live-export.test.ts +232 -0
- package/src/avp/live-export.ts +185 -0
- package/src/avp/ownership.test.ts +101 -0
- package/src/avp/ownership.ts +170 -0
- package/src/avp/statement.ts +50 -0
- package/src/avp/store.ts +152 -0
- package/src/avp/testdata/mock-transport.ts +147 -0
- package/src/codegen/docs-cli.ts +7 -0
- package/src/codegen/docs.ts +873 -0
- package/src/codegen/emit.ts +496 -0
- package/src/codegen/generate-cli.ts +18 -0
- package/src/codegen/generate.test.ts +128 -0
- package/src/codegen/generate.ts +123 -0
- package/src/codegen/naming.ts +101 -0
- package/src/codegen/package.ts +52 -0
- package/src/composites/composites.test.ts +206 -0
- package/src/composites/deny-by-default-set.ts +98 -0
- package/src/composites/index.ts +13 -0
- package/src/composites/owner-can-manage.ts +80 -0
- package/src/config-namespace.test.ts +44 -0
- package/src/config.test.ts +82 -0
- package/src/config.ts +119 -0
- package/src/coverage.test.ts +61 -0
- package/src/coverage.ts +166 -0
- package/src/detect.test.ts +64 -0
- package/src/detect.ts +59 -0
- package/src/generated/index.d.ts +219 -0
- package/src/generated/index.ts +279 -0
- package/src/generated/lexicon-cedar.json +699 -0
- package/src/generated/runtime.ts +2 -0
- package/src/import/adapter.ts +63 -0
- package/src/import/clause-text.ts +178 -0
- package/src/import/generator.test.ts +133 -0
- package/src/import/generator.ts +219 -0
- package/src/import/parser.test.ts +265 -0
- package/src/import/parser.ts +352 -0
- package/src/import/roundtrip.test.ts +127 -0
- package/src/import/testdata/full.cedar +36 -0
- package/src/import/testdata/full.cedar.json +238 -0
- package/src/import/testdata/realistic.cedar +35 -0
- package/src/import/testdata/simple.cedar +6 -0
- package/src/index.ts +94 -0
- package/src/init-templates.test.ts +158 -0
- package/src/init-templates.ts +358 -0
- package/src/lint/audit-catalog.ts +130 -0
- package/src/lint/post-synth/cedar-helpers.ts +209 -0
- package/src/lint/post-synth/cedc010.ts +80 -0
- package/src/lint/post-synth/cedc011.ts +47 -0
- package/src/lint/post-synth/cedc012.ts +64 -0
- package/src/lint/post-synth/cedc013.ts +68 -0
- package/src/lint/post-synth/cedc014.ts +73 -0
- package/src/lint/post-synth/cede010.ts +89 -0
- package/src/lint/post-synth/cede011.ts +58 -0
- package/src/lint/post-synth/ceds010.ts +57 -0
- package/src/lint/post-synth/ceds011.ts +42 -0
- package/src/lint/post-synth/ceds012.ts +50 -0
- package/src/lint/post-synth/index.ts +25 -0
- package/src/lint/post-synth/post-synth.test.ts +474 -0
- package/src/lint/post-synth/wasm-helpers.ts +315 -0
- package/src/lint/rules/index.ts +12 -0
- package/src/lint/rules/policy-shape.test.ts +90 -0
- package/src/lint/rules/policy-shape.ts +148 -0
- package/src/lsp/completions.test.ts +111 -0
- package/src/lsp/completions.ts +55 -0
- package/src/lsp/hover.test.ts +82 -0
- package/src/lsp/hover.ts +75 -0
- package/src/lsp/registry.ts +74 -0
- package/src/mcp/index.ts +103 -0
- package/src/mcp/policy-coverage.test.ts +176 -0
- package/src/mcp/policy-coverage.ts +205 -0
- package/src/package-cli.ts +22 -0
- package/src/plugin.test.ts +115 -0
- package/src/plugin.ts +312 -0
- package/src/serializer.test.ts +502 -0
- package/src/serializer.ts +335 -0
- package/src/skills/chant-cedar-authoring.md +180 -0
- package/src/skills/chant-cedar-avp-embedding.md +125 -0
- package/src/skills/chant-cedar-meta-policy.md +119 -0
- package/src/spec/default-schema.cedarschema +108 -0
- package/src/spec/fetch.ts +107 -0
- package/src/spec/parse.test.ts +123 -0
- package/src/spec/parse.ts +253 -0
- package/src/spec/pin.test.ts +88 -0
- package/src/spec/pin.ts +243 -0
- package/src/spec/pinned-names.json +18 -0
- package/src/spec/wasm.ts +283 -0
- package/src/validate-cli.ts +8 -0
- package/src/validate.ts +85 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared reading helpers for the cedar post-synth checks.
|
|
3
|
+
*
|
|
4
|
+
* Every check in this directory judges the same artifact: the JSON policy-set
|
|
5
|
+
* envelope the serializer writes beside the `.cedar` text
|
|
6
|
+
* (`policies.cedar.json` — see `../../serializer.ts`). The text and the JSON
|
|
7
|
+
* are rendered from one in-memory model, so a finding against the JSON is a
|
|
8
|
+
* finding against both; the JSON is the one with structure worth walking.
|
|
9
|
+
*
|
|
10
|
+
* Excluded from check auto-discovery by the "helper" filename filter
|
|
11
|
+
* (`listRuleFiles` in core's `lint/discover.ts`).
|
|
12
|
+
*/
|
|
13
|
+
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
14
|
+
|
|
15
|
+
/** Files carrying a Cedar JSON policy set end with this. */
|
|
16
|
+
export const CEDAR_JSON_SUFFIX = ".cedar.json";
|
|
17
|
+
|
|
18
|
+
/** The lexicon name stamped on every diagnostic from this directory. */
|
|
19
|
+
export const CEDAR_LEXICON = "cedar";
|
|
20
|
+
|
|
21
|
+
// ── The shape of what we read ─────────────────────────────────────
|
|
22
|
+
//
|
|
23
|
+
// Deliberately loose: these checks also run over a policy set that chant did
|
|
24
|
+
// not write (`chant audit` over a checked-in `policies.cedar.json`), so every
|
|
25
|
+
// field is `unknown` until a narrowing helper has looked at it.
|
|
26
|
+
|
|
27
|
+
/** One scope position of a policy in Cedar's JSON policy format. */
|
|
28
|
+
export interface CedarScopeJson {
|
|
29
|
+
op?: unknown;
|
|
30
|
+
entity?: unknown;
|
|
31
|
+
entities?: unknown;
|
|
32
|
+
entity_type?: unknown;
|
|
33
|
+
in?: unknown;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** One `when`/`unless` clause. */
|
|
37
|
+
export interface CedarConditionJson {
|
|
38
|
+
kind?: unknown;
|
|
39
|
+
body?: unknown;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** A single policy in Cedar's JSON policy format. */
|
|
43
|
+
export interface CedarPolicyJson {
|
|
44
|
+
effect?: unknown;
|
|
45
|
+
principal?: unknown;
|
|
46
|
+
action?: unknown;
|
|
47
|
+
resource?: unknown;
|
|
48
|
+
conditions?: unknown;
|
|
49
|
+
annotations?: unknown;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The policy-set envelope: what `policies.cedar.json` holds. */
|
|
53
|
+
export interface CedarPolicySetDoc {
|
|
54
|
+
staticPolicies?: unknown;
|
|
55
|
+
templates?: unknown;
|
|
56
|
+
templateLinks?: unknown;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** One `[key, policy]` pair out of a policy set, with its origin. */
|
|
60
|
+
export interface CedarPolicyEntry {
|
|
61
|
+
/** The key under `staticPolicies` — the id Cedar will report diagnostics against. */
|
|
62
|
+
key: string;
|
|
63
|
+
policy: CedarPolicyJson;
|
|
64
|
+
/** True when the entry came from `templates` rather than `staticPolicies`. */
|
|
65
|
+
template: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** A policy set found in the build output, with everything needed to name it. */
|
|
69
|
+
export interface CedarPolicySet {
|
|
70
|
+
/** The lexicon output it came from. */
|
|
71
|
+
lexicon: string;
|
|
72
|
+
/** A human-readable origin: the filename, or the lexicon name for a bare string output. */
|
|
73
|
+
source: string;
|
|
74
|
+
/** The raw JSON text, for handing straight to the wasm. */
|
|
75
|
+
raw: string;
|
|
76
|
+
/** Parsed envelope, or undefined when `raw` is not JSON at all. */
|
|
77
|
+
doc?: CedarPolicySetDoc;
|
|
78
|
+
/** The JSON parse error, when there is one. */
|
|
79
|
+
parseError?: string;
|
|
80
|
+
/** Static policies and templates, flattened in document order. */
|
|
81
|
+
entries: CedarPolicyEntry[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ── Narrowing ─────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
export function isRecord(value: unknown): value is Record<string, unknown> {
|
|
87
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** `permit` / `forbid` / whatever else the document claims. */
|
|
91
|
+
export function effectOf(policy: CedarPolicyJson): string | undefined {
|
|
92
|
+
return typeof policy.effect === "string" ? policy.effect : undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The three scope positions, in Cedar's own order. */
|
|
96
|
+
export function scopesOf(policy: CedarPolicyJson): Array<{ variable: string; scope: CedarScopeJson }> {
|
|
97
|
+
const out: Array<{ variable: string; scope: CedarScopeJson }> = [];
|
|
98
|
+
for (const variable of ["principal", "action", "resource"] as const) {
|
|
99
|
+
const scope = policy[variable];
|
|
100
|
+
if (isRecord(scope)) out.push({ variable, scope: scope as CedarScopeJson });
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** True when a scope position is Cedar's unconstrained "any" (`op: "All"`). */
|
|
106
|
+
export function scopeIsAll(scope: unknown): boolean {
|
|
107
|
+
return isRecord(scope) && scope.op === "All";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Every `when`/`unless` clause, or `[]` when the policy is unconditioned. */
|
|
111
|
+
export function conditionsOf(policy: CedarPolicyJson): CedarConditionJson[] {
|
|
112
|
+
if (!Array.isArray(policy.conditions)) return [];
|
|
113
|
+
return policy.conditions.filter(isRecord) as CedarConditionJson[];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** The policy's annotation map, narrowed to the string-valued entries Cedar allows. */
|
|
117
|
+
export function annotationsOf(policy: CedarPolicyJson): Record<string, unknown> {
|
|
118
|
+
return isRecord(policy.annotations) ? policy.annotations : {};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** The `@id` annotation, when it is a string. */
|
|
122
|
+
export function annotatedId(policy: CedarPolicyJson): string | undefined {
|
|
123
|
+
const id = annotationsOf(policy).id;
|
|
124
|
+
return typeof id === "string" ? id : undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Whether a build's `env` (from `--env` or the project's `ownership.env`, see
|
|
129
|
+
* `PostSynthContext#env`) is production-like, for the checks whose severity is
|
|
130
|
+
* env-gated. Same seam and same vocabulary as the aws lexicon's
|
|
131
|
+
* `isFullTierEnv`: an unset or unrecognized env is treated as non-production,
|
|
132
|
+
* so a project that never passes `--env` is never unexpectedly hard-failed.
|
|
133
|
+
*/
|
|
134
|
+
export function isProdLikeEnv(env: string | undefined): boolean {
|
|
135
|
+
return env === "prod" || env === "production" || env === "full";
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── Reading policy sets out of the build output ───────────────────
|
|
139
|
+
|
|
140
|
+
/** Every candidate policy-set document in the build, parsed or not. */
|
|
141
|
+
export function policySets(ctx: PostSynthContext): CedarPolicySet[] {
|
|
142
|
+
const sets: CedarPolicySet[] = [];
|
|
143
|
+
|
|
144
|
+
for (const [lexicon, output] of ctx.outputs) {
|
|
145
|
+
if (typeof output === "string") {
|
|
146
|
+
// A bare string output. `createPostSynthContext` and `chant audit` over a
|
|
147
|
+
// standalone file both land here; only JSON is a policy set, `.cedar`
|
|
148
|
+
// text is the human-facing twin and carries nothing the JSON does not.
|
|
149
|
+
//
|
|
150
|
+
// Every lexicon's output shares this map, and several of them are JSON,
|
|
151
|
+
// so "is it JSON" is not enough to claim one: take it when it came from
|
|
152
|
+
// the cedar output (where even unparseable content is a cedar finding —
|
|
153
|
+
// CEDC010 reports it) or when it is unmistakably a policy-set envelope.
|
|
154
|
+
if (!looksLikeJson(output)) continue;
|
|
155
|
+
const set = readSet(lexicon, lexicon, output);
|
|
156
|
+
if (lexicon === CEDAR_LEXICON || isPolicySetEnvelope(set.doc)) sets.push(set);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
for (const [filename, content] of Object.entries(output.files ?? {})) {
|
|
160
|
+
if (!filename.endsWith(CEDAR_JSON_SUFFIX)) continue;
|
|
161
|
+
if (typeof content !== "string") continue;
|
|
162
|
+
sets.push(readSet(lexicon, filename, content));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return sets;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** The subset of {@link policySets} that parsed as JSON. */
|
|
170
|
+
export function parsedPolicySets(ctx: PostSynthContext): CedarPolicySet[] {
|
|
171
|
+
return policySets(ctx).filter((set) => set.doc !== undefined);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function looksLikeJson(text: string): boolean {
|
|
175
|
+
return text.trimStart().startsWith("{");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** The `PolicySet` envelope has one of these two keys; nothing else does. */
|
|
179
|
+
function isPolicySetEnvelope(doc: CedarPolicySetDoc | undefined): boolean {
|
|
180
|
+
return doc !== undefined && (isRecord(doc.staticPolicies) || isRecord(doc.templates));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function readSet(lexicon: string, source: string, raw: string): CedarPolicySet {
|
|
184
|
+
let doc: CedarPolicySetDoc | undefined;
|
|
185
|
+
let parseError: string | undefined;
|
|
186
|
+
try {
|
|
187
|
+
const parsed: unknown = JSON.parse(raw);
|
|
188
|
+
if (isRecord(parsed)) doc = parsed as CedarPolicySetDoc;
|
|
189
|
+
else parseError = "policy set is not a JSON object";
|
|
190
|
+
} catch (err) {
|
|
191
|
+
parseError = err instanceof Error ? err.message : String(err);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { lexicon, source, raw, doc, parseError, entries: doc ? flatten(doc) : [] };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function flatten(doc: CedarPolicySetDoc): CedarPolicyEntry[] {
|
|
198
|
+
const entries: CedarPolicyEntry[] = [];
|
|
199
|
+
for (const [group, template] of [
|
|
200
|
+
[doc.staticPolicies, false],
|
|
201
|
+
[doc.templates, true],
|
|
202
|
+
] as const) {
|
|
203
|
+
if (!isRecord(group)) continue;
|
|
204
|
+
for (const [key, policy] of Object.entries(group)) {
|
|
205
|
+
if (isRecord(policy)) entries.push({ key, policy: policy as CedarPolicyJson, template });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return entries;
|
|
209
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CEDC010: the emitted policy set must parse as Cedar
|
|
3
|
+
*
|
|
4
|
+
* The floor every other check stands on. `policies.cedar.json` is Cedar's own
|
|
5
|
+
* JSON policy format inside the `PolicySet` envelope the wasm accepts, which
|
|
6
|
+
* means Cedar's own deserializer is the authority on whether it is well formed
|
|
7
|
+
* — not a hand-written shape test. A set that does not parse cannot be loaded
|
|
8
|
+
* by any evaluator (AVP, cedar-agent, an embedded `cedar-wasm`), so this is an
|
|
9
|
+
* error everywhere, in every environment.
|
|
10
|
+
*
|
|
11
|
+
* Two failure modes reach here: the file is not JSON at all, and the JSON is
|
|
12
|
+
* not a Cedar policy set. Both are reported against the file, since neither
|
|
13
|
+
* has a single implicated policy.
|
|
14
|
+
*/
|
|
15
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
16
|
+
import { CEDAR_LEXICON, policySets } from "./cedar-helpers";
|
|
17
|
+
import { loadWasm, normalizePolicySet, parsePolicySet, wasmLoadError } from "./wasm-helpers";
|
|
18
|
+
|
|
19
|
+
export const cedc010: PostSynthCheck = {
|
|
20
|
+
id: "CEDC010",
|
|
21
|
+
description: "The emitted Cedar policy set must parse as Cedar (checkParsePolicySet)",
|
|
22
|
+
|
|
23
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
24
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
25
|
+
const sets = policySets(ctx);
|
|
26
|
+
if (sets.length === 0) return diagnostics;
|
|
27
|
+
|
|
28
|
+
const wasm = loadWasm();
|
|
29
|
+
if (!wasm) {
|
|
30
|
+
// The only place the missing-validator condition is reported. The other
|
|
31
|
+
// wasm-backed checks stay quiet rather than repeating it four times.
|
|
32
|
+
return [
|
|
33
|
+
{
|
|
34
|
+
checkId: "CEDC010",
|
|
35
|
+
severity: "warning",
|
|
36
|
+
message: `Cedar policies were emitted but @cedar-policy/cedar-wasm could not be loaded, so none of them were parsed or validated: ${wasmLoadError()}`,
|
|
37
|
+
lexicon: CEDAR_LEXICON,
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const set of sets) {
|
|
43
|
+
if (set.doc === undefined) {
|
|
44
|
+
diagnostics.push({
|
|
45
|
+
checkId: "CEDC010",
|
|
46
|
+
severity: "error",
|
|
47
|
+
message: `Cedar policy set "${set.source}" is not readable as JSON: ${set.parseError}`,
|
|
48
|
+
entity: set.source,
|
|
49
|
+
lexicon: set.lexicon,
|
|
50
|
+
});
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const { policySet } = normalizePolicySet(wasm, set);
|
|
55
|
+
const parsed = parsePolicySet(wasm, policySet);
|
|
56
|
+
if (!parsed.ok) {
|
|
57
|
+
diagnostics.push({
|
|
58
|
+
checkId: "CEDC010",
|
|
59
|
+
severity: "error",
|
|
60
|
+
message: `Cedar policy set "${set.source}" could not be parsed: ${parsed.message}`,
|
|
61
|
+
entity: set.source,
|
|
62
|
+
lexicon: set.lexicon,
|
|
63
|
+
});
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (const message of parsed.value) {
|
|
68
|
+
diagnostics.push({
|
|
69
|
+
checkId: "CEDC010",
|
|
70
|
+
severity: "error",
|
|
71
|
+
message: `Cedar policy set "${set.source}" does not parse as Cedar: ${message}`,
|
|
72
|
+
entity: set.source,
|
|
73
|
+
lexicon: set.lexicon,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return diagnostics;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CEDC011: every `when`/`unless` condition expression must parse
|
|
3
|
+
*
|
|
4
|
+
* A policy's guard is the part that does the work — `when { context.mfa }`,
|
|
5
|
+
* `unless { resource.public }` — and until schema-driven codegen (#1650) makes
|
|
6
|
+
* those typed trees, the authored model carries them as Cedar expression
|
|
7
|
+
* *source*. Source that does not parse turns into a policy no evaluator will
|
|
8
|
+
* load, and the failure surfaces a long way from the typo: the whole policy set
|
|
9
|
+
* is rejected, naming a byte offset in a generated file.
|
|
10
|
+
*
|
|
11
|
+
* So the clause is parsed on its own and reported on its own, with the
|
|
12
|
+
* expression text and Cedar's own message (which carries a "did you mean"
|
|
13
|
+
* suggestion often enough to be worth passing through).
|
|
14
|
+
*/
|
|
15
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
16
|
+
import { CEDAR_LEXICON, parsedPolicySets } from "./cedar-helpers";
|
|
17
|
+
import { loadWasm, normalizePolicySet } from "./wasm-helpers";
|
|
18
|
+
|
|
19
|
+
export const cedc011: PostSynthCheck = {
|
|
20
|
+
id: "CEDC011",
|
|
21
|
+
description: "Every when/unless condition expression in an emitted policy must parse as a Cedar expression",
|
|
22
|
+
|
|
23
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
24
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
25
|
+
const sets = parsedPolicySets(ctx);
|
|
26
|
+
if (sets.length === 0) return diagnostics;
|
|
27
|
+
|
|
28
|
+
// A missing validator is CEDC010's finding to report, not this one's.
|
|
29
|
+
const wasm = loadWasm();
|
|
30
|
+
if (!wasm) return diagnostics;
|
|
31
|
+
|
|
32
|
+
for (const set of sets) {
|
|
33
|
+
const { expressionFailures } = normalizePolicySet(wasm, set);
|
|
34
|
+
for (const failure of expressionFailures) {
|
|
35
|
+
diagnostics.push({
|
|
36
|
+
checkId: "CEDC011",
|
|
37
|
+
severity: "error",
|
|
38
|
+
message: `Policy "${failure.key}" has a ${failure.kind} clause whose expression does not parse: ${failure.expression} — ${failure.message}`,
|
|
39
|
+
entity: failure.key,
|
|
40
|
+
lexicon: set.lexicon || CEDAR_LEXICON,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return diagnostics;
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CEDC012: no two policies may claim the same id
|
|
3
|
+
*
|
|
4
|
+
* A Cedar policy id is the handle everything downstream uses: the validator
|
|
5
|
+
* reports against it, AVP's `CreatePolicy` stores it, a drift check diffs on
|
|
6
|
+
* it. chant derives one from the logical name (`allowAdminRead` →
|
|
7
|
+
* `allow-admin-read`) unless the author sets `annotations.id` explicitly, and
|
|
8
|
+
* two policies can land on the same string either way — a rename that
|
|
9
|
+
* kebab-cases to something already taken, or the same explicit `@id` pasted
|
|
10
|
+
* twice.
|
|
11
|
+
*
|
|
12
|
+
* The consequence is silent. The `.cedar` text gets two policies carrying
|
|
13
|
+
* `@id("same")`, while the JSON envelope is keyed by id and keeps only the last
|
|
14
|
+
* one — a policy that exists in the artifact a human reviews and not in the one
|
|
15
|
+
* a machine loads. Ids are collected from every policy set in the build, from
|
|
16
|
+
* both the envelope key and the `@id` annotation, so a collision is caught
|
|
17
|
+
* whichever of the two forms it takes.
|
|
18
|
+
*/
|
|
19
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
20
|
+
import { annotatedId, parsedPolicySets } from "./cedar-helpers";
|
|
21
|
+
|
|
22
|
+
interface Claim {
|
|
23
|
+
lexicon: string;
|
|
24
|
+
source: string;
|
|
25
|
+
/** The envelope key the claim was made under. */
|
|
26
|
+
key: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const cedc012: PostSynthCheck = {
|
|
30
|
+
id: "CEDC012",
|
|
31
|
+
description: "No two emitted Cedar policies may claim the same policy id",
|
|
32
|
+
|
|
33
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
34
|
+
const claims = new Map<string, Claim[]>();
|
|
35
|
+
|
|
36
|
+
for (const set of parsedPolicySets(ctx)) {
|
|
37
|
+
for (const entry of set.entries) {
|
|
38
|
+
// The key and the `@id` annotation are two ways of claiming the same
|
|
39
|
+
// id; a policy claiming both only counts once.
|
|
40
|
+
const ids = new Set([entry.key, annotatedId(entry.policy)].filter((id): id is string => !!id));
|
|
41
|
+
for (const id of ids) {
|
|
42
|
+
const existing = claims.get(id) ?? [];
|
|
43
|
+
existing.push({ lexicon: set.lexicon, source: set.source, key: entry.key });
|
|
44
|
+
claims.set(id, existing);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
50
|
+
for (const [id, holders] of [...claims].sort(([a], [b]) => a.localeCompare(b))) {
|
|
51
|
+
if (holders.length < 2) continue;
|
|
52
|
+
const where = holders.map((h) => `${h.source}:${h.key}`).join(", ");
|
|
53
|
+
diagnostics.push({
|
|
54
|
+
checkId: "CEDC012",
|
|
55
|
+
severity: "error",
|
|
56
|
+
message: `Cedar policy id "${id}" is claimed by ${holders.length} policies (${where}). Ids must be unique across the policy set — rename one of the declarations or give it an explicit, distinct @id annotation.`,
|
|
57
|
+
entity: id,
|
|
58
|
+
lexicon: holders[0].lexicon,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return diagnostics;
|
|
63
|
+
},
|
|
64
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CEDC013: annotation hygiene on an emitted policy
|
|
3
|
+
*
|
|
4
|
+
* Annotations are Cedar's only metadata channel — no comments survive the JSON
|
|
5
|
+
* grammar, and `policyToText` drops them — so `@id`, `@doc`, and friends are
|
|
6
|
+
* the whole audit trail a policy carries into AVP or a review. Three things
|
|
7
|
+
* make that trail useless:
|
|
8
|
+
*
|
|
9
|
+
* - an `@id` annotation that disagrees with the key the policy is filed under,
|
|
10
|
+
* so the text artifact and the JSON artifact name the same policy differently;
|
|
11
|
+
* - an annotation whose value is empty, which reads as documented and is not;
|
|
12
|
+
* - an annotation whose value is not a string, which Cedar's `Annotations`
|
|
13
|
+
* (`Record<string, string>`) does not admit.
|
|
14
|
+
*
|
|
15
|
+
* A *missing* `@id` is not a finding: chant derives one from the logical name,
|
|
16
|
+
* which is the normal case and the one the serializer is built around.
|
|
17
|
+
*/
|
|
18
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
19
|
+
import { annotationsOf, parsedPolicySets } from "./cedar-helpers";
|
|
20
|
+
|
|
21
|
+
export const cedc013: PostSynthCheck = {
|
|
22
|
+
id: "CEDC013",
|
|
23
|
+
description: "An emitted policy's @id annotation must match its policy id, and annotation values must be non-empty strings",
|
|
24
|
+
|
|
25
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
26
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
27
|
+
|
|
28
|
+
for (const set of parsedPolicySets(ctx)) {
|
|
29
|
+
for (const entry of set.entries) {
|
|
30
|
+
const annotations = annotationsOf(entry.policy);
|
|
31
|
+
|
|
32
|
+
for (const [key, value] of Object.entries(annotations)) {
|
|
33
|
+
if (typeof value !== "string") {
|
|
34
|
+
diagnostics.push({
|
|
35
|
+
checkId: "CEDC013",
|
|
36
|
+
severity: "warning",
|
|
37
|
+
message: `Policy "${entry.key}" has a non-string @${key} annotation (${JSON.stringify(value)}). Cedar annotations are strings.`,
|
|
38
|
+
entity: entry.key,
|
|
39
|
+
lexicon: set.lexicon,
|
|
40
|
+
});
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (value.trim() === "") {
|
|
44
|
+
diagnostics.push({
|
|
45
|
+
checkId: "CEDC013",
|
|
46
|
+
severity: "warning",
|
|
47
|
+
message: `Policy "${entry.key}" has an empty @${key} annotation. Give it a value or drop it — an empty annotation reads as documentation and carries none.`,
|
|
48
|
+
entity: entry.key,
|
|
49
|
+
lexicon: set.lexicon,
|
|
50
|
+
});
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (key === "id" && value !== entry.key) {
|
|
54
|
+
diagnostics.push({
|
|
55
|
+
checkId: "CEDC013",
|
|
56
|
+
severity: "warning",
|
|
57
|
+
message: `Policy filed under id "${entry.key}" annotates @id("${value}"). The .cedar text and the JSON policy set would name this policy differently — make the annotation match, or remove it and let chant derive the id.`,
|
|
58
|
+
entity: entry.key,
|
|
59
|
+
lexicon: set.lexicon,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return diagnostics;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CEDC014: a scope constraint's entity reference must be a real entity UID
|
|
3
|
+
*
|
|
4
|
+
* Cedar names an entity as `Namespace::Type::"id"` — a type path and a quoted
|
|
5
|
+
* id. chant's serializer splits an authored reference on that grammar, and a
|
|
6
|
+
* reference that does not match it degrades rather than fails: the whole string
|
|
7
|
+
* becomes the type and the id becomes empty. `principal: { eq: "alice" }`
|
|
8
|
+
* therefore emits `{"op":"==","entity":{"type":"alice","id":""}}` — a policy
|
|
9
|
+
* that parses, validates against a schema that happens to define no such type
|
|
10
|
+
* only as an "unrecognized entity type", and silently matches nothing at
|
|
11
|
+
* runtime if it ever does.
|
|
12
|
+
*
|
|
13
|
+
* An empty entity id is the fingerprint of that degradation, and there is no
|
|
14
|
+
* legitimate reason to author one, so it is flagged wherever it appears in a
|
|
15
|
+
* scope: `== E`, `in E`, `in [E, …]`, and the `in` of an `is T in E`.
|
|
16
|
+
*/
|
|
17
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
18
|
+
import { isRecord, parsedPolicySets, scopesOf, type CedarScopeJson } from "./cedar-helpers";
|
|
19
|
+
|
|
20
|
+
/** Every entity reference a scope position carries, in document order. */
|
|
21
|
+
function scopeEntities(scope: CedarScopeJson): unknown[] {
|
|
22
|
+
const found: unknown[] = [];
|
|
23
|
+
const collect = (holder: unknown): void => {
|
|
24
|
+
if (!isRecord(holder)) return;
|
|
25
|
+
if (holder.entity !== undefined) found.push(holder.entity);
|
|
26
|
+
if (Array.isArray(holder.entities)) found.push(...holder.entities);
|
|
27
|
+
};
|
|
28
|
+
collect(scope);
|
|
29
|
+
// `is T in E` nests the `in` constraint under the `is`.
|
|
30
|
+
collect(scope.in);
|
|
31
|
+
return found;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const cedc014: PostSynthCheck = {
|
|
35
|
+
id: "CEDC014",
|
|
36
|
+
description: "An entity reference in a policy scope must be a well-formed Namespace::Type::\"id\" UID",
|
|
37
|
+
|
|
38
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
39
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
40
|
+
|
|
41
|
+
for (const set of parsedPolicySets(ctx)) {
|
|
42
|
+
for (const entry of set.entries) {
|
|
43
|
+
for (const { variable, scope } of scopesOf(entry.policy)) {
|
|
44
|
+
for (const ref of scopeEntities(scope)) {
|
|
45
|
+
if (!isRecord(ref)) {
|
|
46
|
+
diagnostics.push({
|
|
47
|
+
checkId: "CEDC014",
|
|
48
|
+
severity: "error",
|
|
49
|
+
message: `Policy "${entry.key}" has a ${variable} constraint whose entity reference is not an entity UID (${JSON.stringify(ref)}).`,
|
|
50
|
+
entity: entry.key,
|
|
51
|
+
lexicon: set.lexicon,
|
|
52
|
+
});
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const type = typeof ref.type === "string" ? ref.type : "";
|
|
56
|
+
const id = typeof ref.id === "string" ? ref.id : undefined;
|
|
57
|
+
if (type === "" || id === undefined || id === "") {
|
|
58
|
+
diagnostics.push({
|
|
59
|
+
checkId: "CEDC014",
|
|
60
|
+
severity: "error",
|
|
61
|
+
message: `Policy "${entry.key}" constrains ${variable} against "${type}" with no entity id. Write the reference as a full UID — Namespace::Type::"id" — so it resolves to an entity rather than to a bare type name.`,
|
|
62
|
+
entity: entry.key,
|
|
63
|
+
lexicon: set.lexicon,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return diagnostics;
|
|
72
|
+
},
|
|
73
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CEDE010: every emitted policy set validates clean against its schema
|
|
3
|
+
*
|
|
4
|
+
* The epic's "native tool is the test" clause: not "the text is well formed"
|
|
5
|
+
* but "Cedar's own validator, run in-process against the project's schema, has
|
|
6
|
+
* nothing to say about it." This is what catches the second meta-policy wall —
|
|
7
|
+
* a policy naming an entity type or an action the schema does not define — and
|
|
8
|
+
* it catches it everywhere, in every environment, because such a policy can
|
|
9
|
+
* never grant anything.
|
|
10
|
+
*
|
|
11
|
+
* Two things about `validate()` decide how this is written, both measured
|
|
12
|
+
* against 4.12.0 (chant #1648):
|
|
13
|
+
*
|
|
14
|
+
* - `type: "success"` means validation *ran*, not that it passed. The findings
|
|
15
|
+
* are in `validationErrors`, and a check written as `if (answer.type ===
|
|
16
|
+
* "failure")` passes every type error in the corpus. `validatePolicySet`
|
|
17
|
+
* asserts `success && validationErrors.length === 0`.
|
|
18
|
+
* - `validationErrors` comes back in a different order almost every call — the
|
|
19
|
+
* validator parallelizes across policies and does not re-sort. Forty
|
|
20
|
+
* identical calls produced six orderings. `validatePolicySet` sorts by
|
|
21
|
+
* `policyId` then message, so this check's output is stable enough to assert
|
|
22
|
+
* on.
|
|
23
|
+
*
|
|
24
|
+
* When the build emits no schema there is nothing to validate against, and
|
|
25
|
+
* `validate()` cannot be called at all — it has no optional-schema mode, and an
|
|
26
|
+
* empty schema is legal Cedar that reports every policy as inapplicable, which
|
|
27
|
+
* would be worse than useless. Schema-driven codegen is #1650; until a schema
|
|
28
|
+
* is emitted beside the policies, this check says so once, as an advisory,
|
|
29
|
+
* rather than passing silently and claiming a guarantee it did not check.
|
|
30
|
+
* CEDC010's parse gate still applies in that state.
|
|
31
|
+
*/
|
|
32
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
33
|
+
import { parsedPolicySets } from "./cedar-helpers";
|
|
34
|
+
import { findSchema, loadWasm, normalizePolicySet, validatePolicySet } from "./wasm-helpers";
|
|
35
|
+
|
|
36
|
+
export const cede010: PostSynthCheck = {
|
|
37
|
+
id: "CEDE010",
|
|
38
|
+
description: "Every emitted Cedar policy set validates clean against the project schema (cedar-wasm validate)",
|
|
39
|
+
|
|
40
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
41
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
42
|
+
const sets = parsedPolicySets(ctx);
|
|
43
|
+
if (sets.length === 0) return diagnostics;
|
|
44
|
+
|
|
45
|
+
// A missing validator is CEDC010's finding to report, not this one's.
|
|
46
|
+
const wasm = loadWasm();
|
|
47
|
+
if (!wasm) return diagnostics;
|
|
48
|
+
|
|
49
|
+
const schema = findSchema(ctx);
|
|
50
|
+
if (!schema) {
|
|
51
|
+
return [
|
|
52
|
+
{
|
|
53
|
+
checkId: "CEDE010",
|
|
54
|
+
severity: "info",
|
|
55
|
+
message: `No Cedar schema was emitted with the ${sets.length} policy set(s) in this build, so they were checked for parseability only — nothing verified that the entity types, actions, and attributes they name exist. Emit a .cedarschema (or .cedarschema.json) alongside the policies to turn this into real validation.`,
|
|
56
|
+
lexicon: sets[0].lexicon,
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const set of sets) {
|
|
62
|
+
const { policySet } = normalizePolicySet(wasm, set);
|
|
63
|
+
const outcome = validatePolicySet(wasm, policySet, schema.schema);
|
|
64
|
+
|
|
65
|
+
if (outcome.failure) {
|
|
66
|
+
diagnostics.push({
|
|
67
|
+
checkId: "CEDE010",
|
|
68
|
+
severity: "error",
|
|
69
|
+
message: `Cedar policy set "${set.source}" could not be validated against "${schema.source}": ${outcome.failure}`,
|
|
70
|
+
entity: set.source,
|
|
71
|
+
lexicon: set.lexicon,
|
|
72
|
+
});
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
for (const finding of outcome.errors) {
|
|
77
|
+
diagnostics.push({
|
|
78
|
+
checkId: "CEDE010",
|
|
79
|
+
severity: "error",
|
|
80
|
+
message: `Policy "${finding.policyId}" fails Cedar validation against "${schema.source}": ${finding.message}`,
|
|
81
|
+
entity: finding.policyId,
|
|
82
|
+
lexicon: set.lexicon,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return diagnostics;
|
|
88
|
+
},
|
|
89
|
+
};
|