OPA, Cedar, OpenFGA: Why are Policy Languages Trending Right Now?
- Share:
You might have noticed a rising trend lately - Policy languages like OPA, Cedar, and OpenFGA are being increasingly used in Identity and Access Management (IAM) to manage complex authorization requirements. Join us as we explore the challenges faced in authorization, the solutions provided by policy languages, and the benefits of using them.
Policy Languages are on the Rise
Domain-specific declarative languages have been a huge part of software development since its early days. They were created to tackle the complexities and requirements general-purpose programming languages struggled to manage and are now a part of every developer's toolkit.
In Identity and Access Management (IAM), authorization is becoming increasingly challenging in recent years - apps are getting more complex, as are user requirements. The result? a huge surge in domain-specific declarative languages focused on authorization. Older languages, such as Open Policy Agent’s Rego, are getting a facelift with their upcoming V1, and new languages and platforms like OpenFGA and, more recently, AWS’ Cedar, are being created.
It’s quite clear that significant steps are being made to tackle authorization’s ever-growing complexity. In this blog, we’ll talk about why these policy languages are rising in popularity, and how you can use them to your advantage to build better, more secure applications.
The Challenge of Fine-Grained Decisions
There’s a new problem in the software development world: the problem of decisions. Decisions are obviously not a new issue - it’s one of the most basic aspects of software development, and simple ‘if’ statements are at the very foundation of any programming language out there. But in the vast majority of cases, that’s not enough.
Many modern applications require extremely fine-grained decisions to be made, especially regarding security matters in authorization (handling what a user or service can or cannot do in your application). Let’s briefly go over these challenges:
Application Architecture
Working as a developer back in 2010, you could easily imagine having one server, one programming language, one database, and one application. Today, even the most basic apps start tons of services from the get-go, yet we still don’t want users to access data that they're not supposed to. This means all of these services need to have one concrete source of truth for their decision-making, and these decisions have to be streamlined across the entire stack.
Data Points and Decision Fatigue
In LAMP or older architectures, we used to have one SQL database. Even the simplest apps today use multiple data sources, which are only growing more complex by the minute. All of this data still needs to adhere to the same level of security when in comes to making decisions. That means the way of making these decisions is also growing increasingly complex.
The more data we have, the more decisions we need to make, and the more data needs to be included in that decision-making process. At some point, it gets too complex, and we can’t keep supporting more and more granularity.
Velocity and Frameworks
We’re delivering more software. That means more endpoints and more production environments that just keep growing. This creates the need for a layer we can trust to always make the right decision when it comes to access.
Confidence - Not Just End-Users Anymore
Users of modern-day applications are significantly more security aware. They want to own their data and manage its privacy, and you need to support that. That means supporting very fine-grained ownership, temporary data access functions, location-based access policies; you name it. On the other hand, app users are not really users in the classic sense anymore. Think DevOps, RevOps, and AppSec - they all want access to the code, and they affect the way that software should be delivered. Above all else, they affect how access decisions should be made.
And all of that complexity doesn't include the newest player on the block - AI Agents and LLMs. These create a problem of unstructured decisions, as they want unstructured access to our data - how do we provide them with the right access
This long list of challenges requires more complex solutions than what can be achieved with ‘if’ statements hard coded into your application. That’s where policy engines and policy languages come in.
The solution? Structure and Domain-Specific Declarative Code
Now that wev’e gained a better understanding of the problem, - let’s talk solutions.
The first step to solving this challenge is understanding where all these complex decisions should be made. This can be achieved by mapping out our application into a few authorization layers:
- The most basic layer is the code itself. Developers today have a lot of effect on how code is delivered and what production looks like. So the first decision that needs to be made is, basically put, “What can the developer do?”
- Then there are the services. Here there are the questions of “Which service can talk to other services?”, “How are they deployed in the CI/CD and speak with each other”?
- Above that is the application database. Here, we need to make the decision of “Who can read what from the database?”
- On top of that, there’s the application backend, where we need to decide “What actions can application users perform?”
- On top of everything else, we have the frontend, where we need to ask ourselves, “What can our application users see?”
With a clear structure of where authorization decisions should be made, it’s easier for us to design a solution that can actually handle them. To do that, we can use two important architectural principles:
- Centralized Policy Configuration: consolidating all our configuration in one place will help us streamline it and ensure that we're following all the standards we're trying to establish.
- Policy Engines: The Policy Engine is in charge of evaluating authorization queries, using the policy rules as a source of truth. Authorization policies are written in Policy Languages, which the policy engine interprets, providing a decision to any authorization query it is presented with. Having a piece of software that knows how to take the policies we configure and make the right decisions is a must.
This plane needs to be decentralized for several reasons: First, it has to react super fast, and second, we want every part of our stack to be able to communicate with this plane directly and enforce permissions based on these decisions.
Now that we've identified the need for using a decentralized authorization engine let’s dive deeper into the language these engines utilize to process and handle authorization queries - Policy Languages.
What are Policy Languages?
A policy language is a formal and structured way of defining rules, conditions, and logic for specifying policies. It provides a standardized syntax and semantics for expressing authorization rules and access control requirements, making it easier to manage and enforce security policies. There are many different policy languages, such as OPA’s Rego, AWS’ Cedar, OSO’s Polar, and more.
The Benefit of Using Policy Languages
The benefits of using Domain-Specific Declarative languages can help us overcome these challenges thanks to the benefits of having policy as code -
- They are readable. When you look at policy as code written in languages intended for authorization policies, you should immediately understand what is happening - who can do what, on what, and when.
- They improve performance. As all decisions are made in a single domain, nothing is in their way of being made and delivered with no latency.
Not only that - defining policies using code provides you with the ability to ensure policies are consistently enforced across different systems and environments, which can help prevent policy violations and reduce the risk of unauthorized access. It allows you to easily manage and update policies, as you do that with the same tools and processes used to manage and deploy software. This makes it easier to track changes to policies over time, roll back changes if necessary, and in general, enjoy the well-thought-through best practices of the code world (e.g., GitOps).
What Policy Languages are there?
There are many policy languages available to choose from, each more fit to handle different scenarios:
Open Policy Agent (OPA) - Rego
allow {
input.user.role == "viewer"
validate_department(input.user, input.document)
validate_classification(input.user.role, input.document.classification)
validate_dynamic_rules(input.user, input.document)
}
validate_department(user, document) {
user.department == document.department
}
validate_classification(user_role, doc_classification) {
role_permissions[user_role][_] == doc_classification
}
validate_dynamic_rules(user, document) {
dynamic_rules[_](user, document)
}
OPA started out as a multi-purpose policy engine, and that’s where its power comes from. It’s an extremely flexible language that can help you model any type of decision you want. The thing is, Rego can get quite complicated - it's not the perfect example of a declarative language being simple and intuitive, but it does provide you with the ability to handle extremely complex decisions on any layer.
If you have the ability to learn this new language, and you want to have one agent with one policy language across the stack, Open Policy Agent is a great choice.
Read more about it here: RBAC with Open Policy Agent (OPA)
AWS’ Cedar
permit (
principal == PhotoApp::User::"stacey",
action == PhotoApp::Action::"viewPhoto",
resource
)
when { resource in PhotoApp::Account::"stacey" };
Launched by AWS just one year ago, Cedar’s started as a language dedicated language for application-level authorization. Unlike AWS IAM, it's a language that can be used in any application. Cedar uses the Dafny language to provide scientific proof of correctness and performance, yet it is still challenging to use it when dealing with unstructured data, and it lacks ReBAC support. It’s a great option to use for fast ABAC based decisions, with auditing, static analysis, and partial evaluation supported out of the box.
Read more about it here: RBAC With AWS’ Cedar
OpenFGA
Not a policy language per-se, but more of an authorization platform based on Google’s Zanzibar white paper, OpenFGA is a great choice when it comes to handling ReBAC. Backed and maintained by Auth0 and used by them for authorization, with a graph-based engine built-in, it is the perfect solution for large-scale authorization implementations. OpenFGA is less suitable when it comes to RBAC and ABAC.
You can learn more about how it compares with Cedar here: OpenFGA
A broader overview and comparison of all three languages can be found here: How Open Policy Agent compares to AWS Cedar and Google Zanzibar
What do Policy Engines Lack?
While policy languages and engines provide us with a great basis for creating a separate microservice for authorization, both OPA and Cedar lack several key abilities. Let’s look at an example -
Say our policy requires a user to be a subscriber to see parts of our site. To make a policy decision, we need to know the user’s subscription status from our billing service (e.g. PayPal or Stripe) and be immediately aware of any changes to it: a new subscriber expects immediate access to our site, and a churned user should lose access on the spot.
That means our policy engine needs to be updated in real time, from outside data sources, while all of our decentralized policy engines are in sync with each other.
Open Policy Administration Layer (OPAL) is an OSS project created to aid with Policy Engine management, which keeps them updated in real-time with data and policy updates. Supporting both OPA and AWS’ Cedar, OPAL offers two important features:\
- OPAL allows tracking a specific policy repository (like GitHub, GitLab, or Bitbucket) for updates. It does this by either using a webhook or checking for changes every few seconds. This makes the system act as a Policy Administration Point (PAP), which sends the policy changes to your policy engine, ensuring it is always up to date.
- The ability to track any relevant data source (API, Database, external service) for updates via a REST API, and fetch up-to-date data back into your policy engine.
Using these capabilities allows you to take advantage of the full benefits of policy languages by keeping your policy engines up to date with all the relevant policies and data in real time.
Please consider supporting this open-source project by giving OPAL a star on GitHub.
Conclusion
Domain-specific declarative languages are proving to be crucial tools in managing complex tasks in software development. They help us build systems that are high-performing, secure, and user-friendly. Whether it's managing fine-grained access controls or adapting to the demands of AI agents, these languages are keeping us ahead of the curve.
Authorization is a critical component of any modern application, and we can see the tremendous benefit brought to this space with domain-specific declarative languages. Open Policy Agent (OPA), AWS' Cedar, and OpenFGA allow us to tackle the challenges that come with the modern state of IAM, while OPAL (Open Policy Administration Layer) enhances their functionality by automating the synchronization between the policy store and the real-time data required for decision-making, ensuring that policies are consistently applied with the latest relevant data. This integration enables us to create secure, reliable, and dynamic authorization systems that can adapt to changing conditions and requirements.
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.