@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
package/src/avp/embed.ts
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed embedding of a cedar policy into `AWS::VerifiedPermissions::Policy`
|
|
3
|
+
* (#1652).
|
|
4
|
+
*
|
|
5
|
+
* The aws lexicon keeps the deployment vehicle — its generated
|
|
6
|
+
* `VerifiedPermissionsPolicy` takes a required `PolicyStoreId` and a required
|
|
7
|
+
* `Definition`, and the `Definition.Static.Statement` inside it is an opaque
|
|
8
|
+
* string. Opaque is the problem the whole cedar lexicon exists to remove: today
|
|
9
|
+
* a team writes that string with `${}` templating and finds out at deploy time
|
|
10
|
+
* whether the entity type exists.
|
|
11
|
+
*
|
|
12
|
+
* This module closes the loop without joining the two lexicons together. It
|
|
13
|
+
* emits the *string* and the *JSON envelope* AVP expects and stops there:
|
|
14
|
+
* nothing here imports `@intentius/chant-lexicon-aws`, and nothing here needs
|
|
15
|
+
* to. A project that has both installed writes
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* new VerifiedPermissionsPolicy({
|
|
19
|
+
* PolicyStoreId: store.ref(),
|
|
20
|
+
* Definition: avpPolicyDefinition("ownerRead", ownerReadProps),
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* and the statement is the same text `chant build` writes to `.cedar`, rendered
|
|
25
|
+
* by the same renderer, with `@id` intact.
|
|
26
|
+
*
|
|
27
|
+
* ### Why no dependency, and why the example uses plain objects
|
|
28
|
+
*
|
|
29
|
+
* A cedar → aws dependency would invert the epic's decision that Cedar is
|
|
30
|
+
* vendor-neutral and AVP is one of several evaluators, and it would make the
|
|
31
|
+
* cedar lexicon unbuildable without the aws one. The seam is therefore the
|
|
32
|
+
* data shape, which is stable CloudFormation, and
|
|
33
|
+
* `examples/avp-embedding/` demonstrates the pairing with a plain-object stand-in
|
|
34
|
+
* for `VerifiedPermissionsPolicy` — the shipped examples build with the cedar
|
|
35
|
+
* serializer alone, so a real cross-lexicon example could not run in this
|
|
36
|
+
* repo's example harness at all. The stand-in carries the exact prop names the
|
|
37
|
+
* generated class declares (`PolicyStoreId`, `Definition`, `Name`), so the
|
|
38
|
+
* substitution is a one-line edit in a real project.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import type { Declarable } from "@intentius/chant/declarable";
|
|
42
|
+
import type { OwnershipMarker } from "@intentius/chant/ownership";
|
|
43
|
+
import {
|
|
44
|
+
cedarPolicyRecords,
|
|
45
|
+
renderPolicyText,
|
|
46
|
+
resolvePolicyId,
|
|
47
|
+
type CedarPolicyProps,
|
|
48
|
+
} from "../serializer";
|
|
49
|
+
import { policyToJson, type PolicyJson } from "../spec/wasm";
|
|
50
|
+
import { encodeOwnershipDescription } from "./ownership";
|
|
51
|
+
|
|
52
|
+
/** `Definition.Static` — the static-policy half of the AVP definition union. */
|
|
53
|
+
export interface AvpStaticDefinition {
|
|
54
|
+
Statement: string;
|
|
55
|
+
Description?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The `Definition` property of `AWS::VerifiedPermissions::Policy`. */
|
|
59
|
+
export interface AvpPolicyDefinition {
|
|
60
|
+
Static: AvpStaticDefinition;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** The props of `AWS::VerifiedPermissions::Policy` this module can fill. */
|
|
64
|
+
export interface AvpPolicyResource {
|
|
65
|
+
PolicyStoreId: string;
|
|
66
|
+
Definition: AvpPolicyDefinition;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface AvpEmbedOptions {
|
|
70
|
+
/**
|
|
71
|
+
* Ownership marker to stamp into the description — the per-policy channel
|
|
72
|
+
* (see ./ownership.ts). Supply it and `describeResources`/`exportResources`
|
|
73
|
+
* can tell this policy from one somebody added in the console.
|
|
74
|
+
*/
|
|
75
|
+
ownership?: OwnershipMarker;
|
|
76
|
+
/** The author's own description, kept ahead of the marker. */
|
|
77
|
+
description?: string;
|
|
78
|
+
/** Override the Cedar id. Defaults to the serializer's own derivation. */
|
|
79
|
+
policyId?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function propsRecord(props: CedarPolicyProps | Record<string, unknown>): Record<string, unknown> {
|
|
83
|
+
return props as Record<string, unknown>;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The Cedar policy text for one policy — the exact string
|
|
88
|
+
* `Definition.Static.Statement` wants.
|
|
89
|
+
*
|
|
90
|
+
* `name` is the chant entity name; the `@id` annotation the statement carries
|
|
91
|
+
* is derived from it the same way the serializer derives it, which is what lets
|
|
92
|
+
* the live observation match a policy in the store back to the entity that
|
|
93
|
+
* declared it.
|
|
94
|
+
*/
|
|
95
|
+
export function avpStatement(
|
|
96
|
+
name: string,
|
|
97
|
+
props: CedarPolicyProps | Record<string, unknown>,
|
|
98
|
+
options: AvpEmbedOptions = {},
|
|
99
|
+
): string {
|
|
100
|
+
const record = propsRecord(props);
|
|
101
|
+
return renderPolicyText(options.policyId ?? resolvePolicyId(name, record), record);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The Cedar JSON policy format for one policy — for an evaluator that takes
|
|
106
|
+
* JSON rather than text (`cedar-agent`, an embedded `cedar-wasm`).
|
|
107
|
+
*
|
|
108
|
+
* Not what AVP's `Definition` wants; AVP takes the text. Emitted here so the
|
|
109
|
+
* seam is documented in both directions rather than only the AWS one.
|
|
110
|
+
*
|
|
111
|
+
* Built the way the serializer's JSON leg is built (#1653): render the text,
|
|
112
|
+
* then hand it to `cedar-wasm`, so what comes back is Cedar's own reading
|
|
113
|
+
* rather than a second, worse encoder. Throws on text Cedar refuses — this is
|
|
114
|
+
* an authoring-time call, and a policy the module cannot read is a defect the
|
|
115
|
+
* caller wants to hear about rather than a `undefined` to thread through.
|
|
116
|
+
*/
|
|
117
|
+
export function avpStatementJSON(
|
|
118
|
+
name: string,
|
|
119
|
+
props: CedarPolicyProps | Record<string, unknown>,
|
|
120
|
+
options: AvpEmbedOptions = {},
|
|
121
|
+
): PolicyJson {
|
|
122
|
+
const statement = avpStatement(name, props, options);
|
|
123
|
+
const converted = policyToJson(statement);
|
|
124
|
+
if (!converted.ok) {
|
|
125
|
+
throw new Error(`cedar: ${name} did not parse, so no JSON form could be produced — ${converted.error}`);
|
|
126
|
+
}
|
|
127
|
+
return converted.value;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The `Definition` property of `AWS::VerifiedPermissions::Policy`.
|
|
132
|
+
*
|
|
133
|
+
* With `options.ownership` set, the description carries chant's marker — the
|
|
134
|
+
* only per-policy ownership channel AVP has, because policies are not taggable.
|
|
135
|
+
*/
|
|
136
|
+
export function avpPolicyDefinition(
|
|
137
|
+
name: string,
|
|
138
|
+
props: CedarPolicyProps | Record<string, unknown>,
|
|
139
|
+
options: AvpEmbedOptions = {},
|
|
140
|
+
): AvpPolicyDefinition {
|
|
141
|
+
const record = propsRecord(props);
|
|
142
|
+
const policyId = options.policyId ?? resolvePolicyId(name, record);
|
|
143
|
+
const description = options.ownership
|
|
144
|
+
? encodeOwnershipDescription(options.description, options.ownership, policyId)
|
|
145
|
+
: options.description?.trim();
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
Static: {
|
|
149
|
+
Statement: renderPolicyText(policyId, record),
|
|
150
|
+
...(description ? { Description: description } : {}),
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Both required props of `AWS::VerifiedPermissions::Policy`, ready to spread
|
|
157
|
+
* into the generated class.
|
|
158
|
+
*
|
|
159
|
+
* `PolicyStoreId` is a string here rather than an AttrRef because this module
|
|
160
|
+
* does not know the aws lexicon's reference types. A project passes
|
|
161
|
+
* `store.ref()` in place of the literal and TypeScript is satisfied by the
|
|
162
|
+
* generated class's own prop type, not by this one.
|
|
163
|
+
*/
|
|
164
|
+
export function avpPolicyResource(
|
|
165
|
+
name: string,
|
|
166
|
+
props: CedarPolicyProps | Record<string, unknown>,
|
|
167
|
+
policyStoreId: string,
|
|
168
|
+
options: AvpEmbedOptions = {},
|
|
169
|
+
): AvpPolicyResource {
|
|
170
|
+
return { PolicyStoreId: policyStoreId, Definition: avpPolicyDefinition(name, props, options) };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Every `Cedar::Policy` in a build, rendered as AVP definitions and keyed by
|
|
175
|
+
* chant entity name.
|
|
176
|
+
*
|
|
177
|
+
* The whole-set form: one call turns a policy set into the definitions a stack
|
|
178
|
+
* of `VerifiedPermissionsPolicy` declarations needs, with references between
|
|
179
|
+
* declared entities already walked (that is what `cedarPolicyRecords` does).
|
|
180
|
+
*/
|
|
181
|
+
export function avpPolicySet(
|
|
182
|
+
entities: Map<string, Declarable>,
|
|
183
|
+
options: AvpEmbedOptions = {},
|
|
184
|
+
): Record<string, AvpPolicyDefinition> {
|
|
185
|
+
const out: Record<string, AvpPolicyDefinition> = {};
|
|
186
|
+
for (const { name, id, props } of cedarPolicyRecords(entities)) {
|
|
187
|
+
out[name] = avpPolicyDefinition(name, props, { ...options, policyId: id });
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { checkParsePolicySet } from "@cedar-policy/cedar-wasm/nodejs";
|
|
3
|
+
import { CEDAR_POLICY_TYPE, type CedarPolicyProps } from "../serializer";
|
|
4
|
+
import { avpStatement } from "./embed";
|
|
5
|
+
import { exportAvpResources, policyToResourceIR } from "./live-export";
|
|
6
|
+
import {
|
|
7
|
+
markedDescription,
|
|
8
|
+
mockAvpTransport,
|
|
9
|
+
MOCK_ENV,
|
|
10
|
+
type MockStore,
|
|
11
|
+
} from "./testdata/mock-transport";
|
|
12
|
+
|
|
13
|
+
const STORE = "PS-abc123";
|
|
14
|
+
|
|
15
|
+
const OWNER_READ = `@id("owner-read")
|
|
16
|
+
@doc("Owners always read their own documents.")
|
|
17
|
+
permit (
|
|
18
|
+
principal is App::User,
|
|
19
|
+
action in [App::Action::"read", App::Action::"list"],
|
|
20
|
+
resource is App::Document
|
|
21
|
+
)
|
|
22
|
+
when { resource.owner == principal }
|
|
23
|
+
unless { context.mfa == false };`;
|
|
24
|
+
|
|
25
|
+
const RESTRICT_DELETE = `@id("restrict-delete")
|
|
26
|
+
forbid (
|
|
27
|
+
principal,
|
|
28
|
+
action == App::Action::"delete",
|
|
29
|
+
resource is App::Document
|
|
30
|
+
)
|
|
31
|
+
when { resource.classification == "confidential" };`;
|
|
32
|
+
|
|
33
|
+
const store: MockStore = {
|
|
34
|
+
policyStoreId: STORE,
|
|
35
|
+
policies: [
|
|
36
|
+
{
|
|
37
|
+
policyId: "p-2",
|
|
38
|
+
statement: RESTRICT_DELETE,
|
|
39
|
+
description: "added in the console",
|
|
40
|
+
createdDate: "2026-01-01T00:00:00Z",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
policyId: "p-1",
|
|
44
|
+
statement: OWNER_READ,
|
|
45
|
+
description: markedDescription("owner-read", "Owners read their own."),
|
|
46
|
+
lastUpdatedDate: "2026-08-01T00:00:00Z",
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function run(options: Partial<Parameters<typeof exportAvpResources>[0]> = {}) {
|
|
52
|
+
const { http } = mockAvpTransport(store);
|
|
53
|
+
return exportAvpResources({
|
|
54
|
+
environment: "prod",
|
|
55
|
+
policyStoreId: STORE,
|
|
56
|
+
client: { http },
|
|
57
|
+
env: MOCK_ENV,
|
|
58
|
+
...options,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** A live policy through `policyToResourceIR`, which is the import parser's reading of it. */
|
|
63
|
+
function irFor(statement: string, verbatim = false) {
|
|
64
|
+
return policyToResourceIR(
|
|
65
|
+
{
|
|
66
|
+
policyId: "p-x",
|
|
67
|
+
policyStoreId: STORE,
|
|
68
|
+
policyType: "STATIC",
|
|
69
|
+
statement,
|
|
70
|
+
authoredDescription: "",
|
|
71
|
+
marked: false,
|
|
72
|
+
},
|
|
73
|
+
{ verbatim },
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
describe("reading a live statement back", () => {
|
|
78
|
+
it("goes through the import parser, so the id is the Cedar policy id", () => {
|
|
79
|
+
// Not `ownerRead`: turning a policy id into a TypeScript identifier is
|
|
80
|
+
// CedarGenerator's job downstream, and doing it here too applies it twice.
|
|
81
|
+
expect(irFor(OWNER_READ)?.logicalId).toBe("owner-read");
|
|
82
|
+
expect(irFor(OWNER_READ)?.type).toBe(CEDAR_POLICY_TYPE);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("recovers effect and all three scopes", () => {
|
|
86
|
+
const props = irFor(OWNER_READ)!.properties;
|
|
87
|
+
expect(props.effect).toBe("permit");
|
|
88
|
+
expect(props.principal).toEqual({ is: "App::User" });
|
|
89
|
+
expect(props.action).toEqual({ in: ['App::Action::"read"', 'App::Action::"list"'] });
|
|
90
|
+
expect(props.resource).toEqual({ is: "App::Document" });
|
|
91
|
+
expect(irFor(RESTRICT_DELETE)!.properties.action).toEqual({ eq: 'App::Action::"delete"' });
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("keeps the author's own condition text rather than a re-printed tree", () => {
|
|
95
|
+
const props = irFor(OWNER_READ)!.properties;
|
|
96
|
+
expect(props.when).toEqual(["resource.owner == principal"]);
|
|
97
|
+
expect(props.unless).toEqual(["context.mfa == false"]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("does not let a record literal or a quoted brace end a clause early", () => {
|
|
101
|
+
const statement = `@id("tricky")
|
|
102
|
+
permit (principal, action, resource)
|
|
103
|
+
when { context == { mfa: true, note: "}" } };`;
|
|
104
|
+
expect(irFor(statement)!.properties.when).toEqual(['context == { mfa: true, note: "}" }']);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("files a slotted policy as a template, which is Cedar's call and not this reader's", () => {
|
|
108
|
+
const templated = `@id("linked")\npermit (principal == ?principal, action, resource);`;
|
|
109
|
+
expect(irFor(templated)?.type).toBe("Cedar::Template");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("refuses text the parser rejects rather than half-exporting it", () => {
|
|
113
|
+
expect(irFor("this is not cedar")).toBeUndefined();
|
|
114
|
+
expect(irFor("")).toBeUndefined();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("round-trips: live statement → props → statement the parser still accepts", () => {
|
|
118
|
+
const props = irFor(OWNER_READ)!.properties as CedarPolicyProps;
|
|
119
|
+
const rerendered = avpStatement("ownerRead", props);
|
|
120
|
+
|
|
121
|
+
expect(checkParsePolicySet({ staticPolicies: rerendered }).type).toBe("success");
|
|
122
|
+
expect(irFor(rerendered)!.properties).toEqual(props);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("exportAvpResources", () => {
|
|
127
|
+
it("returns import IR keyed by the Cedar policy id", async () => {
|
|
128
|
+
const ir = await run();
|
|
129
|
+
expect(ir.resources.map((r) => r.logicalId)).toEqual(["owner-read", "restrict-delete"]);
|
|
130
|
+
expect(ir.resources[0].type).toBe(CEDAR_POLICY_TYPE);
|
|
131
|
+
expect(ir.parameters).toEqual([]);
|
|
132
|
+
expect(ir.metadata?.policyStoreId).toBe(STORE);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("strips the server-written record by default", async () => {
|
|
136
|
+
const ir = await run();
|
|
137
|
+
const owner = ir.resources.find((r) => r.logicalId === "owner-read")!;
|
|
138
|
+
|
|
139
|
+
expect(owner.properties.avp).toBeUndefined();
|
|
140
|
+
expect(Object.keys(owner.properties).sort()).toEqual([
|
|
141
|
+
"action",
|
|
142
|
+
"annotations",
|
|
143
|
+
"effect",
|
|
144
|
+
"principal",
|
|
145
|
+
"resource",
|
|
146
|
+
"unless",
|
|
147
|
+
"when",
|
|
148
|
+
]);
|
|
149
|
+
// The marker in particular: baking one environment's ownership stamp into
|
|
150
|
+
// regenerated source would make every environment claim to be that one.
|
|
151
|
+
expect(JSON.stringify(owner.properties)).not.toContain("chant:managed-by");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("keeps the whole server record under verbatim", async () => {
|
|
155
|
+
const ir = await run({ verbatim: true });
|
|
156
|
+
const owner = ir.resources.find((r) => r.logicalId === "owner-read")!;
|
|
157
|
+
const avp = owner.properties.avp as Record<string, unknown>;
|
|
158
|
+
|
|
159
|
+
expect(avp.policyId).toBe("p-1");
|
|
160
|
+
expect(avp.policyStoreId).toBe(STORE);
|
|
161
|
+
expect(avp.lastUpdatedDate).toBe("2026-08-01T00:00:00Z");
|
|
162
|
+
expect(String(avp.description)).toContain("chant:managed-by");
|
|
163
|
+
expect(avp.statement).toBe(OWNER_READ);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("filters to chant-owned policies on owned:true", async () => {
|
|
167
|
+
const ir = await run({ owned: true });
|
|
168
|
+
expect(ir.resources.map((r) => r.logicalId)).toEqual(["owner-read"]);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("applies a name selector", async () => {
|
|
172
|
+
expect((await run({ selector: { name: "restrict-delete" } })).resources.map((r) => r.logicalId)).toEqual([
|
|
173
|
+
"restrict-delete",
|
|
174
|
+
]);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("applies a type selector, and yields nothing for another lexicon's type", async () => {
|
|
178
|
+
expect((await run({ selector: { type: CEDAR_POLICY_TYPE } })).resources).toHaveLength(2);
|
|
179
|
+
expect((await run({ selector: { type: "AWS::S3::Bucket" } })).resources).toEqual([]);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("orders resources deterministically, whatever order the store enumerates", async () => {
|
|
183
|
+
const ids = (await run()).resources.map((r) => r.logicalId);
|
|
184
|
+
expect(ids).toEqual([...ids].sort());
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("exports a store that does not exist yet as an empty template", async () => {
|
|
188
|
+
const ir = await run({ policyStoreId: "PS-other" });
|
|
189
|
+
expect(ir.resources).toEqual([]);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("throws rather than reporting an empty estate when the read fails", async () => {
|
|
193
|
+
const { http } = mockAvpTransport({
|
|
194
|
+
...store,
|
|
195
|
+
listFails: { code: "ThrottlingException", message: "Rate exceeded", status: 429 },
|
|
196
|
+
});
|
|
197
|
+
await expect(
|
|
198
|
+
exportAvpResources({ environment: "prod", policyStoreId: STORE, client: { http }, env: MOCK_ENV }),
|
|
199
|
+
).rejects.toThrow(/Rate exceeded/);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("throws when nothing binds the environment to a store", async () => {
|
|
203
|
+
const { http } = mockAvpTransport(store);
|
|
204
|
+
await expect(
|
|
205
|
+
exportAvpResources({ environment: "prod", client: { http }, env: MOCK_ENV }),
|
|
206
|
+
).rejects.toThrow(/no AVP policy store/);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("throws when there are no credentials", async () => {
|
|
210
|
+
const { http } = mockAvpTransport(store);
|
|
211
|
+
await expect(
|
|
212
|
+
exportAvpResources({ environment: "prod", policyStoreId: STORE, client: { http }, env: {} }),
|
|
213
|
+
).rejects.toThrow(/no AWS credentials/);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("drops a policy whose statement could not be read rather than exporting a hole", async () => {
|
|
217
|
+
const flaky = mockAvpTransport({
|
|
218
|
+
policyStoreId: STORE,
|
|
219
|
+
policies: [
|
|
220
|
+
{ policyId: "p-1", statement: OWNER_READ, getFails: true },
|
|
221
|
+
{ policyId: "p-2", statement: RESTRICT_DELETE },
|
|
222
|
+
],
|
|
223
|
+
});
|
|
224
|
+
const ir = await exportAvpResources({
|
|
225
|
+
environment: "prod",
|
|
226
|
+
policyStoreId: STORE,
|
|
227
|
+
client: { http: flaky.http },
|
|
228
|
+
env: MOCK_ENV,
|
|
229
|
+
});
|
|
230
|
+
expect(ir.resources.map((r) => r.logicalId)).toEqual(["restrict-delete"]);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `exportResources()` against an AVP policy store (#1652).
|
|
3
|
+
*
|
|
4
|
+
* `describeResources` returns scrubbed metadata for diffing — you cannot
|
|
5
|
+
* regenerate a policy from "STATIC, last updated Tuesday". This is the other
|
|
6
|
+
* half: the live statement, parsed back into the props a `Cedar::Policy`
|
|
7
|
+
* declaration carries, as `TemplateIR` so it feeds `templateGenerator()`
|
|
8
|
+
* unchanged. The scrubbing boundary stays single-purpose; nothing here is
|
|
9
|
+
* reachable from the lifecycle paths, which consume the lexicon through
|
|
10
|
+
* `ObservationLexicon` and cannot see this method at all.
|
|
11
|
+
*
|
|
12
|
+
* ## The parse is the import parser's
|
|
13
|
+
*
|
|
14
|
+
* The live-export guide's third step is "map to `TemplateIR` by reusing your
|
|
15
|
+
* import parser", and #1653 shipped one. So a live statement goes through
|
|
16
|
+
* `CedarTemplateParser` — the same code path `chant import policies.cedar`
|
|
17
|
+
* takes — rather than a second reader that would have to re-derive scope
|
|
18
|
+
* mapping, clause-text recovery and template detection, and would drift the
|
|
19
|
+
* first time either side changed. This module's job is the AVP half: bind the
|
|
20
|
+
* store, enumerate it, decide what is server-written, and apply the filters.
|
|
21
|
+
*
|
|
22
|
+
* ## What is server-written here
|
|
23
|
+
*
|
|
24
|
+
* The live-export guide says strip to the declared shape by default and keep
|
|
25
|
+
* the rest under `verbatim`. For AVP the split is unusually clean, because the
|
|
26
|
+
* only thing a user authors is the statement. Everything else on the record —
|
|
27
|
+
* `policyId` (service-assigned), `policyStoreId`, `policyType`, `createdDate`,
|
|
28
|
+
* `lastUpdatedDate`, and the derived `principal`/`resource` summaries AVP
|
|
29
|
+
* computes from the statement — is server-written and stripped by default.
|
|
30
|
+
* `Description` is stripped too: chant's marker lives in it, so exporting it
|
|
31
|
+
* verbatim into regenerated source would bake one environment's ownership stamp
|
|
32
|
+
* into the code that produces every environment.
|
|
33
|
+
*
|
|
34
|
+
* `verbatim: true` puts all of it back under an `avp` key, raw description
|
|
35
|
+
* included.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import type { ExportedTemplate, ResourceSelector } from "@intentius/chant/lexicon";
|
|
39
|
+
import type { ResourceIR } from "@intentius/chant/import/parser";
|
|
40
|
+
import { CedarTemplateParser } from "../import/adapter";
|
|
41
|
+
import { credentialsAvailable, storeDoesNotExist, type AvpClientOptions } from "./client";
|
|
42
|
+
import { descriptionIsOwned } from "./ownership";
|
|
43
|
+
import { loadLivePolicies, resolvePolicyStoreId, type AvpLivePolicy } from "./store";
|
|
44
|
+
|
|
45
|
+
export interface ExportAvpOptions {
|
|
46
|
+
environment: string;
|
|
47
|
+
selector?: ResourceSelector;
|
|
48
|
+
/** Restrict to policies carrying chant's description marker. */
|
|
49
|
+
owned?: boolean;
|
|
50
|
+
/** Keep the server-written AVP record alongside the authored props. */
|
|
51
|
+
verbatim?: boolean;
|
|
52
|
+
policyStoreId?: string;
|
|
53
|
+
entities?: Map<string, { entityType: string; props: Record<string, unknown> }>;
|
|
54
|
+
client?: AvpClientOptions;
|
|
55
|
+
env?: Record<string, string | undefined>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The server-written AVP record, kept only under `verbatim`. */
|
|
59
|
+
export interface AvpRecord {
|
|
60
|
+
policyId: string;
|
|
61
|
+
policyStoreId: string;
|
|
62
|
+
policyType: string;
|
|
63
|
+
description?: string;
|
|
64
|
+
createdDate?: string;
|
|
65
|
+
lastUpdatedDate?: string;
|
|
66
|
+
statement: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* One live policy as import IR, or `undefined` when its statement could not be
|
|
71
|
+
* read or parsed.
|
|
72
|
+
*
|
|
73
|
+
* The parse is `CedarTemplateParser`'s — one policy is a policy set of one, so
|
|
74
|
+
* the same reader `chant import` uses answers here, including whether Cedar
|
|
75
|
+
* read it as a static policy or as a template (`Cedar::Policy` vs
|
|
76
|
+
* `Cedar::Template`, decided by whether it carries a slot). The `logicalId` it
|
|
77
|
+
* assigns is the Cedar policy id; turning that into a TypeScript identifier is
|
|
78
|
+
* `CedarGenerator`'s job downstream, and doing it here too would apply the
|
|
79
|
+
* transform twice.
|
|
80
|
+
*
|
|
81
|
+
* Dropping an unparseable policy is deliberate. A regenerated policy set that
|
|
82
|
+
* silently lost a `forbid` reads as a working export and is an authorization
|
|
83
|
+
* hole; a missing entry is visible.
|
|
84
|
+
*/
|
|
85
|
+
export function policyToResourceIR(
|
|
86
|
+
policy: AvpLivePolicy,
|
|
87
|
+
options: { verbatim?: boolean } = {},
|
|
88
|
+
): ResourceIR | undefined {
|
|
89
|
+
if (!policy.statement) return undefined;
|
|
90
|
+
|
|
91
|
+
let parsed;
|
|
92
|
+
try {
|
|
93
|
+
parsed = new CedarTemplateParser().parse(policy.statement);
|
|
94
|
+
} catch {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
const resource = parsed.resources[0];
|
|
98
|
+
if (!resource) return undefined;
|
|
99
|
+
|
|
100
|
+
const properties: Record<string, unknown> = { ...resource.properties };
|
|
101
|
+
|
|
102
|
+
if (options.verbatim) {
|
|
103
|
+
const record: AvpRecord = {
|
|
104
|
+
policyId: policy.policyId,
|
|
105
|
+
policyStoreId: policy.policyStoreId,
|
|
106
|
+
policyType: policy.policyType,
|
|
107
|
+
...(policy.description !== undefined ? { description: policy.description } : {}),
|
|
108
|
+
...(policy.createdDate ? { createdDate: policy.createdDate } : {}),
|
|
109
|
+
...(policy.lastUpdatedDate ? { lastUpdatedDate: policy.lastUpdatedDate } : {}),
|
|
110
|
+
statement: policy.statement,
|
|
111
|
+
};
|
|
112
|
+
properties.avp = record;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
...resource,
|
|
117
|
+
properties,
|
|
118
|
+
metadata: { ...resource.metadata, avpPolicyId: policy.policyId },
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Read a live policy store as full-fidelity import IR.
|
|
124
|
+
*
|
|
125
|
+
* Unlike the observation, this throws when it cannot read: `chant import` has
|
|
126
|
+
* no tri-state to degrade into, and an empty template returned from a failed
|
|
127
|
+
* read would generate a source tree that deletes every policy. The one
|
|
128
|
+
* exception is a store that does not exist, which is an honest empty estate.
|
|
129
|
+
*/
|
|
130
|
+
export async function exportAvpResources(options: ExportAvpOptions): Promise<ExportedTemplate> {
|
|
131
|
+
const env = options.env ?? process.env;
|
|
132
|
+
const policyStoreId = resolvePolicyStoreId({
|
|
133
|
+
environment: options.environment,
|
|
134
|
+
...(options.policyStoreId ? { policyStoreId: options.policyStoreId } : {}),
|
|
135
|
+
...(options.entities ? { entities: options.entities } : {}),
|
|
136
|
+
env,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (!policyStoreId) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`no AVP policy store for environment "${options.environment}" — set CEDAR_AVP_POLICY_STORE_ID or pass policyStoreId`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
if (!credentialsAvailable(env)) {
|
|
145
|
+
throw new Error("no AWS credentials and no endpoint override — cannot export from AVP");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let policies: AvpLivePolicy[];
|
|
149
|
+
try {
|
|
150
|
+
policies = await loadLivePolicies({
|
|
151
|
+
policyStoreId,
|
|
152
|
+
...(options.client ? { client: options.client } : {}),
|
|
153
|
+
withStatements: true,
|
|
154
|
+
});
|
|
155
|
+
} catch (err) {
|
|
156
|
+
if (storeDoesNotExist(err)) policies = [];
|
|
157
|
+
else throw err;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const resources: ResourceIR[] = [];
|
|
161
|
+
for (const policy of policies) {
|
|
162
|
+
// Ownership is read on this path, which is what lets it be declared in
|
|
163
|
+
// `ownershipChannel.reads` (chant #1348).
|
|
164
|
+
if (options.owned && !descriptionIsOwned(policy.description)) continue;
|
|
165
|
+
|
|
166
|
+
const ir = policyToResourceIR(policy, { ...(options.verbatim ? { verbatim: true } : {}) });
|
|
167
|
+
if (!ir) continue;
|
|
168
|
+
// The type is compared against what the parser decided, not a constant:
|
|
169
|
+
// a policy carrying a `?principal`/`?resource` slot comes back as
|
|
170
|
+
// `Cedar::Template`, and `--type Cedar::Policy` should not sweep it up.
|
|
171
|
+
if (options.selector?.type !== undefined && options.selector.type !== ir.type) continue;
|
|
172
|
+
if (options.selector?.name !== undefined && ir.logicalId !== options.selector.name) continue;
|
|
173
|
+
resources.push(ir);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Stable order: the store's enumeration order is not a contract, and an
|
|
177
|
+
// import that reshuffles the file on every run is unusable as a diff.
|
|
178
|
+
resources.sort((a, b) => a.logicalId.localeCompare(b.logicalId));
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
resources,
|
|
182
|
+
parameters: [],
|
|
183
|
+
metadata: { lexicon: "cedar", policyStoreId },
|
|
184
|
+
};
|
|
185
|
+
}
|