Open Policy Agent: A Powerful Policy Engine for Cloud-Native Environments
In today’s fast-paced digital world, organizations need to enforce policies that ensure the security, reliability, and compliance of their applications and infrastructure. However, with the rise of cloud-native technologies, enforcing policies has become more challenging, as applications and infrastructure are more distributed and dynamic than ever before.
Open Policy Agent (OPA) is a tool that helps organizations enforce policies across their software systems. The project was created by Styra and it is currently maintaining by Cloud Native Computing Foundation.
Policies are rules that govern how a system should behave, and can include things like security policies (who can access what resources), compliance policies (making sure the system meets certain standards), and many others.
OPA works by providing a way for developers to write these policies in a language called Rego. Rego is a simple and easy-to-understand language that lets developers express policies in a way that’s both human-readable and machine-executable. Once the policies are written, OPA evaluates them at runtime to make sure they’re being followed correctly.
We might be thinking what’s New here, Just an extra layer behind HTTP Service layer which validated the HTTP request context.
No, One of the great things about OPA is that it’s designed to work with a wide range of software systems, including cloud-native platforms, containers, and Microservices. This means that it can help enforce unified policies across many different parts of an organization’s technology stack.
It can be integrated with APIs, GraphQl, Service Mesh like Istio, the Linux SSH daemon, an object store like CEPH, Event streaming platform like Kafka, Infrastructure Provisional tool like Terraform and so on. OPA designers purposefully avoided basing it on any other project. Accordingly, the policy query and decision do not follow a specific format. That is, you can use any valid JSON data as request attributes as long as it provides the required data. Similarly, the policy decision coming from OPA can also be any valid JSON data. You choose what gets input and what gets output. For example, you can opt to have OPA return a True or False JSON object, a number, a string, or even a complex data object.
Another advantage of OPA is that it provides a centralized way to manage policies across multiple applications and environments. This makes it easier to ensure that policies are being followed consistently, no matter where they’re being applied. Isn’t is super cool 😎 ?? Different Teams just need to fucus on their services related Implementation. After service deployment organizations standard policy will be applied to all the Services layer so that all service will be secured in same manner.
How to install
Download the binary with the following curl command and save it under the name opa.
Linux (64-bit):
$ curl -L -o opa https://openpolicyagent.org/downloads/latest/opa_linux_amd64
macOS (64-bit):
curl -L -o opa https://openpolicyagent.org/downloads/v0.50.1/opa_darwin_amd64
After downloading the file, execute the chmod command to grant permission.
$ chmod 755 opa
Test Installation
Let’s run the OPA installation to see if it works properly. Move to the directory where opa is installed. Run ./opa in Linux and Mac OS. If you have registered PATH environment variables, you can run them directly with opa. If installed normally, the following results can be seen.
$ ./opa
An open source project to policy-enable your service.
Usage:
opa [command]
Available Commands:
bench Benchmark a Rego query
build Build an OPA bundle
check Check Rego source files
deps Analyze Rego query dependencies
eval Evaluate a Rego query
fmt Format Rego source files
help Help about any command
parse Parse Rego source file
run Start OPA in interactive or server mode
sign Generate an OPA bundle signature
test Execute Rego test cases
version Print the version of OPA
Flags:
-h, --help help for opa
Use "opa [command] --help" for more information about a command.
Run OPA
Let’s create a simple policy and evaluate.We will going to check if input text is match with “hello_world”.
// hello.rego
package hello
default allow_ok = false
default allow_ng = false
allow_ok {
"hello_world" == input.text
}
allow_ng {
"hello_world" != input.text
}
// input.json
{
"text": "hello_world"
}
$ opa eval --data hello.rego --input input.json "data.hello.allow_ok"
{
"result": [
{
"expressions": [
{
"value": true,
"text": "data.hello.allow_ok",
"location": {
"row": 1,
"col": 1
}
}
]
}
]
}
$ opa eval --data hello.rego --input input.json "data.hello.allow_ng"
{
"result": [
{
"expressions": [
{
"value": false,
"text": "data.hello.allow_ng",
"location": {
"row": 1,
"col": 1
}
}
]
}
]
}
This is how we simply can write a policy for checking input message and evaluate the request through policy.
A Rego Playground provides a great editor to get started with OPA and share policies. Try it out at https://play.openpolicyagent.org/
Deployment
There are multiple deployment options available for OPA depending on the specific scenario at hand, such as:
- You can deploy OPA as a standalone binary on your machine or in a containerized environment using Docker.
- OPA can also be integrated with popular container orchestration platforms like Kubernetes as a sidecar proxy or a dedicated server.
- Another option is to deploy OPA as a library within your application code, allowing for more fine-grained control over policy enforcement.
- Additionally, OPA can be deployed as a service, providing a centralized policy engine for multiple applications and services to use.
Overall, the choice of deployment option for OPA should be based on factors such as your infrastructure environment, scalability requirements, and the level of policy control needed for your applications.
To know more about deployment follow bellow link https://www.openpolicyagent.org/docs/latest/deployments/
Example Use Case of OPA
Let’s say that we are implementing the Salary Service of our example application. This service is responsible for handling Employee’s salary. It exposes an API where it accepts adjustment(Create/Read/Update/Delete) of salary information for an Employee and this privilege will only given to HR Department. It also allows the Employee to query about his current salary information and also gave access to Manager to view all his downline salary information.
So, to obtain an object containing the salary information service team decided to expose an API with the path /salary/{employee}. Employee can provide their credential information in the Authorization header and send the request. The response would be a JSON data. However, since as a system we don’t want just anyone with network access to have access to the salary API to see others sensitive data, we need to enforce an authorization policy.
In above scenario, Rob can view his salary but he cant see Bob’s salary, Rob’s manager can view his salary and also Bob salary too. HR can view and also update both Bob’s and Rob’s salary.
Now, let’s create our policy for above scenario.
// salary_policy.rego
package play
import future.keywords.if
import future.keywords.in
default allow = false
read_only_all := {
"manager",
"hr",
}
crud_method := {
"GET",
"UPDATE",
"CREATE",
"DELETE"
}
crud_user := {
"hr"
}
allow if {
input.method == "GET"
input.path = ["payments", customer_id]
input.user == customer_id
}
allow if {
input.method == "GET"
input.user in read_only_all
}
allow if {
input.method in crud_method
input.user in crud_user
}
Now, create request payload and see what returns by our policy.
// rob is requesting his own salary
{
"method": "GET",
"path": ["payments","bob"],
"user": "bob"
}
$ opa eval --data example.rego --input input.json "data.play.allow"
{
"result": [
{
"expressions": [
{
"value": true,
"text": "data.play.allow",
"location": {
"row": 1,
"col": 1
}
}
]
}
]
}
Bob can see his own salary which is as expected.
// rob is requesting for creating his own salary
{
"method": "CREATE",
"path": ["payments","bob"],
"user": "bob"
}
$ opa eval --data example.rego --input input.json "data.play.allow"
{
"result": [
{
"expressions": [
{
"value": false,
"text": "data.play.allow",
"location": {
"row": 1,
"col": 1
}
}
]
}
]
}
Bob cant create his salary as because HR only has rights to create his salary not by himself.
Now lets if Rob can access Bob’s salary.
// rob is requesting Bob's salary
{
"method": "GET",
"path": ["payments","bob"],
"user": "rob"
}
$ opa eval --data example.rego --input input.json "data.play.allow"
{
"result": [
{
"expressions": [
{
"value": false,
"text": "data.play.allow",
"location": {
"row": 1,
"col": 1
}
}
]
}
]
}
So Rob cant see Bob’s salary which is also as correct.So far our policy is working as expected.
Now, let’s see if Manager can see his downline Rob’s & Bob’s salary. Manager can only view but can’t update anyone’s salary.
// Manager is requesting Bob's salary
{
"method": "GET",
"path": ["payments","bob"],
"user": "manager"
}
$ opa eval --data example.rego --input input.json "data.play.allow"
{
"result": [
{
"expressions": [
{
"value": true,
"text": "data.play.allow",
"location": {
"row": 1,
"col": 1
}
}
]
}
]
}
// Manager is requesting Bob's salary
{
"method": "CREATE",
"path": ["payments","bob"],
"user": "manager"
}
$ opa eval --data example.rego --input input.json "data.play.allow"
{
"result": [
{
"expressions": [
{
"value": false,
"text": "data.play.allow",
"location": {
"row": 1,
"col": 1
}
}
]
}
]
}
Great, So Manager can access his downline’s salary but he cant update his downline’s salary.
Finally let’s check if HR has all access to salary system. HR should have privilege to read, create, update, delete of anyone’s Salary.
// HR is requesting Bob's salary
{
"method": "GET",
"path": ["payments","bob"],
"user": "hr"
}
$ opa eval --data example.rego --input input.json "data.play.allow"
{
"result": [
{
"expressions": [
{
"value": true,
"text": "data.play.allow",
"location": {
"row": 1,
"col": 1
}
}
]
}
]
}
// HR is Creating Bob's salary
{
"method": "CREATE",
"path": ["payments","bob"],
"user": "hr"
}
$ opa eval --data example.rego --input input.json "data.play.allow"
{
"result": [
{
"expressions": [
{
"value": true,
"text": "data.play.allow",
"location": {
"row": 1,
"col": 1
}
}
]
}
]
}
// HR is Deleting Bob's salary
{
"method": "DELETE",
"path": ["payments","bob"],
"user": "hr"
}
$ opa eval --data example.rego --input input.json "data.play.allow"
{
"result": [
{
"expressions": [
{
"value": true,
"text": "data.play.allow",
"location": {
"row": 1,
"col": 1
}
}
]
}
]
}
Awesome 👏 , Our Policy is working exactly what we have decided. So now Salary Service Team doesn’t need to handle this Business Policy as this will handle before Application Layer, Service Team can develop their own logic in their Application Layer.
Now, If organization decides to create another service called Attendance Service in-that case also we can simply apply similar policy without writing this logic in Application Layer.
This is how organization can enforce policies to ensure the same security throughout their all applications and infrastructure with the help of Open Policy Agent.
If you want to know how OPA can be used to secure your Kubernetes Cluster, please have a look my bellow article.