Comparing OPA/Rego to AWS Cedar and Google Zanzibar
Rego, the policy language of the Open Policy Agent (OPA), is known for its flexibility and power in policy enforcement across various systems. Its declarative syntax and data-centric approach make it versatile for application authorization, infrastructure as code (IaC) authorization, and network policies. To fully appreciate OPA/Rego’s capabilities, it’s helpful to compare it with other policy languages and frameworks like AWS’s Cedar and Google’s Zanzibar. This blog post will describe common use cases for OPA/Rego and the limitations of Cedar and Zanzibar for those domains.
Policy Enforcement with OPA & Rego
OPA’s architecture is flexible enough to solve policy and authorization problems across the stack. At its core, OPA’s architecture helps decouple policy decision-making from policy enforcement. This separation gives users and administrators greater control and visibility over policies. Over the years OPA has delivered official support for various deployment models and integration patterns such as library integrations, sidecar and service deployments on bare-metal, container, or VM based architectures, CLI integrations, as well as compilation to third-party systems such as WebAssembly and SQL.
Similarly, Rego was designed to be expressive enough to handle a wide range of policies. From the first days of the project, Rego featured rich support for expressing declarative rules over arbitrary structured data (JSON). Over the years, Rego has expanded to include syntactic improvements, performance optimizations, extension mechanisms, and a large library of built-in functions for various domains including networking, API authorization, graph data, supply-chain security, and more.
OPA and Rego have been open source since inception and the project was donated to the CNCF in 2018 to ensure it had a vendor-neutral home. The project is maintained by several organizations, and multiple implementations of Rego (e.g. C++, Haskell) exist outside of the core project, all 100% compatible by complying with OPA’s expansive conformance test suite. Moreover, OPA’s user community consists of more than 4,000 organizations, of which 50% of the Fortune 100 and Global 100 belong. This all sounds great but what does Rego actually look like? Let’s look at some examples.
Example Scenario: ABAC for Application Authorization
Application authorization policies are a core part of any application. They govern who can do what and drive behavior across the frontend, backend, and data layer of the application. Rego can express both simple and complex application authorization policies with ease. Whether you are trying to implement RBAC, ABAC, ReBAC, or another type of access control, Rego has you covered for application authorization use cases.
package app
allow if {
user_is_customer
action_is_read
not pet_is_adopted
}
user_is_customer if data.user_attributes[input.user].title == "customer"
action_is_read if input.action == "read"
pet_is_adopted if data.pet_attributes[input.resource].adopted == true
Example Scenario: Infrastructure-as-Code Policies
Infrastructure as code (IaC) has seen tremendous adoption in recent years and is embodied by popular systems such as Kubernetes and Terraform. While IaC is incredibly powerful, guardrails are still required to ensure that IaC is operating within the bounds of an organization’s policies–whether those are related to security, compliance, cost, performance, availability, or just internal convention, automated policy guardrails are essential for IaC users.
The following example shows how you can use Rego to enforce guardrails in Kubernetes clusters. It illustrates several key features of Rego:
- Return values. This policy returns a set of values to the user that describe the error messages detailing why the Kubernetes resource violates policy
- Iteration. An infrastructure decision request may include an unbounded number of resources to analyze, requiring the policy to iterate over those resources and apply conditions, each of which may generate an error message.
- String manipulation. Regexes and the like are often useful as important information is encoded inside of strings. Moreover, constructing error messages often requires injecting pertinent information into the message.
package kubernetes
deny contains msg if {
input.request.kind.kind == "Pod"
some container in input.request.object.spec.containers
not startswith(container.image, "hooli.com/")
msg := sprintf("Image '%v' comes from untrusted registry",
[container.image])
}
Example Scenario: Network Firewall Rules
Network policies are crucial for managing and securing communication between different parts of a system. Rego can express complex network policies that go beyond simple access control, such as rules for traffic routing, firewall settings, and microservices communication.
package network
allow if {
net.cidr_contains(["192.168.1.1/24"], input.source.ip)
net.cidr_contains(["192.168.2.1/24"], input.destination.ip)
}
AWS Cedar
Cedar is an authorization policy language developed by AWS, designed to specify and enforce fine-grained permissions in applications. According to Cedar’s website, it is designed to be:
Expressive. Cedar is a simple yet expressive language that is purpose-built to support authorization use cases for common authorization models such as RBAC and ABAC.
cedarpolicy.com
Performant. Cedar is fast and scalable. The policy structure is designed to be indexed for quick retrieval and to support fast and scalable real-time evaluation, with bounded latency.
Analyzable. Cedar is designed for analysis using Automated Reasoning. This enables analyzer tools capable of optimizing your policies and proving that your security model is what you believe it is.
Limitations of Cedar compared to OPA/Rego
Architecture. Cedar only provides an official Rust library for interpreting policies to render decisions. It lacks an official open-source service that you can deploy for enforcement. Cedar also lacks standard APIs for managing policies and data (in contrast to OPA’s binaries and docker images that include decision and management APIs.) Cedar is designed to be embedded into the proprietary SaaS offering Amazon Verified Permissions (AVP), and it is an exercise to the user to design the architecture they want to use if they forgo AVP.
Expressiveness. Cedar is designed around the PARC model for specifying end-user application permissions. This means that all policies start with basic matching on Principal, Action, and Resource and produce boolean permit/forbid decisions. Policies requiring non-boolean decisions (e.g., traffic redirection), custom abstractions to simplify policy authoring (e.g., helper functions), or iteration are not supported.
Below we detail several Rego examples that Cedar cannot express.
1. Networking Policy Actions
Many firewalls support allow/deny but also keywords that redirect or alert on traffic. Cedar is hardcoded to support only permit/forbid and so can’t express policies outside of that. So while Cedar can express the body of the following rule with isInRange
, it cannot express the decision to redirect
in the head of the rule.
# NOT expressible in Cedar
redirect if {
net.cidr_contains(["192.168.1.1/24"], input.source.ip)
net.cidr_contains(["192.168.2.1/24"], input.destination.ip)
}
2. Networking Policy Helper Rules
In Cedar, users cannot define their own reusable helper functions or rules. One implication of this is that there is no way to write policy at a higher-level of abstraction than the inputs provide. For example, you cannot express policy about application names if the policy query is about IP addresses.
In contrast, in this Rego example the inputs are IP addresses, but Rego introduces the abstraction of an application name by interacting with a CMDB. Day-to-day, people maintain the part of the policy that describes which applications can connect to which other applications, yet that policy is enforced at the level of IPs.
# NOT expressible in Cedar
allow if {
source_app == "salary-reporter"
dest_app == "mongo-db"
}
source_app := ... if { ... } # compute source app based on CMDB
dest_app := ... if { ... } # compute destination app based on CMDB
3. Kubernetes admission control
Cedar doesn’t support iteration so can’t, for example, walk over all the containers in a Kubernetes pod and assert that certain constraints must be true. Even further, Cedar does not accept as input a resource that is an arbitrary JSON object or support the non-boolean responses required by Kubernetes.
# NOT expressible in Cedar
deny contains msg if {
input.request.kind.kind == "Pod"
some container in input.request.object.spec.containers
not startswith(container.image, "hooli.com/")
msg := sprintf("Image '%v' comes from untrusted registry",
[container.image])
}
Google Zanzibar
Google’s Zanzibar is an internal authorization system designed for managing permissions across Google services (e.g., YouTube, Google Drive, etc.). Since Google published the original paper, a handful of companies have reimplemented parts of Zanzibar and offer their own bespoke policy formats and APIs. At the time of writing, there is no standard, vendor-agnostic, open-source implementation of Zanzibar on the market. According to its research paper, Zanzibar’s goals are…
Correctness: It must ensure consistency of access control decisions to respect user intentions.
Flexibility: It must support a rich set of access control policies as required by both consumer and enterprise applications.
Low latency: It must respond quickly because authorization checks are often in the critical path of user interactions. Low latency at the tail is particularly important for serving search results, which often require tens to hundreds of checks.
High availability: It must reliably respond to requests because, in the absence of explicit authorizations, client services would be forced to deny their users access.
Large scale: It needs to protect billions of objects shared by billions of users. It must be deployed around the globe to be near its clients and their end users.
Page 1 of https://research.google/pubs/zanzibar-googles-consistent-global-authorization-system/
Limitations of Zanzibar compared to OPA/Rego
Architecture: Zanzibar is designed to be the source of truth for massive amounts of permissions data, requiring sophisticated world-wide distributed caching, and delivering strong consistency. That principle is at odds with other enforcement models such as sidecars and compiler models that fundamentally forego strong consistency in favor of performance and availability required in networking.
Expressiveness. Zanzibar’s model is highly specialized for end-user permission systems. ABAC policies are outside the scope of Zanzibar and IaC or network policies cannot be expressed since the primary focus is on permission management. Zanzibar’s expressiveness is effectively limited to equality checks over a hierarchical space of users, groups, folders, files, etc. It does NOT support arithmetic, IPs, JWT manipulation, regexes, or string comparisons. It does not support iteration over inputs. Its decision API input is limited to the Principal-Action-Resource, so will not even accept policy queries about infrastructure-as-code, e.g., Kubernetes, Terraform, or networking. Below we give several examples of Rego policies that cannot be expressed in Zanzibar.
1. Attribute-based Access Control (ABAC)
Zanzibar supports neither the arithmetic (age > 18) nor the conflict resolution (e.g. deny overrides allow) shown below.
# NOT expressible in Zanzibar
allow {
input.method == "POST"
input.path == "/accounts"
}
deny {
input.user.age < 18
}
2. Kubernetes admission control
Zanzibar expects principle-action-resource as an input, not arbitrary JSON/YAML as required by Kubernetes. Moreover, Zanzibar supports neither the iteration nor string manipulation used below.
# NOT expressible in Zanzibar
deny contains msg if {
input.request.kind.kind == "Pod"
some container in input.request.object.spec.containers
not startswith(container.image, "hooli.com/")
msg := sprintf("Image '%v' comes from untrusted registry",
[container.image])
}
3. Networking policy
Zanzibar does not support the IP addresses or CIDR arithmetic shown below.
# NOT expressible in Zanzibar
allow if {
net.cidr_contains(["192.168.1.1/24"], input.source.ip)
net.cidr_contains(["192.168.2.1/24"], input.destination.ip)
}
Conclusion
OPA/Rego stand out due to their flexibility, expressiveness, and extensibility, making it an ideal choice for policy enforcement across network, infrastructure, and applications. While Cedar and Zanzibar excel in specific contexts (AWS environments and Google’s internal services, respectively), they lack the general-purpose capabilities and versatility that Rego offers. By leveraging Rego and OPA, organizations can create robust, maintainable, and scalable policies that meet diverse requirements across different domains.
Domain | Subdomain | Rego/OPA | Cedar | Zanzibar |
---|---|---|---|---|
Network | L2-L4 | ✅ | ❌ | ❌ |
Infrastructure-as-code | Kubernetes | ✅ | ❌ | ❌ |
Terraform | ✅ | ❌ | ❌ | |
Application Authorization | ABAC | ✅ | ✅ | ❌ |
ReBAC | ✅ | ✅ | ✅ | |
RBAC | ✅ | ✅ | ✅ |
For more information, here is a detailed feature-by-feature comparison between OPA and Cedar.