oso-default-opengraph

5 Open Policy Agent Examples and Use Cases

1. Introduction to Open Policy Agent (OPA)

If you're building modern applications, especially those using microservices or Kubernetes, you've probably bumped into the challenge of authorizing access to infrastructure. It's not just about who can log in, but what services they can access once they're in, and what those services can access. Today I want to discuss OPA and how we can use it to reason about and implement policy enforcement.

So, what exactly is OPA? At its core, OPA is an open-source, general-purpose policy engine. Think of it as a brain that makes decisions about whether something is allowed or not. The beauty of OPA is that it decouples policy decision-making from policy enforcement. This means your application or service doesn't need to know how to make a policy decision, it just needs to ask OPA, "Is this allowed?" and OPA will give a clear answer.

This is super important, because without proper consideration, you can end up with a hard to manage mess of policies sprinkled all throughout your code. You have policies for who can access your APIs, what containers can run in Kubernetes, how your CI/CD pipelines behave, and even what data can be accessed in your databases. Trying to hardcode these policies into every single service is a nightmare to manage and update. OPA provides a unified way to manage all these policies as code, making them consistent, auditable, and much easier to maintain.

Here's how it generally works: Your software queries OPA, providing structured data (usually JSON) as input. This input could be something like a user's identity, the resource they're trying to access, the time of day, or even environmental variables. OPA then evaluates this input against its policies, which are written in a high-level declarative language called Rego. The output is a decision, which can be a simple allow/denyor more complex structured data, depending on what your policy needs to convey.

In this article, I'm going to walk you through some practical examples and use cases where OPA truly shines. We'll dive into the details of Rego and see how you can use OPA to solve real-world authorization challenges. Let's get started!

2. Understanding Rego: OPA's Policy Language

Now that you have a grasp of what OPA is, let's talk about Rego. Rego is the language you'll use to write your policies in OPA. It's a declarative language, which means you describe what you want to achieve, not how to achieve it. It's specifically designed for expressing policies over complex, hierarchical data structures like JSON.

In Rego, policies are defined as rules. These rules essentially define what is true or false based on the input data. Let's look at some core concepts:

Rules

Rules in Rego can be either complete or partial.

  • Complete Rules: These assign a single value to a variable. For example, a rule that defines whether a request is allowed or denied.


default allow = false

allow {
   input.method == "GET"
   input.path == ["users"]
}

In this simple example, allow is true only if the input method is "GET" and the path is "users". Otherwise, it defaults to false. This means that the following input would result in true .

"input": {
 "method": "GET",
 "path": ["users", "123"]
}

  • Partial Rules: These generate a set of values and assign that set to a variable. This is useful for collecting multiple results that satisfy a condition.

allowed_users[user] {
   data.users[user].role == "admin"
}

This rule would create a set of all users who have the role "admin".

So given the following input,

{
 "users": {
   "alice": { "role": "admin" },
   "bob": { "role": "user" },
   "charlie": { "role": "admin" }
 }
}

you’d get this out.

allowed_users = {"alice", "charlie"}

Expressions

Rego policies are built using expressions. Multiple expressions within a rule are implicitly joined by a logical AND. All expressions must evaluate to true for the rule to be true. For example:

allow {
   input.user == "alice"
   input.action == "read"
   input.resource == "data"
}

Here, all three conditions must be met for allow to be true. Here’s an example input that would result in  true.

{
 "user": "alice",
 "action": "read",
 "resource": "data"
}

Variables

You can use variables to store intermediate values or to iterate over collections. Variables are assigned using the := operator. OPA will find values for variables that make all expressions true.

allow {
   user := input.user
   data.roles[user] == "admin"
}

In this case, user is assigned the value of input.user, and then that user is checked against the data.roles to see if they are an "admin".

Iteration

Iteration in Rego often happens implicitly when you use variables in expressions. For example, to check if any element in a list meets a condition, you can use a variable to represent each element:

allow {
   some i
   input.roles[i] == "admin"
}

This rule would be true if any of the roles in input.roles is "admin". The some keyword is used to declare local variables that are used for iteration.

If you want to return true if roles only contains admin, then you can do the following.

allow {
   not some i
   input.roles[i] != "admin"
}

This says “Allow if there does not exist any index i such that input.roles[i] != "admin"”. In other words, all roles must be “admin”.

This is just a quick overview of Rego basics. The official OPA documentation is an excellent resource for a deeper dive. Now, let's get into some real-world use cases!

3. OPA Use Cases and Practical Examples

Now let’s se OPA in action! I’ve picked out five common scenarios where OPA can be incredibly powerful. For each, I’ll give you a brief scenario, explain how OPA fits in, and provide a Rego policy example.

Use Case 1: Attribute-Based Access Control (ABAC) for Cloud Resource Access

Imagine you’re managing access to cloud resources (e.g., AWS S3 buckets, Azure Blob Storage, Google Cloud Storage). It’s not enough to just know who is trying to access a resource; you need granular control based on various attributes. For instance, a user might only be allowed to access a specific S3 bucket if they are part of a certain team, the request originates from a whitelisted IP range, and the time of day is within business hours. This is a classic ABAC problem, where access decisions are based on attributes of the user, the resource, the environment, and the action.

How OPA helps: OPA is perfectly suited for ABAC because Rego excels at evaluating complex conditions based on structured data. You can feed OPA all the relevant attributes (user roles, source IP, time of day, resource tags, etc.), and your Rego policy will determine if the access is authorized.

Let’s look at a simplified example for controlling access to an S3 bucket. We want to ensure that:

1.The user is part of the 'devops' team.

2.The request comes from an IP within the corporate network range.

3.The access attempt is during business hours (9 AM to 5 PM UTC).

Here’s how you might write a Rego policy for this:

default allow = false

allow {
   input.user.team == "devops"
   is_from_whitelisted_ip
   is_during_business_hours
}

is_from_whitelisted_ip {
   ip_range_contains("192.168.1.0/24", input.source_ip)
}

is_during_business_hours {
   current_hour := time.now_ns() / 1000000000 / 60 / 60 % 24
   current_hour >= 9
   current_hour < 17
}

# Helper function to check if an IP is within a CIDR ip_range_contains(cidr, ip) if {
   # Simplified for example, real implementation would parse CIDR and IP
   # and perform network calculations.
   startswith(ip, "192.168.1.")
}


{
 "user": {
   "name": "alice",
   "team": "devops"
 },
 "source_ip": "192.168.1.50",
 "action": "read",
 "resource": "s3_bucket_logs"
}

If you were to run this with opa eval -d policy.rego -i input.json "data.app.abac.allow", the output would be true (assuming the current time is within business hours and the IP is whitelisted). This demonstrates how OPA can enforce attribute-based policies for infrastructure access.

Use Case 2: Role-Based Access Control (RBAC) for Server Access

Let's say you have a fleet of servers, and different teams or individuals need varying levels of access to them. For example, 'network admins' can configure network interfaces, 'devops' can deploy applications, and 'auditors' can only view logs.

In this kind of scenario, OPA can centralize your infrastructure’s RBAC policies. This makes it easier to manage user roles and their associated permissions across your server fleet. Instead of scattering authorization logic across SSH configurations or individual server scripts, you define it once in OPA. When a user tries to perform an action on a server, your system queries OPA with the user's role and the requested action. Then OPA returns whether it's allowed.

Here’s a Rego policy that implements a basic RBAC system for server access:

default allow = false

# Allow if the user has the required role for the action on the resource
allow {
   some role in data.user_roles[input.user]
   some grant in data.role_grants[role]
   input.action == grant.action
   input.resource_type == grant.resource_type
}

The above Rego code is analogous to the following pseudocode in Python.

for role in data.user_roles[input.user]:
   for grant in data.role_grants[role]:
       if input.action == grant["action"] and input.resource_type == grant["resource_type"]:
           allow = True

Input Data (example data.json):

# Example: network_admin can configure network interfaces
# Example: devops can deploy applications
# Example: auditor can view logs

{
   "user_roles": {
       "alice": [
           "auditor"
       ],
       "bob": [
           "devops",
           "network_admin"
       ],
       "charlie": [
           "auditor",
           "devops"
       ]
   },
   "role_grants": {
       "auditor": [
           {
               "action": "view",
               "resource_type": "logs"
           }
       ],
       "devops": [
           {
               "action": "view",
               "resource_type": "logs"
           },
           {
               "action": "deploy",
               "resource_type": "application"
           }
       ],
       "network_admin": [
           {
               "action": "view",
               "resource_type": "logs"
           },
           {
               "action": "deploy",
               "resource_type": "application"
           },
           {
               "action": "configure",
               "resource_type": "network_interface"
           }
       ]
   }
}

Input Query (example input.json for Alice trying to view logs):

{
 "user": "alice",
 "action": "view",
 "resource_type": "logs"
}

Running opa eval -d policy.rego -d data.json -i input.json "data.app.rbac.allow" would return true for Alice. If Alice tried to deploy an application, it would return false. This example shows how you can define roles and their permissions for infrastructure components, and then easily check if a user, based on their assigned roles, is authorized to perform a specific action. It’s a clean and scalable way to manage access control for your infrastructure.

Use Case 3: Kubernetes Admission Control

Scenario: Kubernetes is fantastic for orchestrating containers, but how do you ensure that only approved images are deployed, or that all deployments have specific labels for cost allocation or security? Kubernetes Admission Controllers intercept requests to the Kubernetes API server before objects are persisted. This is a perfect choke point for policy enforcement.

How OPA helps: OPA can act as a validating or mutating admission controller in Kubernetes. This means you can write policies in Rego that dictate what can and cannot be deployed to your clusters, or even modify resources on the fly. This is incredibly powerful for maintaining security, compliance, and operational best practices across your Kubernetes environments. If you're a company that needs strict control over your Kubernetes deployments, OPA as an admission controller is, in my opinion, a must-have.

Let's consider a policy that prevents deployments from using images from unapproved registries and ensures all deployments have a team label.


default allow = false

allow {
   input.request.kind.kind == "Pod"
   image_is_approved
   has_team_label
}

image_is_approved {
   some i
   image := input.request.object.spec.containers[i].image
   startswith(image, "approved-registry.com/")
}

has_team_label {
   input.request.object.metadata.labels.team
}

Input Query (example input.json for a Pod creation request):

{
 "apiVersion": "admission.k8s.io/v1",
 "kind": "AdmissionReview",
 "request": {
   "uid": "705ab455-63f4-11e8-b7ad-0242ac110002",
   "kind": {
     "group": "",
     "version": "v1",
     "kind": "Pod"
   },
   "resource": {
     "group": "",
     "version": "v1",
     "resource": "pods"
   },
   "object": {
     "metadata": {
       "labels": {
         "app": "my-app",
         "team": "devops"
       }
     },
     "spec": {
       "containers": [
         {
           "name": "my-container",
           "image": "approved-registry.com/my-image:latest"
         }
       ]
     }
   }
 }
}

Running opa eval -d policy.rego -i input.json "data.kubernetes.admission.allow" would return true for this valid request. If the image was from unapproved-registry.com or the team label was missing, the policy would evaluate to false, and Kubernetes would reject the admission request. This provides a robust and centralized way to enforce policies in your Kubernetes clusters.

Use Case 4: API Gateway Authorization

Scenario: Your microservices architecture likely exposes APIs through an API Gateway. This gateway is the first line of defense for your backend services. You need to authorize incoming requests, perhaps based on JWT claims, IP addresses, or even rate limits. Hardcoding this logic into each microservice is inefficient and error-prone.

How OPA helps: OPA can be integrated with API Gateways (like Envoy, Kong, or AWS API Gateway) to offload authorization decisions. When a request comes into the gateway, it sends the request details (headers, body, path, method) to OPA. OPA evaluates these details against your policies and sends back an allow/deny decision. This centralizes your API authorization logic, making it easier to manage and update policies across all your APIs.

Let's imagine a policy where only authenticated users with a specific role can access a sensitive API endpoint, and only from a whitelisted IP range.


default allow = false

allow {
   input.method == "GET"
   input.path == ["v1", "sensitive_data"]
   is_authenticated
   has_required_role
   is_from_whitelisted_ip
}

is_authenticated {
   input.headers.authorization
   # In a real scenario, you'd decode and validate the JWT here
   # For simplicity, we just check for presence of the header
}

has_required_role {
   input.jwt_claims.roles[_] == "admin"
}

is_from_whitelisted_ip {
   ip_range_contains("192.168.1.0/24", input.source_ip)
}

# Helper function to check if an IP is within a CIDR range
# This would typically be a built-in or a more robust library function
ip_range_contains(cidr, ip) if {
   # Simplified for example, real implementation would parse CIDR and IP
   # and perform network calculations.
   # For demonstration, let's assume a simple string match for now.
   startswith(ip, "192.168.1.")
}

Input Query (example input.json for a valid API request):

{
 "method": "GET",
 "path": ["v1", "sensitive_data"],
 "headers": {
   "authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
 },
 "jwt_claims": {
   "sub": "user123",
   "roles": ["user", "admin"]
 },
 "source_ip": "192.168.1.100"
}

Running opa eval -d policy.rego -i input.json "data.api.authz.allow" would return true for this request. If the user didn't have the admin role, or the request came from an unwhitelisted IP, OPA would return false, and the API Gateway would block the request. This centralized approach to API authorization is, in my opinion, a much cleaner way to manage access to your services.

Use Case 5: CI/CD Pipeline Policy Enforcement

Scenario: In a fast-paced development environment, ensuring that your CI/CD pipelines adhere to security, compliance, and operational best practices is crucial. This could involve mandating code reviews for all merges to main, ensuring that specific security scans are run before deployment, or restricting deployments to production environments based on certain criteria (e.g., only from specific branches or by authorized personnel).

How OPA helps: OPA can be integrated into various stages of your CI/CD pipeline to enforce policies. This allows you to "shift left" on security and compliance, catching issues earlier in the development lifecycle. By externalizing these policies to OPA, you keep your pipeline scripts clean and focused on their primary tasks, while OPA handles the complex policy evaluations. If you're looking to automate and standardize your pipeline governance, OPA is an excellent choice.

Let's create a policy that ensures all deployments to the production environment must originate from the main branch and have been approved by at least two reviewers.

default allow = false

allow if {
   input.environment == "production"
   input.source_branch == "main"
   input.pull_request.approvals >= 2
}

Input Query (example input.json for a production deployment request):

{
 "environment": "production",
 "source_branch": "main",
 "pull_request": {
   "id": "pr-123",
   "approvals": 2,
   "status": "merged"
 },
 "user": "dev_lead"
}



Running opa eval -d policy.rego -i input.json "data.ci_cd.policy.allow" would return true for this deployment. If the source_branch was feature/new-feature or approvals was less than 2, the policy would return false, preventing the deployment. This helps ensure that only well-vetted and compliant code makes it to your critical environments. In my experience, this kind of automated governance is invaluable for maintaining high standards in continuous delivery.

4. OPA vs. Oso: When to Use Which?

So now that we have a good idea of how OPA and Rego work, you’re probably wonder if you should implement this in your system. One question you want to answer is whether you want to go with native OPA or use Oso. Both are fantastic tools for authorization, but they shine in different contexts. Think of it this way:

OPA: The Heavyweight, Flexible, Infra-Friendly Policy Engine

In my opinion, OPA is your go-to when you need to enforce policies outside your application. It's designed to be a general-purpose policy engine that can be integrated across your entire stack. This includes:

  • Infrastructure: Kubernetes admission control, Terraform policy enforcement.
  • Microservices: API Gateway authorization, service-to-service authorization.
  • CI/CD Pipelines: Ensuring compliance and security throughout your deployment process.

OPA is incredibly flexible because it decouples policy from enforcement. You write your policies in Rego, and then various services query OPA for decisions. This makes it ideal for broad, system-level policy enforcement where you need a consistent policy layer across diverse technologies. If you're building a large, distributed system and need a centralized policy decision point for your infrastructure and services, OPA is, in my humble opinion, the clear winner.

Oso: The Batteries-Included Tool for Modern Apps

Oso, on the other hand, is built for enforcing access logic inside your application. It's a batteries-included framework that simplifies adding authorization directly into your application code. Oso provides libraries for various languages (Python, Node.js, Go, Rust, etc.) and a declarative policy language called Polar.

Oso is perfect for scenarios where you need fine-grained, application-specific authorization, such as:

  • User Permissions: Determining what a specific user can do within your application (e.g., "Can Alice edit this document?").
  • Multi-tenancy: Managing access to resources across different tenants.
  • Resource-level Authorization: Controlling access to individual resources based on ownership or relationships.

If you're a developer building a new application and you want to quickly and effectively implement authorization logic directly within your codebase, Oso is an excellent choice. It's designed to be developer-friendly and provides a more integrated experience for application-level authorization.

Practical Guidance on Choosing

Here's how I think about it:

  • Use OPA where you enforce policies outside the app. This means policies related to your infrastructure, network, or inter-service communication. It's about controlling the environment your applications run in.
  • Use Oso where you enforce access logic inside the app. This is about controlling what users can do with the data and features within your application.

It's not necessarily an either/or situation. Many organizations might find value in using both. For example, you could use OPA for your Kubernetes admission control and API Gateway authorization, while using Oso to manage user permissions within your application's backend. The key is to understand their strengths and apply them where they make the most sense for your specific authorization challenges.

5. Conclusion

It was quite the journey, but we did it. We looked at five practical examples showcasing the power and versatility of Open Policy Agent. From fine-grained ABAC and RBAC within your applications to robust policy enforcement in Kubernetes, API Gateways, and CI/CD pipelines, OPA provides a consistent and scalable way to manage authorization across your entire stack. Its declarative policy language, Rego, allows you to express complex rules with clarity and precision.

We also touched upon the distinction between OPA and Oso. In my opinion, understanding their core strengths is key: OPA excels at externalizing policy decisions for your infrastructure and services, while Oso is a fantastic tool for building application-level authorization directly into your code. Both are powerful, and often, they can complement each other beautifully in a comprehensive authorization strategy.

About the author

Mathew Pregasen

Technical Writer

Mathew Pregasen is a technical writer and developer based out of New York City. After founding a sales technology company, Battlecard, Mathew focused his attention on technical literature, covering topics spanning security, databases, infrastructure, and authorization. Mathew is an alumnus of Columbia University and YCombinator.

Level up your authorization knowledge

Learn the basics

A list of FAQs related to application authorization.

Read Authorization Academy

A series of technical guides for building application authorization.

Explore more about Oso

Enterprise-grade authorization without redoing your application architecture.