Decoupling Policy from Applications with Open Policy Agent

Klarrio
7 min readNov 28, 2023

Robust access control and security measures are increasingly challenging as software systems grow in complexity and scale.

At Klarrio, this led us to explore Open Policy Agent (OPA), a versatile open-source tool that offers powerful capabilities for managing and enforcing security policies across diverse systems.

In this article, we’ll delve into why and how to integrate OPA into your architecture.

Note: This article is based on our colleague Lander Visterin’s insightful video presentation @Devoxx Belgium 2023, which also features a practical demo. Watch the video demonstration.

Understanding Access Control: Authorization vs. Authentication

When we talk about access control, two important aspects come into play: authorization (authz) and authentication (authn).

Authorization is about defining and enforcing what actions a verified user can perform on a resource. Authentication, on the other hand, deals with verifying the identity of a user through mechanisms like Single Sign-On (SSO) and Multi-Factor Authentication (MFA).

Access Control Models

To enforce authorization, multiple access control models can be used:

  • Access Control Lists (ACLs): Managing the list of users that have access to a resource, for each resource.
  • Role-Based Access Control (RBAC): This model simplifies management by grouping users into roles with certain permissions.
  • Attribute-Based Access Control (ABAC): A more dynamic approach that considers multiple attributes of both the user and the resource to make access decisions. ABAC can be seen as a superset of ACLs and RBAC, enabling more nuanced access control policies. It’s easy to maintain, but the downside: it’s more complex to implement.

Decoupling Policy from Application Code

Managing systems with multiple applications often presents a challenge in ensuring consistent access control.

Initially, it might seem logical to implement a different policy language or framework for each application. But your future self might disagree once your system’s growth spurt kicks in.

Instead of reinventing the wheel ad infinitum, you could adopt a shared authorization system strategy. This allows you to focus on building and improving your policies semi-independently from your applications, which is particularly beneficial when your team scales up.

Additionally, it offers the flexibility to build and maintain a simple interface that can be applied across multiple applications, simplifying policy management.

Building Blocks

The following components are involved:

  • Policy Decision Point (PDP): Contains the logic for making access decisions based on defined policies.
  • Policy Administration Point (PAP): The management layer where policies are defined and distributed to the PDPs.
  • Policy Information Point (PIP): An optional component that provides additional information the PDP might require to make informed decisions.
  • Audit Log: Keeps a record of all decisions made by the system for compliance and analysis.

Introduction to Open Policy Agent (OPA)

Open Policy Agent (OPA) is a general-purpose policy engine that can serve as the PDP in a decoupled architecture. It’s a CNCF graduated open-source project, signifying its maturity and large community.

Integrating OPA in Your Application

There are several ways to integrate OPA with your system:

  • Standalone OPA server, running it as a standalone binary alongside your application. It runs on the same host as the application for improved latency and reliability. On Kubernetes, you might implement this as a sidecar.
  • Implementing the OPA SDK, if you’re working with Golang. It imports OPA entirely in your application. But it doesn’t serve the full OPA API, only the part that allows you to query the policy, and obviously, you can only use Go.
  • Using the Rego Go library or compiling the policies into WebAssembly. The big downside is that you’ll need to manually implement functionalities like loading the policy from a remote endpoint, in exchange for more low-level control.

Policy Distribution: The Role of Policy Bundles

If OPA has been set up and integrated, you still need to get the policy into it. The central point where your policy comes from is called the Policy Administration Point (PAP).

This is where the policy bundles come into the picture. These are archives containing Rego code and extra data required by your policy logic. You can distribute these bundles, using an OCI registry, an object store like Amazon S3, or by implementing the OPA bundle API (HTTP get endpoint).

Monitoring the rollout and integrity of these bundles is crucial for the security and consistency of your policy system. You can implement the OPA status API, which would be called whenever Open Policy Agent has an update, or track it through Prometheus metrics.

Verifying the Integrity of the Policy Bundle

As your policy bundle is distributed across various systems in different locations, it’s crucial to verify its authenticity and integrity. This can be achieved through cryptographic signatures.

To sign a policy bundle, you can use the `opa sign` command. It generates a JWT, which can be packaged into the bundle. The JWT is then verified out of the box by a key that you configure in your OPA instance.

Key management is limited here, as you can only use a single static keypair.

To improve this, you could for example implement a custom interface that OPA provides for extension. This would enable integration with your cloud key management solution, offering a more secure approach than managing the keys yourself.

If you’re using Open Container Initiative (OCI) artifacts to distribute your policy bundles, you might be tempted to apply the same tools you utilize for signing your container images, such as Sigstore or Cosign.

Unfortunately, verifying these signatures is not supported out of the box by OPA. It would require additional configuration and setup. The built-in `opa sign` command is typically sufficient for the needs of most users.

Audit Logging and Policy Testing

Using the standalone server or SDK, you can configure event logging to the console or a designated HTTP endpoint.

With the complete input, output, and policy version stored in the log, you can answer questions like “why was this decision made”, which is particularly useful if you include some kind of request ID in the user-facing error message to find the exact event with. This way, finding bugs in your authorization rules becomes trivial, since you can also locally replay it.

Change impact analysis is also a great superpower your audit logs can give you. Given historical data, and a proposed new policy, you can write some code to calculate what kinds of past events would turn out differently if it were deployed now. This way, you can catch mistakes before they ship, or verify the effectiveness of your rule change.

Another way of verifying your changes is, as we typically do in software development, by writing unit tests. Policies are a great target for tests since inputs and outputs are very well-contained. OPA already provides a way of building and running tests, making it very easy to get started.

Policy Authoring (PAP): Best Practices and Considerations

When writing your policies, there are some best practices you should take into account:

  • Avoid negative rules: Start your policies with a deny by default rule and incrementally allow access rather than denying it, reducing complexity and potential errors.
  • Use version control for policies: Treat policy code like software code, using Git for change history and collaborative management.
  • Consider building policy authoring tools: For ease of use and increased transparency, especially for those who may not be comfortable writing Rego code. You can choose to write your core logic in Rego and express policies as structured JSON data, but it’s also possible to let user-friendly tools output Rego directly.

Your OPA Implementation Action Plan

Are you convinced that OPA might be right up your alley? Then consider these practical steps to get started:

  1. Thoroughly assess your applications for policy control needs, and gather the global access control requirements.
  2. Define all the subject and resource attributes.
  3. Establish a schema for policy requests, ensuring consistency across your system. Evaluate the need for a PIP; if it’s not essential, you can simplify your architecture. Otherwise, plan its implementation based on your data requirements.
  4. Determine your PDPs for enforcing decisions within the application flow. Decide if you’ll deploy an OPA agent with each application instance or opt for a more granular approach depending on the use case.
  5. Consider who will read and write your policy. Determine whether you will need to create user interfaces for policy authoring or if the policies will be managed directly by developers. In the latter case, the policies might simply be code artifacts that are maintained in a version control system like Git.

Glossary

  • ABAC: Attribute-Based Access Control
  • ACL: Access Control Lists
  • OPA: Open Policy Agent
  • PAP: Policy Administration Point
  • PDP: Policy Decision Point
  • PIP: Policy Information Point
  • RBAC: Role-Based Access Control

Author: Lander Visterin
Software Architect @ Klarrio

Lander is a Software Architect at Klarrio with a passion for cloud-native infrastructure. He has worked on solving problems to make cloud-agnostic, multi-tenant platforms scalable and maintainable.

For more information, you can visit our website, www.klarrio.com, or follow us on LinkedIn and Twitter.

--

--

Klarrio

Klarrio empowers you with tailor-made, scalable data platforms & microservices for real-time data processing across various cloud & on-premises infrastructure.