From 0778befa60efddc7626ea4937a5cb1c9dfcfadc1 Mon Sep 17 00:00:00 2001 From: oflatt Date: Mon, 29 Jul 2024 10:57:46 -0700 Subject: [PATCH 01/20] add entity slicing rfc Signed-off-by: oflatt --- text/0073-entity-slicing.md | 562 ++++++++++++++++++++++++++++++++++++ 1 file changed, 562 insertions(+) create mode 100644 text/0073-entity-slicing.md diff --git a/text/0073-entity-slicing.md b/text/0073-entity-slicing.md new file mode 100644 index 00000000..e6692502 --- /dev/null +++ b/text/0073-entity-slicing.md @@ -0,0 +1,562 @@ +# RFC: Entity Slicing using Data Tries + +## Related Issues + +- Reference Issues: [#592](https://github.com/cedar-policy/cedar/issues/592) +- Implementation PR(s): (leave this empty) + +## Timeline + +* Start Date: 2024-06-17 +* Date Entered FCP: +* Date Accepted: +* Date Landed: + +## Summary + +This RFC proposes a new API for finding an **entity slice**: a subset of the entity store needed for a particular request. +Specifically, we propose a new function with signature `(Schema, Policies) -> EntityManifest`. There are more details on the `EntityManifest` later in this document, but the key idea is to store data paths **indexed by the request type**. The `Policies` must validate against the `Schema` in strict mode. + +The rest of this document follows the following format: + +* **Motivation** +* **Example**- example policies show what data paths are needed for what request types. +* **Example Usage**- we’ll show an example use-case for the entity manifest to put the design in context. +* **Data Structure-** we’ll give details of the entity manifest data structure. +* **Computing the Entity Manifest-** we’ll describe the static analysis used to generate the entity manifest. +* **SimplifiedEntityLoader API**- we’ll describe a simple way to use the entity manifest. +* **Drawbacks**- the entity manifest has several limitations to discuss. + +## Motivation + +Applications using Cedar need to provide entity data along with an authorization request. However, it is cost-prohibitive to provide all of an application’s data on every request, so users will want to provide a minimum amount, a *slice*. This proposal demonstrates a way to provide a **sound** slice: users provide a subset of the data, but get the same answer as if the full dataset had been provided. + +The design of entity slicing in this document is meant to achieve several goals: + +1. **Easy to use**- Leveraging entity slicing should be easy for Cedar users in a variety of settings +2. **Performant**- Implementing the slice constraints improves performance without introducing too much overhead. +3. **Flexible**- Entity slicing should work on a variety of data storage formats. + + + +## Example + +Here is a schema involving users and documents: + +``` +entity User in [User]; + +entity Metadata = { + owner: User, + time: String, +}; + +entity Document = { + metadata: Metadata, + readers: Set, +}; + +action Read, Edit appliesTo { + principal: [User], + resource: [Document], +}; +``` + + +Here are a couple cedar policies conforming to the schema: + +``` +// allow documents to be read by anyone in the readers set +permit( + principal, + action == Action::"Read", + resource +) +when +{ + resource.readers.contains(principal) +}; + +// allow documents to be read and edited by owners +permit( + principal, + action in [Action::"Read", Action::"Edit"], + resource +) +when +{ + resource.metadata.owner == principal +}; + +// allow read if the user's ancestor is a global admin +permit( + principal, + action in [Action::"Read"], + resource +) +when +{ + principal in User::"GlobalAdmin" +}; +``` + +Now, we would like to answer the question: given a request that a **User** would like to **Read** a **Document,** what data is required? + +To answer the request correctly, Cedar requires two data paths and one entity’s ancestors in the entity store: + +``` +Request type: (User, Read, Document) +Data required: + resource.readers + resource.metadata.owner + principal.**ancestors** (where ancestors are the ancestors in the entity hierarchy) +``` + +On the other hand, if the **User** would like to **Edit** a **Document**, we only need one piece of data: + +``` +Request type: (User, Edit, Document) +Data required: + resource.metadata.owner +``` + + + +## Example Usage + + +The image above shows an example usage of the entity manifest. On the left, a client has access to the entity data and a cached entity manifest. On the right, a server stores the schema and policies. + +When a client would like to answer a cedar `Request`, it first consults the cached entity manifest to load the entity slice. Then, it sends the entity slice and the request to the server. The server returns the authorization result. + +However, the server or client must take care to ensure that the client’s entity manifest is up-to-date. Since the entity manifest is computed based on the schema and policies, is must be re-computed whenever the schema or policies change. There are multiple ways to ensure that the cache is up to date, one being to tag each entity manifest uniquely and check on each request. + +Looking to the future, we hope to provide several options and guidence for how to use the entity manifest: + +1. Use the `SimplifiedEntityLoader` api (see later section). +2. Use a specialized entity loader when the format of the database sufficiently matches the schema. +3. Write custom entity loading that leverages the entity manifest. + + + +## Data structure + +This document proposes the **Entity Manifest**, a data structure that specifies the data needed for each type of request. To reduce redundant access paths, the manifest is stored as a Trie. + +``` +type EntityManifest = HashMap +``` + +`RequestType` stores the type of the principal, action, and resource. This allows entity slicing to be specific to the type of the request, greatly reducing the amount of data required. (The context type can be inferred from the action UID) + +``` +pub struct RequestType { + pub principal: EntityType, + pub action: EntityUID, + pub resource: EntityType, +} +``` + + +The `RequestEntityManifest` is a trie storing all the access paths required for the given request type. Access paths all start with Cedar variables and entity literals: + +``` +/// The root of an entity slice. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)] +pub enum EntityRoot { + /// Literal entity ids + Literal(EntityUID), + /// A Cedar variable + Var(Var), +} + + +/// a [`RequestEntityManifest`] is a trie that specifies what data to load +#[serde_as] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct RequestEntityManifest { + #[serde_as(as = "Vec<(_, _)>")] + /// The data that needs to be loaded, organized by root + pub trie: HashMap, +} +``` + + +Finally, we have the `AccessTrie` data structure, which stores the access paths trie. These paths can also request parents in the entity hierarchy. Note: in the implementation, the entity slice also stores optional type annotations. + +``` +pub struct AccessTrie { + /// Child data of this entity slice. + pub children: HashMap>, + /// If the parents in the entity hierarchy are required. + pub parents_required: bool +} +``` + + + + +## Computing the Entity Manifest: High Level + +Computing the entity manifest has two parts: **calculating access paths** and **merging** **access paths.** +For calculating access paths, we write a static analysis that finds all the relevant access paths in the policies. Cedar has only four variables (`principal`, `resource`, `action`, and `context` ), so all data is loaded starting with a variable or entity literal. +For merging access paths, we define a method for merging access paths, sharing common prefixes in a Trie. + +The high-level algorithm looks like this: + +1. First, perform Cedar type-checking to get type-annotated policies that are specific to the type of the request. +2. For each type of request + 1. For each policy with that request type + 1. **Compute access paths** + 2. **Merge** all of the access paths into a single entity slice trie + + +The merge operator for access paths is straightforward. First, convert all access paths directly to a Trie, where the fields in the path are the edges in the Trie. Then, use the standard merge operator on Tries. + + +## Computing access paths + +This section proposes a simple static analysis that soundly computes all of the necessary access paths for a cedar expression. +While it’s possible to soundly handle all Cedar expressions, we simplify the problem by rejecting Cedar programs unless they follow the following grammar: + + +``` + = + in + + + if { } { } + ... all other cedar operators not mentioned by datapath-expr + + = . + has + + + + = principal + resource + action + context + +``` + + +The grammar partitions cedar expressions into **datapath expressions** and all other cedar operators. The partition is reasonable since, in practice, users do not interleave datapath expressions with other operators. +Here are examples of cedar expressions rejected by this grammar: + + +``` +{ "myfield": principal.name }.myfield +(if principal.ismanager then { "myfield" : 2 } else { "myfield": 3 }).myfield +(if principal has mobilePhone then principal.mobilePhone else principal.workPhone).zipCode +``` + + +Now that we have this partition, computing all the access paths is fairly simple: + +1. First, find all access path expressions. These can be translated into access paths directly. +2. Second, handle all instances of equality between records. For equality between records (`==`) or set containment where the elements are records (`.contains`, `.containsAny`, and `.containsAll`) + 1. Find all access paths that are children of these operators + 1. Following the schema, fully load all fields for the leaf of the access path +3. Finally, handle instances where the ancestors in the entity hierarchy are required. Annotate the left-hand-side of the `in` operator with the `parents_required` flag. + +## + +## SimplifiedEntityLoader API + +Having the entity manifest enables different data loading strategies that are less precise, but easy to implement. +The easiest is the `SimpleEntityLoader` api shown here. Users need only write a function for loading entities via their IDs, and Cedar handles determining which entities need to be loaded. + + +``` +/// Implement this trait to efficiently load entities based on +/// the entity manifest. +/// This entity loader is called "Simple" for two reasons: +/// 1) First, it is not synchronous- `load_entity` is called multiple times. +/// 2) Second, it is not precise- the entity manifest only requires some +/// fields to be loaded. +pub trait SimpleEntityLoader { + /// Simple entity loaders must implement `load_entity`, + /// a function that loads an entities based on their EntityUIDs. + /// For each element of `entity_ids`, returns the corresponding + /// [`Entity`] in the output vector. + fn load_entity(&mut self, entity_ids: &[&EntityUID]) -> Vec; + + /// Loads all the entities needed for a request + /// using the `load_entity` function. + fn load( + &mut self, + schema: &Schema, + entity_manifest: &EntityManifest, + request: &Request, + ) -> Result { + ... + } +} +``` + + + + +## Drawbacks + +### Risk in unverified code + +Increasing the complexity of the Cedar API with entity manifests provides new places for users to make mistakes. For example, using an outdated entity manifest can result in unsound entity slices. Another example would be a user writing a buggy implementation of entity loading using the manifest. + + +### Full Cedar Language Support + +The current analysis does not support the full Cedar language (see the **Computing Access Paths** section). With some effort, the implementation could be extended to support all of Cedar by more carefully tracking access paths through different types of Cedar operations. However, it’s unclear just how tricky this will be. + +Along a similar note, all Cedar changes in the future will need a corresponding change in the static analysis, making it harder to extend Cedar. For example, any operations that apply to records will need careful consideration. + + +### Supporting Partial Loading of Sets + +As written, entity manifests in this RFC do not support loading only parts of a Cedar Set, or only some of the parents in the entity hierarchy. This is because sets are loaded on the leaves of the access trie, with no way to specify which elements are requested. + +To support this feature, we recommend that we take a constraint-based approach. Constraints would be attached to nodes in the access trie, specifying which elements of sets or the parent hierarchy are needed. Constraints form a small query language, so this would increase the complexity of the entity manifest. + + + + + +# Extended examples + +## Paths needed in document_cloud example + +``` +Action::"CreateDocument" +[ + context.is_authenticated +] + +Action::"ViewDocument" +PrincipalType1 +ResourceType1 +[ + principal.blocked + resource.owner.blocked + resource.viewACL + resource.private + resource.publicAccess + resrouce.isPrivate + context.is_authenticated +] + +Action::"ModifyDocument" +[ + principal.blocked + resource.owner.blocked + resource.modifyACL + resource.isPrivate + context.is_authenticated +] + +action::"EditIsPrivate" +[ + principal.blocked + resource.owner.blocked + resource.isPrivate + context.is_authenticated +] + +action::"AddToShareACL" +[ + principal.blocked + resource.owner.blocked + resource.manageACL + resource.isPrivate + context.is_authenticated +] + +action::"EditPublicAccess" +[ + principal.blocked + resource.owner.blocked + resource.manageACL + resource.isPrivate + context.is_authenticated +] + +action::"CreateGroup" +[ + resource.isPrivate + context.is_authenticated +] + +action::"ModifyGroup" +[ + resource.owner + resource.isPrivate + context.is_authenticated +] + +action::"DeleteGroup" +[ + resource.owner + resource.isPrivate + context.is_authenticated +] +``` + + + + +## Paths needed in tags_n_roles + +``` +Action::"Role-A Actions" +principal.allowedTagsForRole +principal.allowedTagsForRole["Role-A"] +principal.allowedTagsForRole["Role-A"]["stage"] +principal.allowedTagsForRole["Role-A"].production_status +principal.allowedTagsForRole["Role-A"]["country"] + +resource.tags +resource.tags["production_status"] +resource.tags.country +resource.tags.stage +``` + + + + +``` +Action::"Role-B Actions" +principal.allowedTagsForRole +principal.allowedTagsForRole["Role-B"] +principal.allowedTagsForRole["Role-B"]["stage"] +principal.allowedTagsForRole["Role-B"].production_status +principal.allowedTagsForRole["Role-B"]["country"] + +resource.tags +resource.tags["production_status"] +resource.tags.country +resource.tags.stage + +``` + + + +## Paths needed in github_example + +using "parents" as cedar's ancestor relation- requires all ancestors transitively + + +``` +Action::"pull" +resource.readers.parents + + +Action::"fork" +resource.readers.parents + +Action::"delete_issue" +resource.repo.readers.parents +resource.reporter + +Action::"assign_issue" +resource.repo.triagers + +Action::"push" +resource.writers + +Action::"edit_issue" +resource.repo.writers + +Action::"delete_issue" +resource.repo.maintainers + +Action::"add_reader" ect +resource.admins.parents +``` + + + +## Paths needed in tax_preparer + +``` +Action::"viewDocument" +principal.assigned_orgs ;; could constrain this one to only pull a single element if we run resource first +principal.location +resource.owner.organization +resource.serviceline +resource.location + +context.consent.client +context.conset.team_region_list + +;; ad-hoc rules luckily don't require more information +``` + + + +## Paths needed in sales_orgs + +``` +Action::"ExternalPrezViewActions" +resource.viewers ;; opportunity to use constraints to pull in only principal in the set + +Action::"InternalPrezViewActions" +principal.job +resource.viewers ;; similar constraint opportunity + +Action::"PrezEditActions" +resource.owner +resource.editors + +Action::"grantViewAccessToPresentation" +context.target.job +context.target.customerId +principal.job +principal.customerId + +Action::"grantEditAccessToPresentation" +context.target.job + +Action::"MarketTemplateViewActions" +resource.viewerMarkets + +Action::"InternalTemplateViewActions" +principal.job +resource.viewers ;; could limit to principal in the set + +Action::"TemplateEditActions" +resource.owner +resource.editors +resource.editorMarkets + +Action::"grantViewAccessToTemplate" +context.targetUser.job +context.targetUser.customerId +principal.job +principal.customerId + +Action::"grantEditAccessToTemplate" +context.targetUser.job +``` + + + + +## Paths needed in hotel_chains + +``` +Action::"viewReservation" ect +principal.viewPermissions.hotelReservations ;; opportunity for set constraint +principal.viewPermissions.propertyReservations ;; opportunity for set constraint +principal.hotelAdminPermissions ;; opportunity for set constraint +principal.propertyAdminPermissions ;; opportunity for set constraint + + +Action::"viewProperty" ect +principal.viewPermissions.hotelReservations ;; opportunity for set constraint +principal.viewPermissions.propertyReservations ;; opportunity for set constraint +principal.hotelAdminPermissions ;; opportunity for set constraint +principal.propertyAdminPermissions ;; opportunity for set constraint +``` + + + + From d33585dfbcb1c32043a37b58c30a73d79f1b3467 Mon Sep 17 00:00:00 2001 From: Oliver Flatt Date: Tue, 30 Jul 2024 12:56:42 -0700 Subject: [PATCH 02/20] Update 0073-entity-slicing.md Signed-off-by: oflatt --- text/0073-entity-slicing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0073-entity-slicing.md b/text/0073-entity-slicing.md index e6692502..3818a251 100644 --- a/text/0073-entity-slicing.md +++ b/text/0073-entity-slicing.md @@ -3,7 +3,7 @@ ## Related Issues - Reference Issues: [#592](https://github.com/cedar-policy/cedar/issues/592) -- Implementation PR(s): (leave this empty) +- Implementation PR(s): [#1102](https://github.com/cedar-policy/cedar/pull/1102) ## Timeline From 0b89ff35081072dcc6a734eebeb4c9b2b94f85c1 Mon Sep 17 00:00:00 2001 From: Oliver Flatt Date: Thu, 1 Aug 2024 10:34:50 -0700 Subject: [PATCH 03/20] Update text/0073-entity-slicing.md Co-authored-by: Craig Disselkoen Signed-off-by: oflatt --- text/0073-entity-slicing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0073-entity-slicing.md b/text/0073-entity-slicing.md index 3818a251..652d942b 100644 --- a/text/0073-entity-slicing.md +++ b/text/0073-entity-slicing.md @@ -63,7 +63,7 @@ action Read, Edit appliesTo { ``` -Here are a couple cedar policies conforming to the schema: +Here are a couple Cedar policies conforming to the schema: ``` // allow documents to be read by anyone in the readers set From d634187cc55df29c50d687db7b5a62eb6518cc9c Mon Sep 17 00:00:00 2001 From: Oliver Flatt Date: Thu, 1 Aug 2024 10:35:01 -0700 Subject: [PATCH 04/20] Update text/0073-entity-slicing.md Co-authored-by: Craig Disselkoen Signed-off-by: oflatt --- text/0073-entity-slicing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0073-entity-slicing.md b/text/0073-entity-slicing.md index 652d942b..8b4c78e1 100644 --- a/text/0073-entity-slicing.md +++ b/text/0073-entity-slicing.md @@ -131,7 +131,7 @@ When a client would like to answer a cedar `Request`, it first consults the cach However, the server or client must take care to ensure that the client’s entity manifest is up-to-date. Since the entity manifest is computed based on the schema and policies, is must be re-computed whenever the schema or policies change. There are multiple ways to ensure that the cache is up to date, one being to tag each entity manifest uniquely and check on each request. -Looking to the future, we hope to provide several options and guidence for how to use the entity manifest: +Looking to the future, we hope to provide several options and guidance for how to use the entity manifest: 1. Use the `SimplifiedEntityLoader` api (see later section). 2. Use a specialized entity loader when the format of the database sufficiently matches the schema. From 01905a4cdfead18eee0463e0770dcbf358439a36 Mon Sep 17 00:00:00 2001 From: Oliver Flatt Date: Thu, 1 Aug 2024 10:35:06 -0700 Subject: [PATCH 05/20] Update text/0073-entity-slicing.md Co-authored-by: Craig Disselkoen Signed-off-by: oflatt --- text/0073-entity-slicing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0073-entity-slicing.md b/text/0073-entity-slicing.md index 8b4c78e1..6ec11a49 100644 --- a/text/0073-entity-slicing.md +++ b/text/0073-entity-slicing.md @@ -276,7 +276,7 @@ The easiest is the `SimpleEntityLoader` api shown here. Users need only write a /// fields to be loaded. pub trait SimpleEntityLoader { /// Simple entity loaders must implement `load_entity`, - /// a function that loads an entities based on their EntityUIDs. + /// a function that loads entities based on their `EntityUID`s. /// For each element of `entity_ids`, returns the corresponding /// [`Entity`] in the output vector. fn load_entity(&mut self, entity_ids: &[&EntityUID]) -> Vec; From e20d942980c487efb2fe07c34f958191452e83cd Mon Sep 17 00:00:00 2001 From: Oliver Flatt Date: Thu, 1 Aug 2024 10:35:11 -0700 Subject: [PATCH 06/20] Update text/0073-entity-slicing.md Co-authored-by: Craig Disselkoen Signed-off-by: oflatt --- text/0073-entity-slicing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0073-entity-slicing.md b/text/0073-entity-slicing.md index 6ec11a49..9f48bac8 100644 --- a/text/0073-entity-slicing.md +++ b/text/0073-entity-slicing.md @@ -279,7 +279,7 @@ pub trait SimpleEntityLoader { /// a function that loads entities based on their `EntityUID`s. /// For each element of `entity_ids`, returns the corresponding /// [`Entity`] in the output vector. - fn load_entity(&mut self, entity_ids: &[&EntityUID]) -> Vec; + fn load_entity(&mut self, entity_ids: impl IntoIterator) -> impl Iterator; /// Loads all the entities needed for a request /// using the `load_entity` function. From 31903b77add23e183d3c969d57d4857c4a4b705d Mon Sep 17 00:00:00 2001 From: Oliver Flatt Date: Thu, 1 Aug 2024 10:35:19 -0700 Subject: [PATCH 07/20] Update text/0073-entity-slicing.md Co-authored-by: Craig Disselkoen Signed-off-by: oflatt --- text/0073-entity-slicing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0073-entity-slicing.md b/text/0073-entity-slicing.md index 9f48bac8..baf4befe 100644 --- a/text/0073-entity-slicing.md +++ b/text/0073-entity-slicing.md @@ -273,7 +273,7 @@ The easiest is the `SimpleEntityLoader` api shown here. Users need only write a /// This entity loader is called "Simple" for two reasons: /// 1) First, it is not synchronous- `load_entity` is called multiple times. /// 2) Second, it is not precise- the entity manifest only requires some -/// fields to be loaded. +/// fields to be loaded, but this loader always loads all fields. pub trait SimpleEntityLoader { /// Simple entity loaders must implement `load_entity`, /// a function that loads entities based on their `EntityUID`s. From afbb92a3cc6f6c716853debee70b6828dac795a9 Mon Sep 17 00:00:00 2001 From: Oliver Flatt Date: Thu, 1 Aug 2024 10:35:28 -0700 Subject: [PATCH 08/20] Update text/0073-entity-slicing.md Co-authored-by: Craig Disselkoen Signed-off-by: oflatt --- text/0073-entity-slicing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0073-entity-slicing.md b/text/0073-entity-slicing.md index baf4befe..046df61c 100644 --- a/text/0073-entity-slicing.md +++ b/text/0073-entity-slicing.md @@ -147,7 +147,7 @@ This document proposes the **Entity Manifest**, a data structure that specifies type EntityManifest = HashMap ``` -`RequestType` stores the type of the principal, action, and resource. This allows entity slicing to be specific to the type of the request, greatly reducing the amount of data required. (The context type can be inferred from the action UID) +`RequestType` stores the principal type, resource type, and action UID. This allows entity slicing to be specific to the type of the request, greatly reducing the amount of data required. (The context type can be inferred from the action UID and schema.) ``` pub struct RequestType { From da15abc1995023997064671ce9683084dedf1599 Mon Sep 17 00:00:00 2001 From: Oliver Flatt Date: Thu, 1 Aug 2024 10:35:41 -0700 Subject: [PATCH 09/20] Update text/0073-entity-slicing.md Co-authored-by: Craig Disselkoen Signed-off-by: oflatt --- text/0073-entity-slicing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0073-entity-slicing.md b/text/0073-entity-slicing.md index 046df61c..48d274d7 100644 --- a/text/0073-entity-slicing.md +++ b/text/0073-entity-slicing.md @@ -240,7 +240,7 @@ While it’s possible to soundly handle all Cedar expressions, we simplify the p ``` -The grammar partitions cedar expressions into **datapath expressions** and all other cedar operators. The partition is reasonable since, in practice, users do not interleave datapath expressions with other operators. +The grammar partitions Cedar expressions into **datapath expressions** and all other Cedar operators. The partition is reasonable since, in practice, users do not interleave datapath expressions with other operators. Here are examples of cedar expressions rejected by this grammar: From 3d6b22d6d98718ddd2c27781c5d650c319e29ed9 Mon Sep 17 00:00:00 2001 From: Oliver Flatt Date: Thu, 1 Aug 2024 10:35:53 -0700 Subject: [PATCH 10/20] Update text/0073-entity-slicing.md Co-authored-by: Craig Disselkoen Signed-off-by: oflatt --- text/0073-entity-slicing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0073-entity-slicing.md b/text/0073-entity-slicing.md index 48d274d7..edc717f7 100644 --- a/text/0073-entity-slicing.md +++ b/text/0073-entity-slicing.md @@ -241,7 +241,7 @@ While it’s possible to soundly handle all Cedar expressions, we simplify the p The grammar partitions Cedar expressions into **datapath expressions** and all other Cedar operators. The partition is reasonable since, in practice, users do not interleave datapath expressions with other operators. -Here are examples of cedar expressions rejected by this grammar: +Here are examples of Cedar expressions rejected by this grammar: ``` From 3d0365e2dac32d3705667390bfdc7fa38db74aef Mon Sep 17 00:00:00 2001 From: oflatt Date: Thu, 1 Aug 2024 10:37:03 -0700 Subject: [PATCH 11/20] Changes based on great feedback from @cdisselkoen Signed-off-by: oflatt --- text/0073-entity-slicing.md | 54 +++++++++++++++++++----------------- text/0073/clientserver.png | Bin 0 -> 73924 bytes 2 files changed, 29 insertions(+), 25 deletions(-) create mode 100644 text/0073/clientserver.png diff --git a/text/0073-entity-slicing.md b/text/0073-entity-slicing.md index edc717f7..7bcbdef2 100644 --- a/text/0073-entity-slicing.md +++ b/text/0073-entity-slicing.md @@ -14,8 +14,12 @@ ## Summary -This RFC proposes a new API for finding an **entity slice**: a subset of the entity store needed for a particular request. -Specifically, we propose a new function with signature `(Schema, Policies) -> EntityManifest`. There are more details on the `EntityManifest` later in this document, but the key idea is to store data paths **indexed by the request type**. The `Policies` must validate against the `Schema` in strict mode. +This RFC proposes a new API for describing an **entity slice**: a subset of the entity store needed for a particular request. +This RFC doesn't handle loading the entity slice itself, +instead proposing a data structure called an Entity Manifest. +The Entity manifest describes what entity data is needed +independent of how it is stored. +Specifically, we propose a new function with signature `(Schema, Policies) -> EntityManifest`. The `Policies` must validate against the `Schema` in strict mode. The rest of this document follows the following format: @@ -88,16 +92,12 @@ when resource.metadata.owner == principal }; -// allow read if the user's ancestor is a global admin +// allow read if the user is a global admin permit( - principal, + principal in User::"GlobalAdmin", action in [Action::"Read"], resource -) -when -{ - principal in User::"GlobalAdmin" -}; +); ``` Now, we would like to answer the question: given a request that a **User** would like to **Read** a **Document,** what data is required? @@ -124,10 +124,11 @@ Data required: ## Example Usage +![A client communicates with a Cedar Server](clientserver.png) The image above shows an example usage of the entity manifest. On the left, a client has access to the entity data and a cached entity manifest. On the right, a server stores the schema and policies. -When a client would like to answer a cedar `Request`, it first consults the cached entity manifest to load the entity slice. Then, it sends the entity slice and the request to the server. The server returns the authorization result. +When a client would like to answer a Cedar `Request`, it first consults the cached entity manifest to load the entity slice. Then, it sends the entity slice and the request to the server. The server returns the authorization result. However, the server or client must take care to ensure that the client’s entity manifest is up-to-date. Since the entity manifest is computed based on the schema and policies, is must be re-computed whenever the schema or policies change. There are multiple ways to ensure that the cache is up to date, one being to tag each entity manifest uniquely and check on each request. @@ -182,14 +183,14 @@ pub struct RequestEntityManifest { ``` -Finally, we have the `AccessTrie` data structure, which stores the access paths trie. These paths can also request parents in the entity hierarchy. Note: in the implementation, the entity slice also stores optional type annotations. +Finally, we have the `AccessTrie` data structure, which stores the access paths trie. These paths can also request ancestors in the entity hierarchy. Note: in the implementation, the entity slice also stores optional type annotations. ``` pub struct AccessTrie { /// Child data of this entity slice. pub children: HashMap>, - /// If the parents in the entity hierarchy are required. - pub parents_required: bool + /// If the ancestors in the entity hierarchy are required. + pub ancestors_required: bool } ``` @@ -216,7 +217,7 @@ The merge operator for access paths is straightforward. First, convert all acces ## Computing access paths -This section proposes a simple static analysis that soundly computes all of the necessary access paths for a cedar expression. +This section proposes a simple static analysis that soundly computes all of the necessary access paths for a Cedar expression. While it’s possible to soundly handle all Cedar expressions, we simplify the problem by rejecting Cedar programs unless they follow the following grammar: @@ -225,7 +226,7 @@ While it’s possible to soundly handle all Cedar expressions, we simplify the p in + if { } { } - ... all other cedar operators not mentioned by datapath-expr + ... all other Cedar operators not mentioned by datapath-expr = . has @@ -240,7 +241,10 @@ While it’s possible to soundly handle all Cedar expressions, we simplify the p ``` -The grammar partitions Cedar expressions into **datapath expressions** and all other Cedar operators. The partition is reasonable since, in practice, users do not interleave datapath expressions with other operators. +The grammar partitions Cedar expressions into **datapath expressions** and all other Cedar operators. +On the LHS of `in`, `.`, and `has`, expressions involving `in`, `+`, `if-then-else`, and most other Cedar functions/operators are not allowed; only Cedar variables, entity literals, and `.` or `has` expressions are allowed. + +The partition is reasonable since, in practice, users do not interleave datapath expressions with other operators. Here are examples of Cedar expressions rejected by this grammar: @@ -253,11 +257,11 @@ Here are examples of Cedar expressions rejected by this grammar: Now that we have this partition, computing all the access paths is fairly simple: -1. First, find all access path expressions. These can be translated into access paths directly. +1. First, find all datapath expressions. These can be translated into access paths directly. 2. Second, handle all instances of equality between records. For equality between records (`==`) or set containment where the elements are records (`.contains`, `.containsAny`, and `.containsAll`) 1. Find all access paths that are children of these operators 1. Following the schema, fully load all fields for the leaf of the access path -3. Finally, handle instances where the ancestors in the entity hierarchy are required. Annotate the left-hand-side of the `in` operator with the `parents_required` flag. +3. Finally, handle instances where the ancestors in the entity hierarchy are required. Annotate the left-hand-side of the `in` operator with the `ancestors_required` flag. ## @@ -268,7 +272,7 @@ The easiest is the `SimpleEntityLoader` api shown here. Users need only write a ``` -/// Implement this trait to efficiently load entities based on +/// Implement this trait to load entities based on /// the entity manifest. /// This entity loader is called "Simple" for two reasons: /// 1) First, it is not synchronous- `load_entity` is called multiple times. @@ -313,7 +317,7 @@ Along a similar note, all Cedar changes in the future will need a corresponding ### Supporting Partial Loading of Sets -As written, entity manifests in this RFC do not support loading only parts of a Cedar Set, or only some of the parents in the entity hierarchy. This is because sets are loaded on the leaves of the access trie, with no way to specify which elements are requested. +As written, entity manifests in this RFC do not support loading only parts of a Cedar Set, or only some of the ancestors in the entity hierarchy. This is because sets are loaded on the leaves of the access trie, with no way to specify which elements are requested. To support this feature, we recommend that we take a constraint-based approach. Constraints would be attached to nodes in the access trie, specifying which elements of sets or the parent hierarchy are needed. Constraints form a small query language, so this would increase the complexity of the entity manifest. @@ -441,19 +445,19 @@ resource.tags.stage ## Paths needed in github_example -using "parents" as cedar's ancestor relation- requires all ancestors transitively +using "ancestors" as Cedar's ancestor relation- requires all ancestors transitively ``` Action::"pull" -resource.readers.parents +resource.readers.ancestors Action::"fork" -resource.readers.parents +resource.readers.ancestors Action::"delete_issue" -resource.repo.readers.parents +resource.repo.readers.ancestors resource.reporter Action::"assign_issue" @@ -469,7 +473,7 @@ Action::"delete_issue" resource.repo.maintainers Action::"add_reader" ect -resource.admins.parents +resource.admins.ancestors ``` diff --git a/text/0073/clientserver.png b/text/0073/clientserver.png new file mode 100644 index 0000000000000000000000000000000000000000..756ea4cb623c3d5fb4da67d1655187057b1b5726 GIT binary patch literal 73924 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 KKI^LS!~X+>r`hBH literal 0 HcmV?d00001 From 08727fc8fbd03e49588271f4a2b87d8f2cea812a Mon Sep 17 00:00:00 2001 From: oflatt Date: Thu, 1 Aug 2024 10:38:41 -0700 Subject: [PATCH 12/20] fix image Signed-off-by: oflatt --- text/{ => 0073}/0073-entity-slicing.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename text/{ => 0073}/0073-entity-slicing.md (100%) diff --git a/text/0073-entity-slicing.md b/text/0073/0073-entity-slicing.md similarity index 100% rename from text/0073-entity-slicing.md rename to text/0073/0073-entity-slicing.md From 9ba753cad1365aa1aab8b7f2c914772146e38d08 Mon Sep 17 00:00:00 2001 From: oflatt Date: Thu, 1 Aug 2024 10:41:45 -0700 Subject: [PATCH 13/20] avoid synchronous Signed-off-by: oflatt --- text/0073/0073-entity-slicing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0073/0073-entity-slicing.md b/text/0073/0073-entity-slicing.md index 7bcbdef2..bb309f5b 100644 --- a/text/0073/0073-entity-slicing.md +++ b/text/0073/0073-entity-slicing.md @@ -275,7 +275,7 @@ The easiest is the `SimpleEntityLoader` api shown here. Users need only write a /// Implement this trait to load entities based on /// the entity manifest. /// This entity loader is called "Simple" for two reasons: -/// 1) First, it is not synchronous- `load_entity` is called multiple times. +/// 1) First, it may cause database consistency challenges- `load_entity` is called multiple times. /// 2) Second, it is not precise- the entity manifest only requires some /// fields to be loaded, but this loader always loads all fields. pub trait SimpleEntityLoader { From 4dcbff908bf7ea5df82eb9c001b137cd355216a4 Mon Sep 17 00:00:00 2001 From: oflatt Date: Thu, 1 Aug 2024 11:48:46 -0700 Subject: [PATCH 14/20] re-name to 0074 Signed-off-by: oflatt --- .../0074-entity-slicing.md} | 0 text/{0073 => 0074}/clientserver.png | Bin 2 files changed, 0 insertions(+), 0 deletions(-) rename text/{0073/0073-entity-slicing.md => 0074/0074-entity-slicing.md} (100%) rename text/{0073 => 0074}/clientserver.png (100%) diff --git a/text/0073/0073-entity-slicing.md b/text/0074/0074-entity-slicing.md similarity index 100% rename from text/0073/0073-entity-slicing.md rename to text/0074/0074-entity-slicing.md diff --git a/text/0073/clientserver.png b/text/0074/clientserver.png similarity index 100% rename from text/0073/clientserver.png rename to text/0074/clientserver.png From 7b63a128f67cec2aff599d5fc9834a373fd70a91 Mon Sep 17 00:00:00 2001 From: Oliver Flatt Date: Mon, 26 Aug 2024 10:01:40 -0700 Subject: [PATCH 15/20] Update text/0074/0074-entity-slicing.md Co-authored-by: Kesha Hietala Signed-off-by: oflatt --- text/0074/0074-entity-slicing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0074/0074-entity-slicing.md b/text/0074/0074-entity-slicing.md index bb309f5b..2d3193d2 100644 --- a/text/0074/0074-entity-slicing.md +++ b/text/0074/0074-entity-slicing.md @@ -130,7 +130,7 @@ The image above shows an example usage of the entity manifest. On the left, a cl When a client would like to answer a Cedar `Request`, it first consults the cached entity manifest to load the entity slice. Then, it sends the entity slice and the request to the server. The server returns the authorization result. -However, the server or client must take care to ensure that the client’s entity manifest is up-to-date. Since the entity manifest is computed based on the schema and policies, is must be re-computed whenever the schema or policies change. There are multiple ways to ensure that the cache is up to date, one being to tag each entity manifest uniquely and check on each request. +However, the server or client must take care to ensure that the client’s entity manifest is up-to-date. Since the entity manifest is computed based on the schema and policies, it must be re-computed whenever the schema or policies change. There are multiple ways to ensure that the cache is up to date, one being to tag each entity manifest uniquely and check on each request. Looking to the future, we hope to provide several options and guidance for how to use the entity manifest: From d7ff80fda049ade21a72085314de2c9c44f69e4c Mon Sep 17 00:00:00 2001 From: oflatt Date: Mon, 26 Aug 2024 10:16:45 -0700 Subject: [PATCH 16/20] generalized version Signed-off-by: oflatt --- text/0074/0074-entity-slicing.md | 78 +++++++++++++++----------------- 1 file changed, 36 insertions(+), 42 deletions(-) diff --git a/text/0074/0074-entity-slicing.md b/text/0074/0074-entity-slicing.md index 2d3193d2..03e260c7 100644 --- a/text/0074/0074-entity-slicing.md +++ b/text/0074/0074-entity-slicing.md @@ -217,53 +217,54 @@ The merge operator for access paths is straightforward. First, convert all acces ## Computing access paths -This section proposes a simple static analysis that soundly computes all of the necessary access paths for a Cedar expression. -While it’s possible to soundly handle all Cedar expressions, we simplify the problem by rejecting Cedar programs unless they follow the following grammar: - - -``` - = - in - + - if { } { } - ... all other Cedar operators not mentioned by datapath-expr - - = . - has - - - - = principal - resource - action - context - +This section proposes a simple static analysis that soundly computes all of the necessary access paths for a Cedar expression. +Given a Cedar expression, the analysis produces a pair (`HashMap`, `WrappedAccessPaths`). +The first value is the accumulated trie of data that needs to be loaded. +The second value is the "live" access paths that need to be considered for the particular expression. + +`WrappedAccessPaths` represents multiple potential access paths, soundly over-approximating the execution of a cedar expression. +Since Cedar expressions may be record literals, the access paths are wrapped in corresponding record literals in the analysis: + +```rust +/// This allows the Entity Manifest to soundly handle +/// data that is wrapped in record or set literals. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub(crate) enum WrappedAccessPaths { + /// No access paths are needed. + #[default] + Empty, + /// A single access path, starting with a cedar variable. + AccessPath(AccessPath), + /// The union of two [`WrappedAccessPaths`], denoting that + /// all access paths from both are required. + /// This is useful for join points in the analysis (`if`, set literals, ect) + Union(Box, Box), + /// A record literal, each field having access paths. + RecordLiteral(HashMap>), + /// A set literal containing access paths. + /// Used to note that this type is wrapped in a literal set. + SetLiteral(Box), +} ``` +The analysis is bottom-up traveral of cedar expressions. +Most operators are strait-forward to analyze, but there are some special cases to get right: -The grammar partitions Cedar expressions into **datapath expressions** and all other Cedar operators. -On the LHS of `in`, `.`, and `has`, expressions involving `in`, `+`, `if-then-else`, and most other Cedar functions/operators are not allowed; only Cedar variables, entity literals, and `.` or `has` expressions are allowed. - -The partition is reasonable since, in practice, users do not interleave datapath expressions with other operators. -Here are examples of Cedar expressions rejected by this grammar: - - -``` -{ "myfield": principal.name }.myfield -(if principal.ismanager then { "myfield" : 2 } else { "myfield": 3 }).myfield -(if principal has mobilePhone then principal.mobilePhone else principal.workPhone).zipCode -``` +1. For entity or struct dereferences, be sure to add to all `WrappedAccessPaths`. +2. For equality between records (`==` or `.contains`, `.containsAny`, and `.containsAll`), all fields in the type need to be added to the access paths. +3. For if statements, the `Union` variant should be used to ensure both +control-flow cases are covered. +4. Whenever the `WrappedAccessPaths` are dropped, it's important to add them to the accumulated answer. Now that we have this partition, computing all the access paths is fairly simple: 1. First, find all datapath expressions. These can be translated into access paths directly. -2. Second, handle all instances of equality between records. For equality between records (`==`) or set containment where the elements are records (`.contains`, `.containsAny`, and `.containsAll`) +2. Second, handle all instances of equality between records. 1. Find all access paths that are children of these operators 1. Following the schema, fully load all fields for the leaf of the access path 3. Finally, handle instances where the ancestors in the entity hierarchy are required. Annotate the left-hand-side of the `in` operator with the `ancestors_required` flag. -## ## SimplifiedEntityLoader API @@ -308,13 +309,6 @@ pub trait SimpleEntityLoader { Increasing the complexity of the Cedar API with entity manifests provides new places for users to make mistakes. For example, using an outdated entity manifest can result in unsound entity slices. Another example would be a user writing a buggy implementation of entity loading using the manifest. -### Full Cedar Language Support - -The current analysis does not support the full Cedar language (see the **Computing Access Paths** section). With some effort, the implementation could be extended to support all of Cedar by more carefully tracking access paths through different types of Cedar operations. However, it’s unclear just how tricky this will be. - -Along a similar note, all Cedar changes in the future will need a corresponding change in the static analysis, making it harder to extend Cedar. For example, any operations that apply to records will need careful consideration. - - ### Supporting Partial Loading of Sets As written, entity manifests in this RFC do not support loading only parts of a Cedar Set, or only some of the ancestors in the entity hierarchy. This is because sets are loaded on the leaves of the access trie, with no way to specify which elements are requested. From bf5d5254f9eb18acd3ee76b475ba6c8fb9884fa7 Mon Sep 17 00:00:00 2001 From: oflatt Date: Mon, 26 Aug 2024 10:20:00 -0700 Subject: [PATCH 17/20] add links to examples Signed-off-by: oflatt --- text/0074/0074-entity-slicing.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/text/0074/0074-entity-slicing.md b/text/0074/0074-entity-slicing.md index 03e260c7..7b870ee4 100644 --- a/text/0074/0074-entity-slicing.md +++ b/text/0074/0074-entity-slicing.md @@ -321,7 +321,7 @@ To support this feature, we recommend that we take a constraint-based approach. # Extended examples -## Paths needed in document_cloud example +## Paths needed in [document_cloud](https://github.com/cedar-policy/cedar-examples/blob/release/3.2.x/cedar-example-use-cases/document_cloud/policies.cedar) example ``` Action::"CreateDocument" @@ -401,7 +401,7 @@ action::"DeleteGroup" -## Paths needed in tags_n_roles +## Paths needed in [tags_n_roles](https://github.com/cedar-policy/cedar-examples/blob/main/cedar-example-use-cases/tags_n_roles/policies.cedar) ``` Action::"Role-A Actions" @@ -437,7 +437,7 @@ resource.tags.stage -## Paths needed in github_example +## Paths needed in [github_example](https://github.com/cedar-policy/cedar-examples/tree/main/cedar-example-use-cases/github_example) using "ancestors" as Cedar's ancestor relation- requires all ancestors transitively @@ -472,7 +472,7 @@ resource.admins.ancestors -## Paths needed in tax_preparer +## Paths needed in [tax_preparer](https://github.com/cedar-policy/cedar-examples/tree/main/cedar-example-use-cases/tax_preprarer) ``` Action::"viewDocument" @@ -490,7 +490,7 @@ context.conset.team_region_list -## Paths needed in sales_orgs +## Paths needed in [sales_orgs](https://github.com/cedar-policy/cedar-examples/tree/main/cedar-example-use-cases/sales_orgs) ``` Action::"ExternalPrezViewActions" @@ -538,7 +538,7 @@ context.targetUser.job -## Paths needed in hotel_chains +## Paths needed in [hotel_chains](https://github.com/cedar-policy/cedar-examples/tree/main/cedar-example-use-cases/hotel_chains) ``` Action::"viewReservation" ect From f97fcddf592e575bd9cfe4e9b3520efe71fcfa08 Mon Sep 17 00:00:00 2001 From: oflatt Date: Mon, 26 Aug 2024 10:20:45 -0700 Subject: [PATCH 18/20] clarify option 2 Signed-off-by: oflatt --- text/0074/0074-entity-slicing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0074/0074-entity-slicing.md b/text/0074/0074-entity-slicing.md index 7b870ee4..1ed1c85b 100644 --- a/text/0074/0074-entity-slicing.md +++ b/text/0074/0074-entity-slicing.md @@ -135,7 +135,7 @@ However, the server or client must take care to ensure that the client’s entit Looking to the future, we hope to provide several options and guidance for how to use the entity manifest: 1. Use the `SimplifiedEntityLoader` api (see later section). -2. Use a specialized entity loader when the format of the database sufficiently matches the schema. +2. Use a specialized entity loader when the format of the database sufficiently matches the schema. For example, Cedar could provide an SQL database data loader. 3. Write custom entity loading that leverages the entity manifest. From ca306d5cab8888ef118b640cf8bd3edfdcb8a472 Mon Sep 17 00:00:00 2001 From: oflatt Date: Mon, 26 Aug 2024 10:29:53 -0700 Subject: [PATCH 19/20] nit Signed-off-by: oflatt --- text/0074/0074-entity-slicing.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/text/0074/0074-entity-slicing.md b/text/0074/0074-entity-slicing.md index 1ed1c85b..3c18160f 100644 --- a/text/0074/0074-entity-slicing.md +++ b/text/0074/0074-entity-slicing.md @@ -276,18 +276,18 @@ The easiest is the `SimpleEntityLoader` api shown here. Users need only write a /// Implement this trait to load entities based on /// the entity manifest. /// This entity loader is called "Simple" for two reasons: -/// 1) First, it may cause database consistency challenges- `load_entity` is called multiple times. +/// 1) First, it may cause database consistency challenges- `load_entities` is called multiple times. /// 2) Second, it is not precise- the entity manifest only requires some /// fields to be loaded, but this loader always loads all fields. pub trait SimpleEntityLoader { - /// Simple entity loaders must implement `load_entity`, + /// Simple entity loaders must implement `load_entities`, /// a function that loads entities based on their `EntityUID`s. /// For each element of `entity_ids`, returns the corresponding /// [`Entity`] in the output vector. - fn load_entity(&mut self, entity_ids: impl IntoIterator) -> impl Iterator; + fn load_entities(&mut self, entity_ids: impl IntoIterator) -> impl Iterator; /// Loads all the entities needed for a request - /// using the `load_entity` function. + /// using the `load_entities` function. fn load( &mut self, schema: &Schema, From 5ab6a62ccb6f0d24f66d46e7116dbd014466b028 Mon Sep 17 00:00:00 2001 From: oflatt Date: Wed, 28 Aug 2024 12:54:08 -0600 Subject: [PATCH 20/20] move load fn out Signed-off-by: oflatt --- text/0074/0074-entity-slicing.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/text/0074/0074-entity-slicing.md b/text/0074/0074-entity-slicing.md index 3c18160f..590f6430 100644 --- a/text/0074/0074-entity-slicing.md +++ b/text/0074/0074-entity-slicing.md @@ -285,17 +285,17 @@ pub trait SimpleEntityLoader { /// For each element of `entity_ids`, returns the corresponding /// [`Entity`] in the output vector. fn load_entities(&mut self, entity_ids: impl IntoIterator) -> impl Iterator; - - /// Loads all the entities needed for a request - /// using the `load_entities` function. - fn load( - &mut self, - schema: &Schema, - entity_manifest: &EntityManifest, - request: &Request, - ) -> Result { - ... - } +} + +/// Loads all the entities needed for a request +/// using the `load_entities` function. +fn load( + &mut loader: SimpleEntityLoader, + schema: &Schema, + entity_manifest: &EntityManifest, + request: &Request, +) -> Result { + ... } ```