Skip to content

feat: implement policy templates #83

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

Open
wants to merge 25 commits into
base: main
Choose a base branch
from

Conversation

caiorcferreira
Copy link

Description of changes:

One of Cedar's main features is Policy Templates, which allows for managing large sets of policies while keeping the authorization rules centralized.

This PR is an attempt to implement Templates in cedar-go.

Features

This implementation follows the Template behavior described in the Language Guide.

We can break the Template behavior described in the Language Guide in 3 main requirements:

1. Template Variables

You can use placeholders in a Cedar policy template for only the following two elements of a policy statement:

  • Principal – ?principal
  • Resource – ?resource

You can use either one or both in a policy template.

To fulfill this requirement, we introduce types.SlotID with two possible values: PrincipalSlotID (?principal) and ResourceSlotID (?resource).
The unmarshaling logic expects only these values and errors otherwise.

2. Operator support

Placeholders can appear in only the policy scope on the right-hand side of the == or in operators. This includes in operators when appearing together with an is operator, but excludes solitary is operators.

This requirement guided the changes in marshaling and the ScopeTypeX supported by the linkScope function.

3. Cascade change propagation

However, the new policy isn’t actually constructed as a static policy with that policy statement. Instead, the new policy is a template-linked policy that dynamically links the policy template with those two specific placeholder values. Later, you might choose to modify the policy template, such as by adding an additional condition to the unless clause. From that moment on the change immediately affects all authorization decision results for all template-linked policies created from the modified policy template.

Based on this requirement, we created the LinkedPolicy type. It holds a template reference, a template ID, a link ID, and the slot env with values that will replace the variables.

The LinkedPolicy can be serialized to JSON, following the cedar-cli structure, so that it can be persisted and later rendered into a policy again. Because we store the template ID (instead of the actual template), if the template content changes in the meantime, the client code should be able to retrieve the template's latest version and create a new instance of the LinkedPolicy to render the policy.

Conclusion

Policy templates will soon become a critical feature for my Cedar use case. Hence, I want to try to move this feature forward, and I appreciate any help!

Copy link
Collaborator

@patjakdev patjakdev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we are close to being able to merge this.

However, after consulting internally at StrongDM, we'd like to move the template-supporting PolicySet into x/exp/templates for now. Some work we just to expose an Authorize() function that takes a policy set iterator interface will probably help make that work well.

Can we sync up on the Cedar Slack to discuss further?

return Policy{}, fmt.Errorf("slot env length %d does not match template slot length %d", len(p.slotEnv), len(body.Slots()))
}

for _, slot := range body.Slots() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bookkeeping seems unnecessary. Let's just hunt for slot usages in the expected places and replace them with the provided values from the environment or return an error if no value is provided.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which bookkeeping do you refer to?

  • Checking if slotEnv size is the same as the number of slots in the template? If so, I did the same as in the Rust impl, such that if a policy fails in the Rust impl it also fails in the Go impl.
  • Iterating over the return from Slots()? It just makes it easier to check whether the slot is used or not.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was imagining that we didn't need Slots() at all. It seems to me that ast.Policy ought to not know anything about templates (beyond the VariableSlot nodes in the AST) and that we could just iterate the AST here looking for VariableSlots to replace with values from slotEnv. The error conditions would then be:

  1. A value does not exist in the slotEnv when we go to replace it
  2. A value in the slotEnv isn't used. We'd obviously have to do some bookkeeping locally in Render() to keep track of that, but I think I prefer that to polluting types.Policy with knowledge about templates.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, when parsing a Cedar document and trying to decide whether it's a Policy or a Template, I'd just look at the scope AST to see if there are any VariableSlots.

If templates ever become more general (which, perhaps, one day they may), this will be more expensive and we can address it then. For now, it's easy enough to look through the scope AST to decide what's going on.

…emplates-2

* 'main' of github.com:caiorcferreira/cedar-go: (23 commits)
  cedar-go: remove golangci-lint badge from README.md
  cedar-go: retract v1.2.0
  cedar-go: change receiver type for IsAuthorized() and Get() for efficiency reasons and mark Map() as deprecated
  cedar-go: change the name of the AuthorizationPolicySet to PolicyIterator
  cedar-go: reduce the minimum go version 1.23.0 and require the version of golang.org/x/exp that introduces constraints
  cedar-go: reduce the usage of x/exp/maps in favor of the standard library
  cedar-go: add documentation for batch and Go version requirements
  types: fix test weirdnesses
  cedar-go: add an All() method to PolicyMap to make it easy to pass to Authorize()
  cedar-go: change Authorize() to take an interface with a single All() method rather than a bare iter.Seq2
  cedar-go: add code coverage enforcement to CI
  x/exp/batch: change the batch Authorize function to take a policy iterator as well
  cedar-go: get test coverage up to 100%
  cedar-go: advertize new features in README.md
  cedar-go: use the PolicySet.All() iterator to implement PolicySet.IsAuthorized()
  cedar-go: consolidate Github workflows
  types: add iterators to each of the container types defined in the module.
  cedar-go: Add a new top-level Authorize() function
  cedar-go: upgrade golangci-lint checks to v2 and fix all issues found
  cedar-go: remove vendor directory
  ...

Signed-off-by: Caio Ferreira <[email protected]>
Signed-off-by: Caio Ferreira <[email protected]>
@philhassey
Copy link
Collaborator

I see there are some conflicts here yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants