@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,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cedar lexicon serializer.
|
|
3
|
+
*
|
|
4
|
+
* Emits two views of the same policy set:
|
|
5
|
+
*
|
|
6
|
+
* - `.cedar` policy text — the primary output, and the surface every Cedar
|
|
7
|
+
* evaluator reads (AVP, cedar-agent, an embedded `cedar-wasm`).
|
|
8
|
+
* - the Cedar JSON policy format — a second file, written alongside, and the
|
|
9
|
+
* parse source for import (#1653). It is produced by handing the emitted
|
|
10
|
+
* `.cedar` text back to `cedar-wasm`, so what lands on disk is whatever
|
|
11
|
+
* Cedar itself says that text means. Deriving it from the in-memory model
|
|
12
|
+
* instead is what shipped first, and it wrote `when` bodies as
|
|
13
|
+
* `{ "__expr": "<text>" }` — a key Cedar's JSON grammar does not have, which
|
|
14
|
+
* every Cedar tool rejects with `unknown variant '__expr'`.
|
|
15
|
+
*
|
|
16
|
+
* The entity model this serializer reads is hand-shaped for now: one
|
|
17
|
+
* `Cedar::Policy` entity type whose `props` carry an effect, three scope
|
|
18
|
+
* constraints, and optional `when`/`unless` guards. Schema-driven codegen
|
|
19
|
+
* (#1650) generates typed classes onto exactly this shape, so nothing here
|
|
20
|
+
* changes when it lands — the guards become typed expressions rather than
|
|
21
|
+
* the opaque Cedar-expression strings they are today.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { Declarable } from "@intentius/chant/declarable";
|
|
25
|
+
import { isPropertyDeclarable, isResourceDeclarable } from "@intentius/chant/declarable";
|
|
26
|
+
import type { Serializer, SerializerResult } from "@intentius/chant/serializer";
|
|
27
|
+
import type { LexiconOutput } from "@intentius/chant/lexicon-output";
|
|
28
|
+
import { walkValue, type SerializerVisitor } from "@intentius/chant/serializer-walker";
|
|
29
|
+
import { policyToJson, splitPolicySet, templateToJson, type PolicyJson } from "./spec/wasm";
|
|
30
|
+
|
|
31
|
+
// ── The policy entity model ───────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
/** The `entityType` this serializer reads. */
|
|
34
|
+
export const CEDAR_POLICY_TYPE = "Cedar::Policy";
|
|
35
|
+
|
|
36
|
+
/** Filename of the JSON policy-set companion to the primary `.cedar` output. */
|
|
37
|
+
export const CEDAR_JSON_FILENAME = "policies.cedar.json";
|
|
38
|
+
|
|
39
|
+
/** A Cedar policy either permits or forbids; there is no third effect. */
|
|
40
|
+
export type CedarEffect = "permit" | "forbid";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* One scope position (`principal`, `action`, `resource`).
|
|
44
|
+
*
|
|
45
|
+
* `{}` is "any" — the unconstrained form Cedar writes as a bare variable.
|
|
46
|
+
* The constrained forms mirror the grammar: `== E`, `in E`, `in [E, …]`,
|
|
47
|
+
* `is T`, and `is T in E`.
|
|
48
|
+
*/
|
|
49
|
+
export type CedarScope =
|
|
50
|
+
| Record<string, never>
|
|
51
|
+
| { eq: string; in?: never; is?: never }
|
|
52
|
+
| { in: string | string[]; eq?: never; is?: never }
|
|
53
|
+
| { is: string; in?: string | string[]; eq?: never };
|
|
54
|
+
|
|
55
|
+
/** The `props` of a `Cedar::Policy` entity. */
|
|
56
|
+
export interface CedarPolicyProps {
|
|
57
|
+
/** Defaults to `permit` when omitted. */
|
|
58
|
+
effect?: CedarEffect;
|
|
59
|
+
/** Defaults to unconstrained (`{}`) when omitted. */
|
|
60
|
+
principal?: CedarScope;
|
|
61
|
+
/** Defaults to unconstrained (`{}`) when omitted. */
|
|
62
|
+
action?: CedarScope;
|
|
63
|
+
/** Defaults to unconstrained (`{}`) when omitted. */
|
|
64
|
+
resource?: CedarScope;
|
|
65
|
+
/** Cedar expression strings, each emitted as its own `when { … }` clause. */
|
|
66
|
+
when?: string[];
|
|
67
|
+
/** Cedar expression strings, each emitted as its own `unless { … }` clause. */
|
|
68
|
+
unless?: string[];
|
|
69
|
+
/**
|
|
70
|
+
* Emitted as `@key("value")` above the policy. An explicit `id` wins over
|
|
71
|
+
* the one derived from the logical name.
|
|
72
|
+
*/
|
|
73
|
+
annotations?: Record<string, string>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── Helpers ───────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
79
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getProps(entity: Declarable): Record<string, unknown> {
|
|
83
|
+
if (isResourceDeclarable(entity) && isRecord(entity.props)) {
|
|
84
|
+
return entity.props;
|
|
85
|
+
}
|
|
86
|
+
return {};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Escape a string for a double-quoted Cedar literal. */
|
|
90
|
+
export function escapeCedarString(value: string): string {
|
|
91
|
+
return value
|
|
92
|
+
.replace(/\\/g, "\\\\")
|
|
93
|
+
.replace(/"/g, '\\"')
|
|
94
|
+
.replace(/\n/g, "\\n")
|
|
95
|
+
.replace(/\r/g, "\\r")
|
|
96
|
+
.replace(/\t/g, "\\t");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Derive a policy id from a logical name: `allowAdminRead` → `allow-admin-read`.
|
|
101
|
+
* Cedar ids are free-form strings; kebab-case keeps them readable in the
|
|
102
|
+
* `@id` annotation and stable across a rename-free refactor.
|
|
103
|
+
*/
|
|
104
|
+
export function policyIdFromLogicalName(name: string): string {
|
|
105
|
+
return name
|
|
106
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1-$2")
|
|
107
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2")
|
|
108
|
+
.replace(/[\s_]+/g, "-")
|
|
109
|
+
.toLowerCase();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Walker visitor ────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Cedar has no intrinsic function syntax, so references collapse to the text
|
|
116
|
+
* a policy would name them by. Property names pass through verbatim.
|
|
117
|
+
*/
|
|
118
|
+
const cedarVisitor: SerializerVisitor = {
|
|
119
|
+
attrRef: (logicalName, attribute) => `${logicalName}.${attribute}`,
|
|
120
|
+
resourceRef: (logicalName) => logicalName,
|
|
121
|
+
propertyDeclarable: (entity, walk) => {
|
|
122
|
+
const out: Record<string, unknown> = {};
|
|
123
|
+
for (const [key, value] of Object.entries(getProps(entity))) {
|
|
124
|
+
if (key === "entityType" || key === "lexicon") continue;
|
|
125
|
+
if (value === undefined) continue;
|
|
126
|
+
out[key] = walk(value);
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// ── Cedar policy text ─────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
function refText(value: unknown): string {
|
|
135
|
+
return String(value);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** `principal`, `principal == User::"alice"`, `action in [ … ]`, `resource is Photo`. */
|
|
139
|
+
function renderScope(variable: string, scope: unknown): string {
|
|
140
|
+
if (!isRecord(scope)) return variable;
|
|
141
|
+
|
|
142
|
+
const parts = [variable];
|
|
143
|
+
if (typeof scope.is === "string") parts.push(`is ${scope.is}`);
|
|
144
|
+
if (scope.eq !== undefined) parts.push(`== ${refText(scope.eq)}`);
|
|
145
|
+
if (scope.in !== undefined) {
|
|
146
|
+
parts.push(
|
|
147
|
+
Array.isArray(scope.in)
|
|
148
|
+
? `in [${scope.in.map(refText).join(", ")}]`
|
|
149
|
+
: `in ${refText(scope.in)}`,
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return parts.join(" ");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function conditionStrings(value: unknown): string[] {
|
|
156
|
+
if (value === undefined || value === null) return [];
|
|
157
|
+
if (Array.isArray(value)) return value.map(refText);
|
|
158
|
+
return [refText(value)];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* One policy, as `.cedar` text.
|
|
163
|
+
*
|
|
164
|
+
* Exported because the AVP embedding (#1652) needs exactly this string and
|
|
165
|
+
* nothing else — `AWS::VerifiedPermissions::Policy` carries its policy as
|
|
166
|
+
* `Definition.Static.Statement`, a single Cedar policy rather than a set. Two
|
|
167
|
+
* renderers would be two dialects; there is one.
|
|
168
|
+
*/
|
|
169
|
+
export function renderPolicyText(id: string, props: Record<string, unknown>): string {
|
|
170
|
+
const lines: string[] = [];
|
|
171
|
+
|
|
172
|
+
// @id first, then the author's own annotations in declaration order.
|
|
173
|
+
const annotations = isRecord(props.annotations) ? props.annotations : {};
|
|
174
|
+
lines.push(`@id("${escapeCedarString(id)}")`);
|
|
175
|
+
for (const [key, value] of Object.entries(annotations)) {
|
|
176
|
+
if (key === "id" || value === undefined || value === null) continue;
|
|
177
|
+
lines.push(`@${key}("${escapeCedarString(refText(value))}")`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const effect = props.effect === "forbid" ? "forbid" : "permit";
|
|
181
|
+
lines.push(`${effect} (`);
|
|
182
|
+
lines.push(` ${renderScope("principal", props.principal)},`);
|
|
183
|
+
lines.push(` ${renderScope("action", props.action)},`);
|
|
184
|
+
lines.push(` ${renderScope("resource", props.resource)}`);
|
|
185
|
+
lines.push(")");
|
|
186
|
+
|
|
187
|
+
for (const clause of conditionStrings(props.when)) {
|
|
188
|
+
lines.push(`when { ${clause} }`);
|
|
189
|
+
}
|
|
190
|
+
for (const clause of conditionStrings(props.unless)) {
|
|
191
|
+
lines.push(`unless { ${clause} }`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return lines.join("\n") + ";";
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ── Cedar JSON policy format ──────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
/** The JSON policy-set envelope, exactly as `cedar-wasm` accepts it. */
|
|
200
|
+
export interface CedarPolicySetJSON {
|
|
201
|
+
staticPolicies: Record<string, PolicyJson>;
|
|
202
|
+
templates: Record<string, PolicyJson>;
|
|
203
|
+
templateLinks: unknown[];
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Build the JSON companion from the emitted `.cedar` text.
|
|
208
|
+
*
|
|
209
|
+
* Two things fall out of going through the module rather than around it. The
|
|
210
|
+
* condition bodies become real expression trees, which is the whole reason the
|
|
211
|
+
* file is worth writing; and a policy carrying a `?principal`/`?resource` slot
|
|
212
|
+
* lands under `templates` rather than `staticPolicies`, because Cedar — not
|
|
213
|
+
* this serializer — is what decides which one it is.
|
|
214
|
+
*
|
|
215
|
+
* Ids come from each policy's own `@id`, not from the emission order, so the
|
|
216
|
+
* keys here cannot drift from the annotations in the text beside them.
|
|
217
|
+
*/
|
|
218
|
+
export function policySetJSON(text: string): { doc?: CedarPolicySetJSON; error?: string } {
|
|
219
|
+
const parts = splitPolicySet(text);
|
|
220
|
+
if (!parts.ok) return { error: parts.error };
|
|
221
|
+
|
|
222
|
+
const doc: CedarPolicySetJSON = { staticPolicies: {}, templates: {}, templateLinks: [] };
|
|
223
|
+
|
|
224
|
+
const collect = (
|
|
225
|
+
sources: string[],
|
|
226
|
+
convert: (source: string) => ReturnType<typeof policyToJson>,
|
|
227
|
+
into: Record<string, PolicyJson>,
|
|
228
|
+
fallbackPrefix: string,
|
|
229
|
+
): string | undefined => {
|
|
230
|
+
for (const [index, source] of sources.entries()) {
|
|
231
|
+
const converted = convert(source);
|
|
232
|
+
if (!converted.ok) return converted.error;
|
|
233
|
+
const id = converted.value.annotations?.id;
|
|
234
|
+
into[typeof id === "string" && id.length > 0 ? id : `${fallbackPrefix}${index}`] = converted.value;
|
|
235
|
+
}
|
|
236
|
+
return undefined;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const policyError = collect(parts.value.policies, policyToJson, doc.staticPolicies, "policy");
|
|
240
|
+
if (policyError) return { error: policyError };
|
|
241
|
+
|
|
242
|
+
const templateError = collect(parts.value.templates, templateToJson, doc.templates, "template");
|
|
243
|
+
if (templateError) return { error: templateError };
|
|
244
|
+
|
|
245
|
+
return { doc };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// ── The shared policy model ───────────────────────────────────────
|
|
249
|
+
|
|
250
|
+
/** One declared policy, resolved: its chant name, its Cedar id, and its walked props. */
|
|
251
|
+
export interface CedarPolicyRecord {
|
|
252
|
+
/** The chant entity name — the export name on the `*.ts` file. */
|
|
253
|
+
name: string;
|
|
254
|
+
/** The Cedar policy id, as it appears in `@id(…)`. */
|
|
255
|
+
id: string;
|
|
256
|
+
/** Props with references resolved, ready for either renderer. */
|
|
257
|
+
props: Record<string, unknown>;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* The Cedar id for a policy: an explicit `annotations.id` when the author gave
|
|
262
|
+
* one, else derived from the logical name.
|
|
263
|
+
*
|
|
264
|
+
* This is the only rule that links a chant entity to a policy in a live AVP
|
|
265
|
+
* store (#1652) — the observation resolves the same id from the same props and
|
|
266
|
+
* matches it against the `@id` annotation the statement carries. Two copies of
|
|
267
|
+
* this rule would be a mapping that drifts silently, so there is one.
|
|
268
|
+
*/
|
|
269
|
+
export function resolvePolicyId(logicalName: string, props: Record<string, unknown>): string {
|
|
270
|
+
const explicit = isRecord(props.annotations) ? props.annotations.id : undefined;
|
|
271
|
+
return typeof explicit === "string" && explicit.length > 0
|
|
272
|
+
? explicit
|
|
273
|
+
: policyIdFromLogicalName(logicalName);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Every `Cedar::Policy` in a build, with references walked and ids resolved.
|
|
278
|
+
*
|
|
279
|
+
* The serializer's own first pass, exported so the AVP embedding (#1652)
|
|
280
|
+
* renders from the same model rather than re-deriving it.
|
|
281
|
+
*/
|
|
282
|
+
export function cedarPolicyRecords(entities: Map<string, Declarable>): CedarPolicyRecord[] {
|
|
283
|
+
// Reverse map first: walkValue resolves a Declarable reference by identity,
|
|
284
|
+
// so every entity has to be known before any of them is walked.
|
|
285
|
+
const entityNames = new Map<Declarable, string>();
|
|
286
|
+
for (const [name, entity] of entities) {
|
|
287
|
+
entityNames.set(entity, name);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const records: CedarPolicyRecord[] = [];
|
|
291
|
+
for (const [name, entity] of entities) {
|
|
292
|
+
if (isPropertyDeclarable(entity)) continue;
|
|
293
|
+
if (entity.entityType !== CEDAR_POLICY_TYPE) continue;
|
|
294
|
+
|
|
295
|
+
const props = walkValue(getProps(entity), entityNames, cedarVisitor) as Record<string, unknown>;
|
|
296
|
+
records.push({ name, id: resolvePolicyId(name, props), props });
|
|
297
|
+
}
|
|
298
|
+
return records;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// ── Serializer ────────────────────────────────────────────────────
|
|
302
|
+
|
|
303
|
+
export const cedarSerializer: Serializer = {
|
|
304
|
+
name: "cedar",
|
|
305
|
+
rulePrefix: "CED",
|
|
306
|
+
|
|
307
|
+
serialize(entities: Map<string, Declarable>, _outputs?: LexiconOutput[]): string | SerializerResult {
|
|
308
|
+
const policyText: string[] = [];
|
|
309
|
+
|
|
310
|
+
for (const { id, props } of cedarPolicyRecords(entities)) {
|
|
311
|
+
policyText.push(renderPolicyText(id, props));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (policyText.length === 0) return "";
|
|
315
|
+
|
|
316
|
+
const primary = policyText.join("\n\n") + "\n";
|
|
317
|
+
const { doc, error } = policySetJSON(primary);
|
|
318
|
+
|
|
319
|
+
// A policy set Cedar cannot read is a real defect, but it is the lint and
|
|
320
|
+
// post-synth surface's to report (#1651) — the text is still the artifact
|
|
321
|
+
// every evaluator consumes, so it is emitted either way, with the module's
|
|
322
|
+
// own message carried out as a build warning rather than swallowed.
|
|
323
|
+
if (!doc) {
|
|
324
|
+
return {
|
|
325
|
+
primary,
|
|
326
|
+
warnings: [`cedar: the emitted policy text did not parse, so ${CEDAR_JSON_FILENAME} was not written — ${error}`],
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
primary,
|
|
332
|
+
files: { [CEDAR_JSON_FILENAME]: JSON.stringify(doc, null, 2) + "\n" },
|
|
333
|
+
};
|
|
334
|
+
},
|
|
335
|
+
};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: chant-cedar-authoring
|
|
3
|
+
description: Author Cedar authorization policies as typed chant resources — schema to generated classes to .cedar and JSON outputs
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Cedar Policies as Typed Resources
|
|
8
|
+
|
|
9
|
+
## What this lexicon covers
|
|
10
|
+
|
|
11
|
+
Cedar is an authorization policy language. Its toolchain validates and evaluates
|
|
12
|
+
policies; it does not help you write them. There are no functions, no modules,
|
|
13
|
+
no loops, and templates carry exactly two slots (`?principal`, `?resource`), so
|
|
14
|
+
teams managing large policy sets generate `.cedar` text with string templating.
|
|
15
|
+
This lexicon replaces that with typed TypeScript.
|
|
16
|
+
|
|
17
|
+
Two artifacts come out of every build:
|
|
18
|
+
|
|
19
|
+
- `<name>.cedar` — the policy text every Cedar evaluator reads (Amazon Verified
|
|
20
|
+
Permissions, cedar-agent, an embedded `cedar-wasm`).
|
|
21
|
+
- `policies.cedar.json` — the same set in the Cedar JSON policy format, beside
|
|
22
|
+
it. This is also the parse source for import.
|
|
23
|
+
|
|
24
|
+
chant is nowhere in either. An emitted policy set is consumed by any evaluator.
|
|
25
|
+
|
|
26
|
+
## The three steps
|
|
27
|
+
|
|
28
|
+
### 1. Declare a schema
|
|
29
|
+
|
|
30
|
+
The schema is *your* file, not a global upstream — it is the input codegen
|
|
31
|
+
reads. Write it in Cedar's human-readable syntax and point the config at it:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// chant.config.ts
|
|
35
|
+
import type { ChantConfig } from "@intentius/chant";
|
|
36
|
+
import "@intentius/chant-lexicon-cedar";
|
|
37
|
+
|
|
38
|
+
export default {
|
|
39
|
+
lexicons: ["cedar"],
|
|
40
|
+
cedar: {
|
|
41
|
+
schema: "authz/app.cedarschema",
|
|
42
|
+
validation: { mode: "strict", requireProjectSchema: true },
|
|
43
|
+
},
|
|
44
|
+
} satisfies ChantConfig;
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Turn `requireProjectSchema` on once the project has its own schema. Without it a
|
|
48
|
+
missing file silently falls back to the bundled default, and the difference
|
|
49
|
+
between "your entity types" and "somebody else's" is invisible.
|
|
50
|
+
|
|
51
|
+
### 2. Run generate
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx chant generate --lexicon cedar
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
That produces, per declaration in the schema:
|
|
58
|
+
|
|
59
|
+
| Schema declaration | Generated |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `entity Document in [Folder] = { … }` | `Document` class, `DocumentAttributes` property class, `DocumentUid` template-literal type |
|
|
62
|
+
| `action read appliesTo { … }` | `ReadAction` constant, `ReadContext` property class |
|
|
63
|
+
| — | `Policy` class, `EntityTypeName`, `ActionUid`, `PolicyScope`, `ALL_ACTIONS`, `ALL_ENTITY_TYPES` |
|
|
64
|
+
|
|
65
|
+
`DocumentUid` is `` `App::Document::"${string}"` `` — a typo'd namespace is a
|
|
66
|
+
compile error, not a validation error hours later.
|
|
67
|
+
|
|
68
|
+
### 3. Write policies
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { Policy, ReadAction, WriteAction, type UserUid } from "@intentius/chant-lexicon-cedar";
|
|
72
|
+
|
|
73
|
+
const archivist: UserUid = 'App::User::"archivist"';
|
|
74
|
+
|
|
75
|
+
export const ownerRead = new Policy({
|
|
76
|
+
effect: "permit",
|
|
77
|
+
principal: { is: "App::User" },
|
|
78
|
+
action: { in: [ReadAction, WriteAction] },
|
|
79
|
+
resource: { is: "App::Document" },
|
|
80
|
+
when: ["resource.owner == principal"],
|
|
81
|
+
annotations: { doc: "Owners always read their own documents." },
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
export const restrictDelete = new Policy({
|
|
85
|
+
effect: "forbid",
|
|
86
|
+
resource: { is: "App::Document" },
|
|
87
|
+
when: ['resource.classification == "confidential"'],
|
|
88
|
+
unless: [`principal == ${archivist}`],
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Policy props
|
|
93
|
+
|
|
94
|
+
| Prop | Meaning |
|
|
95
|
+
|------|---------|
|
|
96
|
+
| `effect` | `"permit"` or `"forbid"`. Defaults to `permit` |
|
|
97
|
+
| `principal`, `action`, `resource` | Scope constraints. Omit for unconstrained |
|
|
98
|
+
| `when` | Cedar expression strings, one `when { … }` clause each |
|
|
99
|
+
| `unless` | Cedar expression strings, one `unless { … }` clause each |
|
|
100
|
+
| `annotations` | `Record<string, string>`, emitted as `@key("value")` |
|
|
101
|
+
|
|
102
|
+
### Scope forms
|
|
103
|
+
|
|
104
|
+
| Written | Emitted |
|
|
105
|
+
|---|---|
|
|
106
|
+
| `{}` or omitted | `principal` |
|
|
107
|
+
| `{ eq: X }` | `principal == X` |
|
|
108
|
+
| `{ in: X }` | `principal in X` |
|
|
109
|
+
| `{ in: [X, Y] }` | `principal in [X, Y]` |
|
|
110
|
+
| `{ is: "App::User" }` | `principal is App::User` |
|
|
111
|
+
| `{ is: "App::User", in: X }` | `principal is App::User in X` |
|
|
112
|
+
|
|
113
|
+
`when`/`unless` stay strings. Cedar's expression grammar *is* the policy
|
|
114
|
+
language; typing it in TypeScript is a separate problem, and the escape hatch
|
|
115
|
+
would leak either way.
|
|
116
|
+
|
|
117
|
+
## Policy ids
|
|
118
|
+
|
|
119
|
+
The id comes from the export's logical name, kebab-cased: `allowAdminRead`
|
|
120
|
+
becomes `@id("allow-admin-read")`. Set `annotations.id` to pin one explicitly —
|
|
121
|
+
worth doing for a policy whose id something downstream references.
|
|
122
|
+
|
|
123
|
+
## Composites
|
|
124
|
+
|
|
125
|
+
Repeated policy shapes go in a factory, because Cedar has nowhere to put them.
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import {
|
|
129
|
+
DeleteAction,
|
|
130
|
+
DenyByDefaultSet,
|
|
131
|
+
OwnerCanManage,
|
|
132
|
+
ReadAction,
|
|
133
|
+
WriteAction,
|
|
134
|
+
} from "@intentius/chant-lexicon-cedar";
|
|
135
|
+
|
|
136
|
+
const docOwner = OwnerCanManage({
|
|
137
|
+
entityType: "App::Document",
|
|
138
|
+
actions: [ReadAction, WriteAction],
|
|
139
|
+
principal: "App::User",
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const guarded = DenyByDefaultSet({
|
|
143
|
+
policies: [docOwner],
|
|
144
|
+
entityType: "App::Document",
|
|
145
|
+
actions: DeleteAction,
|
|
146
|
+
when: ['resource.classification == "confidential"'],
|
|
147
|
+
unless: ['principal == App::User::"archivist"'],
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
export const [confidentialFloor, documentOwnerGrant] = guarded.all;
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`DenyByDefaultSet` returns the `forbid` floor and its members from one call, so
|
|
154
|
+
deleting the floor deletes the grants with it. A `forbid` beats every `permit`
|
|
155
|
+
in the set unconditionally — it is the only construct that survives a later,
|
|
156
|
+
wider grant.
|
|
157
|
+
|
|
158
|
+
## Checking the result
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npx chant build # emits both artifacts, runs the CED rules
|
|
162
|
+
npx chant coverage --lexicon cedar # is every schema declaration generated?
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The MCP tool `cedar:coverage` answers the other question: which schema entity
|
|
166
|
+
types and actions the *policy set* can reach, which are reachable only from a
|
|
167
|
+
`forbid`, and which no policy touches at all. An entity type nothing covers is
|
|
168
|
+
inert under Cedar's default-deny — either the intent or a hole.
|
|
169
|
+
|
|
170
|
+
## Things that will bite
|
|
171
|
+
|
|
172
|
+
- **An empty schema validates everything clean.** `checkParseSchema("")`
|
|
173
|
+
succeeds. Set `requireProjectSchema: true`.
|
|
174
|
+
- **`Group` and `Team`-shaped container entities appear in no `appliesTo`.**
|
|
175
|
+
They exist to be `in`, so no policy — not even `permit (principal, action,
|
|
176
|
+
resource)` — resolves to them. `cedar:coverage` reports that honestly rather
|
|
177
|
+
than rounding it up.
|
|
178
|
+
- **A policy naming an entity type outside the schema still parses.** It
|
|
179
|
+
resolves to an empty request envelope and never fires. `cedar:coverage`
|
|
180
|
+
reports it under `inert`.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
---
|
|
2
|
+
skill: chant-cedar-avp-embedding
|
|
3
|
+
description: Embed a typed cedar-lexicon policy into an AWS Verified Permissions policy resource instead of a hand-written string
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Cedar Policies Inside Verified Permissions
|
|
8
|
+
|
|
9
|
+
## The seam
|
|
10
|
+
|
|
11
|
+
Amazon Verified Permissions is one deployment vehicle for Cedar, and chant
|
|
12
|
+
already ships it: `AWS::VerifiedPermissions::Policy` in the aws lexicon carries
|
|
13
|
+
its policy text in a `definition.static.statement` field typed `CedarPolicy` —
|
|
14
|
+
which is to say, `string`.
|
|
15
|
+
|
|
16
|
+
That string is the seam. Everything upstream of it — the schema, the entity
|
|
17
|
+
types, the actions, the scope constraints — is what the cedar lexicon owns.
|
|
18
|
+
Everything downstream — the policy store, the CloudFormation `ApplyOp`, the IAM
|
|
19
|
+
around it — is the aws lexicon's, and already works.
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
cedar lexicon aws lexicon
|
|
23
|
+
schema → Policy → .cedar text → VerifiedPermissionsPolicy.definition.static.statement
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The walk-away test holds on both sides. The emitted `.cedar` file is read by any
|
|
27
|
+
evaluator with no AWS involved; the AVP resource deploys through the same
|
|
28
|
+
CloudFormation path every other AWS resource does.
|
|
29
|
+
|
|
30
|
+
## The typed handoff
|
|
31
|
+
|
|
32
|
+
`avpPolicyDefinition(name, props)` returns exactly the `Definition` property
|
|
33
|
+
`AWS::VerifiedPermissions::Policy` takes, rendered by the same renderer that
|
|
34
|
+
writes the `.cedar` file — so the deployed policy and the reviewed file cannot
|
|
35
|
+
disagree.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { Policy, ReadAction, avpPolicyDefinition } from "@intentius/chant-lexicon-cedar";
|
|
39
|
+
import { VerifiedPermissionsPolicy } from "@intentius/chant-lexicon-aws";
|
|
40
|
+
|
|
41
|
+
const ownerReadProps = {
|
|
42
|
+
effect: "permit",
|
|
43
|
+
principal: { is: "App::User" },
|
|
44
|
+
action: { eq: ReadAction },
|
|
45
|
+
resource: { is: "App::Document" },
|
|
46
|
+
when: ["resource.owner == principal"],
|
|
47
|
+
} as const;
|
|
48
|
+
|
|
49
|
+
/** The evaluator-agnostic artifact: this is what lands in the `.cedar` file. */
|
|
50
|
+
export const ownerRead = new Policy(ownerReadProps);
|
|
51
|
+
|
|
52
|
+
/** The AVP deployment view of the same policy. */
|
|
53
|
+
export const ownerReadAvp = new VerifiedPermissionsPolicy({
|
|
54
|
+
PolicyStoreId: policyStore.ref(),
|
|
55
|
+
Definition: avpPolicyDefinition("ownerRead", ownerReadProps, {
|
|
56
|
+
ownership: { stack: "authz", env: "prod" },
|
|
57
|
+
description: "Owners read their own documents.",
|
|
58
|
+
}),
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Beside it: `avpStatement()` for the bare string, `avpStatementJSON()` for
|
|
63
|
+
evaluators that take the JSON policy format, and `avpPolicySet(entities)` to
|
|
64
|
+
render a whole build's policies at once, keyed by chant entity name.
|
|
65
|
+
|
|
66
|
+
**The cedar lexicon does not depend on the aws lexicon.** The seam is the data
|
|
67
|
+
shape, which is stable CloudFormation. `examples/avp-embedding/` shows the
|
|
68
|
+
pairing with a plain-object stand-in, because the shipped cedar examples build
|
|
69
|
+
against the cedar serializer alone.
|
|
70
|
+
|
|
71
|
+
## The lifecycle surface
|
|
72
|
+
|
|
73
|
+
`describeResources()`, `observeAmbient()` and `exportResources()` read a live
|
|
74
|
+
policy store. Point them at one with `CEDAR_AVP_POLICY_STORE_ID` (or
|
|
75
|
+
`CEDAR_AVP_POLICY_STORE_ID_<ENV>`), or a `policyStoreId` prop on a declared
|
|
76
|
+
policy.
|
|
77
|
+
|
|
78
|
+
The link from a chant entity to a live policy is the Cedar `@id` annotation,
|
|
79
|
+
which is derived from the export name (`ownerRead` → `owner-read`) unless
|
|
80
|
+
`annotations.id` overrides it. Rename an export and the observation follows it;
|
|
81
|
+
rename it *and* pin `annotations.id` and the live policy stays matched.
|
|
82
|
+
|
|
83
|
+
## Rules
|
|
84
|
+
|
|
85
|
+
- **Do not write the statement as prose.** A hand-typed
|
|
86
|
+
`"permit(principal, action, resource);"` in an AVP resource is the exact thing
|
|
87
|
+
this lexicon exists to remove, and the meta-policy wall (see the
|
|
88
|
+
`chant-cedar-meta-policy` skill) fails a bare permit in a prod build.
|
|
89
|
+
- **Do not try to tag an individual policy.** AVP policy *stores* are taggable;
|
|
90
|
+
individual policies are not — `CreatePolicy` has no tag surface and a policy
|
|
91
|
+
has no ARN. chant's per-policy marker therefore rides in the policy
|
|
92
|
+
description, stamped by passing `ownership` to `avpPolicyDefinition` and read
|
|
93
|
+
back by `describeResources`/`exportResources`. Store tags remain the coarse
|
|
94
|
+
channel. The design record, including what the choice costs, is
|
|
95
|
+
`src/avp/OWNERSHIP.md`.
|
|
96
|
+
- **Do not hand-write the description when you want ownership.** Pass
|
|
97
|
+
`ownership` and let the marker be encoded; the description is capped at 150
|
|
98
|
+
characters and the encoder truncates prose rather than the marker, which is
|
|
99
|
+
what keeps a chant-owned policy from silently reading as foreign.
|
|
100
|
+
- **Do not treat an ambient permit as housekeeping.** A permit found in a store
|
|
101
|
+
that no source file declares is a standing grant somebody made outside review.
|
|
102
|
+
It is a security finding. `observeAmbient()` reports the statement and its
|
|
103
|
+
effect and stops short of the verdict — the judgement is yours.
|
|
104
|
+
|
|
105
|
+
## Non-AVP evaluators
|
|
106
|
+
|
|
107
|
+
AVP is not the only target, and the lexicon does not privilege it. The same
|
|
108
|
+
emitted `.cedar` and `policies.cedar.json` feed:
|
|
109
|
+
|
|
110
|
+
- **cedar-agent** — a standalone Cedar decision service; point it at the policy
|
|
111
|
+
file and the entity store.
|
|
112
|
+
- **An embedded `cedar-wasm`** — the package this lexicon already depends on.
|
|
113
|
+
Load the policy text in-process and call `isAuthorized`.
|
|
114
|
+
- **Cloudflare-style embeddings** — the same file, read at the edge.
|
|
115
|
+
|
|
116
|
+
If the deployment target is anything other than AVP, there is no embedding step
|
|
117
|
+
at all. Emit the files and ship them.
|
|
118
|
+
|
|
119
|
+
## Cedar-for-Kubernetes
|
|
120
|
+
|
|
121
|
+
The CNCF push includes Cedar as a Kubernetes authorizer, with policies as CRDs.
|
|
122
|
+
Those kinds belong to the k8s lexicon's CRD sources — the same rule that kept
|
|
123
|
+
`helm.cattle.io` out of the k3s lexicon. What the cedar lexicon does there is
|
|
124
|
+
lint the policy *text* embedded in those kinds, which is the pattern the ARGO
|
|
125
|
+
rules already use.
|