-
Notifications
You must be signed in to change notification settings - Fork 15
Generalized Cedar Templates #98
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…xample to show utility of slots in condition, and edited detailed design
…ionally annotated
|
It seems that there will be a language of type annotations. Can you specify the details of the language (e.g., the syntax of Cedar types, if it's related to a schema)? Can you also specify the grammar of the template annotations? And what if there are name conflicts (e.g., And should users provide values or expressions when binding template variables? |
Signed-off-by: Alan Wang <[email protected]>
The types provided by the user should be schema types since compared with AST types we are able to more precisely specify the types of Set and Record. In the implementation, I have a function that provided with a schema translates a schema type into a type checker type to perform validation.
The grammar of template annotations is as follows.
If there are name conflicts, the template should not be allowed. Each new typed slot requires a distinct name. However, within the when/unless clause the same typed slot can be used multiple times.
Users should provide values when binding template variables. I think it will be confusing if a user provides an expression and the expression doesn't evaluate to what the user expects it to when linking with a template. In the implementation, the type of the values provided by the user should be of |
|
@alanwang67 thank you for your reply. Could you please incorporate your response to the RFC? Furthermore, I have another question that is if bind-time validation is too expensive. In other words, we validate linked policies and reject them if type checking fails? I feel like creation-time validation only buys us when the policies are very complicated and templates do not show up often. My rationale is that type annotation may be too much a burden for both Cedar users and implementors. Bind-time validation save us from it at the cost of potential repeated validation work. |
Since templates are typically tied to a user action, I believe performance could be important. In practice though, I am not sure if there will be a substantial difference. With bind time validation there are also some other implications: I think with bind time validation we would still want some type of validation to be performed at creation time. If we didn't it would be possible to have templates where there is no possible substitution for the slots that results in a valid linked template. The lack of type annotations could also be a problem for analysis. To perform analysis without type annotations, we would likely have to consider all possible combinations of the types of the slots that will allow for link time validation to succeed. This means that what we could say about our policies would have to be generalized over these combinations which could potentially result in a less meaningful analysis. If that isn't a concern, then the question of eliminating type annotations comes down to how much of a burden type annotations are for Cedar users. |
I don't think it a fundamental blocker because users will be likely inspect the policies when bind-time validation fails and correct the aforementioned issue.
Good point. I wonder if type-inference could help us to reduce the combinations. |
I am not sure if we would be able to infer the exact type. Take for example, the following template: If multiple entity types have this attribute in their schema with type |
Yes, the goal is not to find the exact match but to reduce the number of possible matches. |
text/0098-generalized-templates.md
Outdated
|
|
||
| ```?principal``` and ```?resource``` can optionally have their types annotated in ```template()```. A similiar effect can be done using the ```is``` operator in the condition. However, this would allow templates to be instantiated with slots always evaluate to false. By having an explicit type annotation, we prevent these instantiations from being linked in the first place. | ||
|
|
||
| The proposed syntax looks as follows ```template(?bound1: type1, ?bound2: type2) =>``` and types specified must be valid schema types. Schema types are chosen over AST types because they allow us to more precisely specify the type for sets and records. Each new typed slot requires a distinct variable name. However, within the when/unless clause the same typed slot can be used multiple times. The grammar of template annotations is as follows. ```Type``` is taken directly from the Cedar documentation for schema types. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typeis taken directly from the Cedar documentation for schema types.
Cedar schema types include named type definitions. Do you expect that these will be allowed here? This would mean you need a schema at template link time which I don't think you need otherwise.
Signed-off-by: Alan Wang <[email protected]>
|
Hi, thanks for the RFC. Here's some feedback: I think the signature for Might look better with the ?'s removed, as they are unnecessary: It seems like if I'm not understanding why we wouldn't want to be able to put the custom slots into the scope, can you explain that more? |
They are mainly kept there for uniformity and as a identifier that they are slots, without ?'s you can potentially have conflicts with the naming. For example, let's say you have an entity type in the global namespace as slot. If you then use this within your policy with the
As for user defined slots, I'm not exactly sure what types of use cases would motivate having them also appear in the scope. There are also some reasons related to policy indexing behind why |
I think if they were given type information, maybe they wouldn't have to be special? Without type info, I understand they need to be in the scope to be analyzed.
Here's an example I suppose these could be just ?principal and ?resource, but it seems convenient to be able to name them whatever I want. |
cdisselkoen
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In favor of this change
|
Thanks for the RFC! I think it was worthwhile to revisit the question of generalized templates, and if we were to add something like this to the language, this RFC provides a step in the right direction. That said, I don't think we should add this feature. In my opinion, the cost of doing so significantly outweighs the benefits, and there are reasonable solutions to the motivating examples given in this RFC that don't require this RFC. Here is what I mean in more detail. The cost of implementing this RFC is not limited to the cost of changing Cedar, which is already non-trivial. This RFC will also impose a high implementation burden on any policy management service that wants to stay up to date with new Cedar versions. Specifically, services that support the current template model assume that In particular, the motivating examples for this RFC fall roughly into three categories: (1) using templates to implement relation-based access control (ReBAC) (Examples 1 and 2); Using templates for ReBAC (1) goes against our recommended best practices (see here). Doing so amounts to storing relationship tuples as policies rather than encoding them in the entity hierarchy. This makes policies difficult to understand and analyze: there will be tens of thousands of templates instantiations for a real application. And while we can analyze templates given type information, template analysis will be overapproximate (giving up precision) compared to analysis of static policies. For ReBAC use cases, I would recommend using static policies over an entity hierarchy that encodes the relevant relations. Using templates to store data in policies (2) is an anti-pattern, for the same reason why we don't recommend using templates for ReBAC. For Example 3 in this RFC, I would recommend adding a We do recommend using templates for discretionary permissions (3) (see here). The current RFC does make it possible to enforce grouping of such permissions, and this is something that cannot be enforced by the existing templating mechanism. Beyond grouping, this RFC does provide a more powerful mechanism for expressing discretionary policies compared to what we have today, although it does not solve the most general use cases we have seen (where a template slot is filled with an expression rather than a constant). So, while this would expand our current templating capabilities, the amount of additional safety and convenience that it provides over the existing mechanisms is relatively minor, especially given the cost and complexity of implementing this proposal in Cedar and in services based on Cedar. |
|
And one minor comment on the proposal: I don't think we should treat |
Good point. I agree that this RFC needs more convincing examples on discretionary policies.
I don't think there will be a huge burden. My naive understanding of the workflow of templates is that a frontend sends a request to a backend running Cedar that instantiates a template and adds it to the policy set. The request should contain the bindings (from template variables to values), presumably in JSON. And the whole process should not rely on any persistent storage. Furthermore, the new set of bindings should be a superset of existing ( |
Policy management services would store the template and the instantiations of the template. In other words, the instantiation is not passed in by the caller of the service, so the request itself doesn't contain the binding from template variables to values. That creates issues since we can't assume that a service stores the instantiation record as a JSON object that binds |
Indeed. Our policy management service, for instance, does not store the "principal" or "resource" variable name at all. One thing we could do that would make supporting this change slightly easier is to keep the behavior of principal and resource the same as it is today. Then service owners would only need to add support for the inclusion of new bindings (not allowed in the scope) in addition to the existing principal and resource. This would mean all their existing storage representations are valid (where the additional bindings is an empty map.) |
|
I think the reason for supporting this feature is primarily around discretionary permissions. A good usecase is for defining row/column level access. For example, a customer uses Cedar policies to manage access to a data warehouse based on user groups and attributes. They create a policy for each user group that grant access to a subset of rows based on resource attributes For example, a policy might look like The customers have hundreds of such policies in a policy store, and uses partial evaluation to generate a SQL query to get only authorized rows. The reason they want templates is because some parts of the policy conditions are fixed (eg. MFA) and some vary based on the user group that is being given access. They want a easy way to update all policies when these common set of conditions are updated. The customer further mentioned that in their case, they want to give ownership of managing permissions to business admins in the long term. They would create a UI that allows business admins a UX, that allows them to define conditions with limited flexibility. They want to easily build these policies based on UX options, and do not want to use string manipulation due to security risks and developer pain associated with it |
Signed-off-by: Alan Wang <[email protected]>
|
We (maintainers) don't have a formal FCP process anymore for RFCs, but we'd still like to do an informal "last call for comments" on this one. Note this one already has an in-progress implementation on the Rust side (cedar-policy/cedar#1732 and cedar-policy/cedar#1738). There's been quite a bit of engagement on this one, and there have also been a few updates since the last comments. We are looking for comments on the latest version. In particular, for those who have previously been against (@emina @blubben, I think?), does the latest version satisfy your concerns, or not? For those who have previously been in favor, do you have any responses to the last few comments above? Looking particularly at the high-level discussion that happened July 9-24 |
|
To put a date on it, please try to get comments in by 2025-12-17 at 11:59pm UTC (6:59pm ET, 3:59pm PT). |
|
The current version resolves the concerns around This RFC will make it easier to write reusable templates for discretionary policies. |
|
In my company, we were working on a custom implementation of templates in order to support custom variable names (e.g. |
|
|
||
| 1. We want to relax the restriction on the two slots (```?principal``` and ```?resource```) that Cedar currently provides to allow them to appear in the condition of the template if they also appear in the scope. | ||
|
|
||
| 2. We want to allow for an arbitrary number of user defined slots to appear in the condition (```when/unless```) clause of the template if the type is explicitly annotated. Annotated types can be of any valid Cedar Schema Type including common types. User defined slots are different from ```?principal``` and ```?resource``` slots because they can be instantiated with values that are not entity types and also can not appear in the scope. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd still like to request that ?principal and ?resource not be given any special treatment. They should be variables just like any other variable. It seems like that will make implementation more straightforward and won't result in any backwards-incompatible changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For variables used within the scope, it's possible to infer their types without having to put them in a template() clause. (In most cases, a variable would be an EntityUID, except for when used in action in ?something, where it could be a set of entity UID or a single entity UID.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also think I should be able to name a variable whatever I want when I use it in the principal or resource parts of the scope. ?group or ?database or whatever.
|
|
||
| ### What is allowed and not allowed? | ||
| 1. Every user defined slot needs to have a type associated with it | ||
| 2. Disallow ```?action``` and ```?context``` slots |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure why you'd disallow action or context as variable names.
philhassey
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be nice to remove all the arbitrary constraints surrounding ?principal and ?resource and allow people to name and use whatever variables they want.
| In addition, there is some subtlety involved when handling type declarations involving primitive types. The main issue stems from entity types being allowed to shadow primitive type. Without a schema to distinguish whether or not this is the case, we opt to be more permissive. We interpret primitive types as both entity types and Cedar primitive types and typechecking passes if one of the cases typechecks. | ||
|
|
||
| ### Syntax | ||
| The proposed syntax looks as follows ```template(?bound1: type1, ?bound2: type2) =>``` and types specified must be valid schema types. Schema types are chosen over AST types because they allow us to more precisely specify the type for sets and records. Each new typed slot requires a distinct variable name. However, within the ```when/unless``` clause the same typed slot can be used multiple times. The grammar of template declarations is as follows. ```Type``` is taken directly from the Cedar documentation for schema types. The template syntax (```template(?bound1: type1, ?bound2: type2) =>```) must go after all the declarations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the => required? Can't we just assume that a policy statement follows the template(...) statement?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we use a scope of e.g. { ... } instead? That'd allow expanding to supporting multiple policies within one template group in the future, if that ever were to be.
As an aside, I was planning to open a proposal for supporting the => operator as a shorthand for if <condition> then <statement> else true; using the => string here could maybe cause conflicts in the parser?
|
As there is still an active conversation on this RFC we will extend the informal "last call for comments" to 2026-01-09 at 11:59pm UTC (6:59pm ET, 3:59pm PT). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I support this RFC! In general, I'm in agreement with @philhassey in that I think it would be a nicer experience if ?principa., ?resource, ?action, and ?context did not have any special treatment / are reserved.
I understand its in part to be backwards compatible and be opinionated about name choices (to potentially avoid confusion, but I feel like the ? in front of the slot names would already be distinguishing). As a compromise, we could issue a warning during template parsing if they are using one of the reserved keywords in an unexpected way (e.g., ?principal within the resource scope, etc.).
luxas
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great proposal! 🎉
Left a couple of comments, mostly curious about some of the rationale, and how much further this could be generalized. But I'd be happy to see this in one form or the other 😄
| permit(principal == ?principal, | ||
| action, | ||
| resource == ?resource) when | ||
| { ?startTime <= context.now && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: schema has context.date, but here context.now is referenced.
|
|
||
| ## Summary | ||
|
|
||
| Cedar templates are quite restrictive. There are only two slots provided (```?principal``` and ```?resource```) and they are limited to appearing in the scope of the template. This results in users of Cedar needing to come up with their own [solution](https://github.com/cedar-policy/rfcs/pull/3#issuecomment-1632645305) to handling templates that can not be expressed in Cedar currently. In this RFC, we propose to generalize Cedar templates in two different ways: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: only single ` quotes are required if one wants less typing, I think the formatting should be the same 😄
|
|
||
| #### Template | ||
| ``` | ||
| permit(principal, action == Action::"AccessDocument", resource in ?resource) when { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As others pointed out above, it feels like it'd be clearer to force the template syntax everywhere a template is used (even though there would not be other arguments), it'd make it clearer than this policy just being an "implicit" template by just using the special ?resource and ?principal. I understand why they are special, but I'm thinking if there's a simple way to make it more explicit.
|
|
||
| Suppose there are certain professors with dual appointments under two different departments who want to give students of their group access to both the resources in the first department and the resources in their second department. | ||
|
|
||
| Note: Although we can create two seperate templates and instantiate them individually, we would need to make sure they stay in sync. With generalized templates, since there is only one template we do not need to do so. Error states where only one part of the template is applied but not the other would not be able to occur. Generalized templates allows us to subsume the functionality described in [#7](https://github.com/cedar-policy/rfcs/pull/7) and with typed slots we can express the motivating example for template-groups. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Note: Although we can create two seperate templates and instantiate them individually, we would need to make sure they stay in sync. With generalized templates, since there is only one template we do not need to do so. Error states where only one part of the template is applied but not the other would not be able to occur. Generalized templates allows us to subsume the functionality described in [#7](https://github.com/cedar-policy/rfcs/pull/7) and with typed slots we can express the motivating example for template-groups. | |
| Note: Although we can create two separate templates and instantiate them individually, we would need to make sure they stay in sync. With generalized templates, since there is only one template we do not need to do so. Error states where only one part of the template is applied but not the other would not be able to occur. Generalized templates allows us to subsume the functionality described in [#7](https://github.com/cedar-policy/rfcs/pull/7) and with typed slots we can express the motivating example for template-groups. |
|
|
||
| Suppose there are certain professors with dual appointments under two different departments who want to give students of their group access to both the resources in the first department and the resources in their second department. | ||
|
|
||
| Note: Although we can create two seperate templates and instantiate them individually, we would need to make sure they stay in sync. With generalized templates, since there is only one template we do not need to do so. Error states where only one part of the template is applied but not the other would not be able to occur. Generalized templates allows us to subsume the functionality described in [#7](https://github.com/cedar-policy/rfcs/pull/7) and with typed slots we can express the motivating example for template-groups. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that this as a nice side-effect mostly subsumes #7. However, it depends on the user "inlining" all the logic into one policy, which might be undesirable, especially if there's both permit and deny policies for which you want to enforce atomicity. Indeed, this can be papered over by the higher-level implementation (enforcing atomicity), but I'd be interested to know if there at this time was any specific reason allowing multiple policies within a single template "scope" is not supported?
| In addition, there is some subtlety involved when handling type declarations involving primitive types. The main issue stems from entity types being allowed to shadow primitive type. Without a schema to distinguish whether or not this is the case, we opt to be more permissive. We interpret primitive types as both entity types and Cedar primitive types and typechecking passes if one of the cases typechecks. | ||
|
|
||
| ### Syntax | ||
| The proposed syntax looks as follows ```template(?bound1: type1, ?bound2: type2) =>``` and types specified must be valid schema types. Schema types are chosen over AST types because they allow us to more precisely specify the type for sets and records. Each new typed slot requires a distinct variable name. However, within the ```when/unless``` clause the same typed slot can be used multiple times. The grammar of template declarations is as follows. ```Type``` is taken directly from the Cedar documentation for schema types. The template syntax (```template(?bound1: type1, ?bound2: type2) =>```) must go after all the declarations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we use a scope of e.g. { ... } instead? That'd allow expanding to supporting multiple policies within one template group in the future, if that ever were to be.
As an aside, I was planning to open a proposal for supporting the => operator as a shorthand for if <condition> then <statement> else true; using the => string here could maybe cause conflicts in the parser?
| ### What is allowed and not allowed? | ||
| 1. Every user defined slot needs to have a type associated with it | ||
| 2. Disallow ```?action``` and ```?context``` slots | ||
| 3. Annotated ```?principal``` and ```?resource``` types must be an entity type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be represented in some explicit way in the parameters list? One could imagine that user-specified arguments also could need this, and be used as ?container: <AnyEntityUID> and resource.thing in ?container.
| 5. ```?principal``` and ```?resource``` slots must appear in the scope | ||
| 6. User defined slots can not appear in the scope |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there other arguments for these requirements other than simpler implementation?
| 1. Every user defined slot needs to have a type associated with it | ||
| 2. Disallow ```?action``` and ```?context``` slots | ||
| 3. Annotated ```?principal``` and ```?resource``` types must be an entity type | ||
| 4. All slots within the template declaration (```template()```) must be used in the template |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given this, it'd be nice to somehow "allowlist" the use of ?resource and ?principal too somehow.
rendered