Policy as Code | From Infrastructure to Fine-Grained Authorization
- Share:
Join Permit.io Livestreams
This blog is based on one of our latest weekly livestreams. Each session features software development experts who share their insights, tools, and best practices on a wide range of topics, from identity and access management to security and various other software development questions and challenges.
In “Policy as Code | From Infrastructure to Fine-Grained Authorization”, we had the pleasure of hosting Jimmy Ray (Author of “Policy as Code”) and Omer Zuarets (Technical Leader @Permit.io).
We hope you join us in future sessions - make sure to check out our upcoming livestream calendar.
Meet Jimmy Ray and Omer Zuarets
Jimmy Ray, author of Policy as Code and Proving Cloud-Native Security, has spent over two decades in the tech industry, transitioning from traditional programming to cloud-native technologies. He has worked extensively with Kubernetes, Open Policy Agent (OPA), and tools like Cloud Custodian, using policy as code to automate security, governance, and compliance, making him a great source of knowledge on policy-as-code.
Joining Jimmy was Omer Zuarets, an engineer and technical leader at Permit.io, who shared his insights into policy engines and the role of tooling in simplifying policy implementation.
Here are some of the major highlights of what we learned in this session:
What is Policy-As-Code
Policy as code is the practice of defining and enforcing policies—rules governing access, operations, and compliance—using code rather than relying on static configurations or manual enforcement through tools like spreadsheets or ad-hoc scripts. Policy as code allows us to manage policies with the same tools (and, thus, benefits) that we would have when managing application code.
Policy as code is based on policy languages, which are designed to express and enforce rules in specific domains. Examples of such languages include Rego for Open Policy Agent (OPA), AWS’ Cedar language and policy engine, and OpenFGA. These languages vary in their syntax and use cases but share a common goal: abstracting the complexity of managing policies in modern environments.
As Jimmy described it:
"One of the biggest promises of modern policy languages is that they abstract away the complexity of the underlying API. You don’t have to understand all the API calls happening under the hood. You just learn the syntax of the language and focus on writing policies that meet your use case."
According to Omer:
"Policy should be treated just as you would your application code. If you think of it like developing any other application, you apply the same principles: choose the right language for the task, build with scalability in mind, and focus on performance."
The rise of Policy-as-Code drastically changed how developers think about policy management, shifting from static configurations and making policies more dynamic and adaptable. As Jimmy emphasized:
"You don’t want to be a subject matter expert in every cloud SDK or Kubernetes API. Policy languages lift those complexities into a higher-level domain-specific language, making them easier to work with."
Validation vs. Authorization
Policy as code has a wide range of applications, but two distinct areas in which it is most commonly used are validation and authorization. These concepts often overlap in practice, but they address fundamentally different challenges.
Validation:
Validation focuses on ensuring that incoming changes or requests meet a set of predefined rules before they are allowed to proceed. It’s often used in systems like Kubernetes, where validating admission controllers act as gatekeepers. As Jimmy explained:
"In Kubernetes, validation happens through event-driven API server request flows. If the request fails validation, we prevent an unwanted change from happening. Validation is essentially about deciding whether a change should be allowed to proceed."
Authorization:
Authorization, on the other hand, attempts to determine whether a specific user or system can perform a specific action. The distinction here is important:
"The key difference between validation and authorization is the user. Validation is about verifying an input against a rule, while authorization explicitly checks whether an authenticated or unauthenticated user is allowed to perform a specific action."
The interplay between validation and authorization becomes particularly clear in tools like Open Policy Agent (OPA), which can be used for both purposes. Jimmy noted:
"To me, validation in Kubernetes feels like a form of authorization because it’s deciding whether a change can proceed. But at its core, validation is focused on inputs and rules, while authorization involves users and their permissions."
This difference can be made clearer through a practical example:
"Imagine you have a file on Google Drive with tens of thousands of users with access permissions. If you’re validating, you might check whether the file metadata meets specific rules. If you’re authorizing, check whether a specific user can access or modify that file."
This dual capability—validation and authorization—is one of the reasons why policy as code has become so important in modern application and infrastructure management.
Policy Languages: Design and Use Cases
Policy languages allow us to express and enforce rules across diverse domains, with each language possessing its strengths, weaknesses, and ideal use cases.
Rego
One very popular example of a policy language is Rego, the language used by Open Policy Agents (OPA).
"Rego is a multipurpose logical language that’s domain-agnostic. It doesn’t care what the domain is—you send it data and policies, and if the data matches the policy, it acts on the request."
This flexibility makes Rego a popular choice for Kubernetes admission control, cloud compliance, and infrastructure as code.
AWS’ Cedar
By contrast, Cedar, a policy language developed by AWS, emphasizes readability and determinism. Omer explained:
"Sometimes you might choose Cedar because it’s easier to read and provides a deterministic way of knowing what the answer will be,"
Its declarative nature makes Cedar particularly well-suited for access control scenarios where clarity and predictability are critical.
OpenFGA
Then there’s OpenFGA, which excels at graph-based permissions.
"OpenFGA focuses on relationships, making it ideal for scenarios where you need to model complex connections between entities, like determining who has access to what in a highly interconnected system."
Choosing a Policy Language: Design Patterns and Abstraction
The **choice of policy language** often goes beyond syntax and use cases—it’s also about abstraction and design patterns. Jimmy emphasized the importance of abstraction:
"I don’t want to be a subject matter expert in every cloud SDK or Kubernetes API. Policy languages should lift those complexities into a higher-level domain-specific language, enabling us to focus on the rules themselves."
It is also important to highlight the role of design patterns in enforcing consistency:
"When working with raw OPA, applying strict design patterns can help maintain a consistent hierarchy of rules, making policies easier to manage and scale."
At Permit.io, this principle is further developed by layering abstractions over raw policy languages. Omer noted:
"We let developers model RBAC, ReBAC, and ABAC through SDKs and APIs," Omer explained. This enables us to generate hierarchies similar to those discussed, making the policies more maintainable and accessible.
The Advantages of Multi-Domain Policy Engines
Multi-domain policy engines, like Open Policy Agent (OPA), address various use cases across diverse environments. Its ability to function across domains enables organizations to consolidate their policy management efforts, reduce redundancy, and ensure consistency.
As Omer explained:
"OPA’s domain-agnostic design allows it to be used in various contexts, from infrastructure as code to application authorization, without being tied to a specific type of policy."
This flexibility is very valuable for organizations dealing with complex, multi-layered systems in which different domains require different types of policies.
Another advantage of multi-domain engines is their role in fostering ecosystem growth. Projects that build on OPA (More on these in the “Tooling” section) provide modularity, enabling organizations to adopt what they need without overhauling their entire infrastructure.
"Having an engine that can support multiple domains means that new satellite projects and integrations naturally emerge, offering even more value over time,".
Despite its multipurpose nature, OPA doesn’t sacrifice performance or scalability.
"With the right design patterns and optimizations, OPA can handle diverse policies efficiently, making it a go-to choice for teams managing both validation and authorization".
Multi-domain engines like OPA exemplify the principle of "build once, use everywhere," giving organizations a powerful tool to unify their policy management practices.
However, all of these benefits come with trade-offs:
"Being multipurpose often requires more customization for specific use cases. Some organizations might prefer using multiple engines tailored to specific domains for efficiency."
The choice of a policy language or engine ultimately depends on the use case, the team’s expertise, and the required abstraction level. As Jimmy concluded:
"It’s one thing to learn the core language; it’s another to operationalize it for your use case. The more extensible and integrative the solution, the easier it is to adopt and scale."
The Challenges of Policy as Code
While policy as code offers many advantages, it also presents significant challenges that developers must overcome.
Policy Language Learning Curves
One significant hurdle is the learning curve associated with certain policy languages. Jimmy shared his experience learning Rego:
"When I first came across OPA in 2018, I assumed it was imperative based on its syntax, but it’s not. It’s an assertion-based logical language, and I had to unlearn my preconceptions from writing Java for nearly 20 years."
This steep learning curve can make it difficult for developers to troubleshoot their policies effectively.
"Rego has a concept of truthiness," Jimmy explained. "You might assume something would be evaluated as false, but it’s actually true because of how truthiness works in Rego. Understanding this is key to writing effective policies."
Omer added:
"Rego is not truly imperative, but it’s not entirely declarative either. It has loops, conditions, functions, and extensibility, making it challenging for developers unfamiliar with logical languages."
Issues with the Data Plane
Another challenge lies in data management and scalability. Managing large volumes of data efficiently is a must, especially as systems grow.
Going back to the previous “Google Drive” example presented by Omer:
“Imagine you have a file with tens of thousands of users with access permissions. If you’re validating or authorizing access to that file, the data related to those permissions changes frequently. If you store this data in the policy engine, it can consume significant memory and eventually overwhelm the system."
Design patterns play a crucial role in addressing these kinds of issues. As Omer noted:
"One solution is to shard or segment your data across multiple policy agents. This ensures that each agent only handles the specific subset of data relevant to its queries. However, in graph-based permissions, this approach becomes tricky, as connected objects need to be grouped together to ensure consistency."
Jimmy also emphasized the importance of optimization:
"Any system needs to be tunable. You might consider fragmenting data, performing partial evaluations, or short-cutting decision points. These are forms of optimization that can help manage data volume while maintaining performance."
Maintaining Consistency and Managing Operationalization
The issue of consistency—particularly in graph-based permissions—is another layer of complexity.
"When working with graph permissions, you need to ensure that all connected objects are stored together in the same shard, as permissions for one object might depend on another. This can complicate data segmentation."
The operationalization of policies is a critical factor in overcoming these challenges.
"It’s one thing to learn the language, but it’s another to operationalize it for your use case. The more extensions, SDKs, and integrations a platform provides, the easier it becomes to adopt and maintain over time."
While the challenges of policy as code are real, the benefits far outweigh the obstacles:
"Policy as code allows us to define and enforce rules dynamically. It abstracts complexity, adapts to different scenarios, and scales with modern environments."
For organizations willing to invest in the right tools and practices, policy as code offers a powerful framework for governance, compliance, and automation.
But what are these tools?
A Solution to the Challenges: Integrations and Tooling
Integrations, SDKs, and libraries surrounding policy engines are key to solving the challenges of policy as code. As Jimmy noted:
"The more tools, libraries, or SDKs a platform can offer, the better. They enable operationalizing solutions. It’s not just about learning the language; it’s about having the resources to apply it effectively across your use cases."
Tools like OPAL, Cloud Custodian, Styra, and Permit.io are prime examples of how integrations can simplify and enhance the policy-as-code experience:
- OPAL: An open-source project from Permit.io, OPAL provides data and policy synchronization for OPA. It wraps OPA with an additional layer that enables efficient data syncing and policy syncing. It’s also expanding to support Cedar and OpenFGA, making it a useful tool for multi-engine ecosystems.
- Cloud Custodian: provides a solution for cloud governance by allowing users to define resource management policies in YAML. This helps manage cloud environments without diving into low-level APIs. Cloud Custodian makes event-driven compliance easier by allowing you to act on real-time changes or run periodic scans across cloud resources. This means you can enforce governance policies without extensive custom scripting.
- Styra: The creators of OPA, Styra provides enterprise-grade tools like policy validation, testing, and management - simplifying some for the complexity and steep learning curve presented by the Rego language. Jimmy highlighted: "The Rego linter from Styra is a fantastic tool. It helps you learn quickly and ensures that your policies are both syntactically and logically correct, which is crucial for newcomers."
- Permit.io: Permit.io takes abstraction further by providing SDKs and APIs for modeling access control. "At Permit.io," Omer explained, "we let developers define role-based, relation-based, and attribute-based access control, and we generate the policy hierarchies for them. This makes it easier to maintain consistency and scalability without needing to write raw Rego directly."
These integrations and tooling options show the power of policy engines and their versatility—they are not just singular projects but entire ecosystems of tools and extensions, especially when it comes to more mature ones like OPA. They help bridge the gap between theoretical policy management and practical implementation.
"It’s not just about having a policy engine, it’s about having the ecosystem around it to support adoption and long-term maintainability."
The Future of Policy as Code
As solutions for policy as code mature, the opportunities for developers to adopt this approach have never been greater. These is a constant stream of new tools and frameworks, addressing all the challenges we discussed: scalability, complexity, and operationalization.
"Policy as code has been around for years in open source, but we’re now hitting a point where enterprise support and tooling are helping organizations adopt it at scale. The ecosystem is maturing to make it accessible for teams of all sizes."
This evolution extends to policy languages themselves, which are adapting to meet new demands. Whether it’s supporting multi-domain use cases, enabling real-time data synchronization, or abstracting complexity, policy languages are becoming more versatile and developer-friendly. Omer explained:
"Policy as code is not just about writing policies; it’s about building ecosystems that support their adoption. The more extensible and integrative the tools, the more organizations can leverage policy as code effectively."
Try Permit.io for your Policy-as-Code Implementation
If you want to try implementing policy as code yourself, we invite you to try out the free Permit.io Community tier, as well as our open-source project OPAL. With SDKs, APIs, and various integrations, we hope to simplify the process of defining and managing policies.
Join the Conversation
We’d also love to see you at our future livestreams! Don’t miss the chance to learn and ask questions and learn from leading software developers.
Finally, be sure to join our growing authorization community, where developers collaborate to share insights, tools, and best practices.
Written by
Daniel Bass
Application authorization enthusiast with years of experience as a customer engineer, technical writing, and open-source community advocacy. Comunity Manager, Dev. Convention Extrovert and Meme Enthusiast.