Enabling policy as code (PaC) with OPA and Rego
January 19, 2022
0 mins readThe Cambridge Dictionary defines a policy as: "a set of ideas or a plan of what to do in particular situations that has been agreed to officially by a group of people, a business organization, a government, or a political party." And in the context of software development, your organization may have some rules about how a policy is built, configured, deployed, and used. Some examples of software policies include:
Application policies
Certain web service contexts can only be accessed by internal users.
Regional features will only be shown for requests by users with IP addresses in the same geo-location.
Transactions over certain amounts are only allowed for users with managerial roles.
Build & deploy policies
All open source application libraries must use one of the approved licenses.
All container images must be pulled from specific registries.
No Kubernetes Pod should ever run in Privileged mode.
A ServiceAccount’s default token should not be auto-mounted into a container.
Policy as code (PaC)is the process of defining policies in code leveraging a high-level declarative language, enabling you to automate enforcement of and centrally manage your policies. Doing so reduces the need for duplicated logic across your application services which are increasingly distributed and may use a diverse collection of languages and platforms.
In this blog, we'll be taking a look at Open Policy Agent (OPA) and its Rego rule language which can be used to help streamline your PaC efforts both for your own applications as well as enforcing policy for containerized workloads on Kubernetes.
Uniform policy definition across disparate services
Application policy enforcement is often implemented in-line in the code. In today’s distributed systems, the same logic may be duplicated. This effect can then be compounded if those disparate services are written by different teams and/or with different technologies.
Having standardized policy definitions and enforcement tooling that can be used by all teams across your organization — regardless of the language they are written in or frameworks they run on — is essential. Uniform policy definitions allow for simple, flexible implementation and easier compliance governance for everyone involved.
Proactive vs. reactive policy enforcement for build and deploy
Build and deployment policy rules are often enforced through a combination of manual code reviews, static analysis (hopefully automated into your CI/CD pipeline), or simply the threat of disciplinary action if someone violates the policy. These practices are reactionary, alerting us to policy violations when some kind of audit is being done. This may work but it often creates an adversarial relationship between the policymakers and the developers that may not be fully aware of the existence of policy rules.
Implementing policies and enforcing them in the developer’s workflow, either as part of their build processes or as a gate to deployment allows you to stop violations as they are created rather than having to rely on late-stage auditing or manual reviews.
Uniform policy application across disparate environments
Once you’ve solved the problem of unifying your policy rules into a single, shared platform, you now can apply those policies uniformly across all environments. For example, if your development sandbox Kubernetes cluster allows free reign to deploy anything and everything but your production cluster has multiple restrictions in place, developers may not realize they have implemented a violation until later in the SDLC. If all clusters share the same rule set, however, then it is much less likely that such violations will get committed to source control, because they would have been seen during initial unit testing.
Open Policy Agent (OPA)
The Open Policy Agent — usually referred to by its initials OPA and pronounced “oh-pah” — is an open source, CNCF-graduated project that implements a general-purpose policy engine. It is widely used for both application policies — especially in distributed, microservices architectures — and as a Kubernetes API admission controller where it is often integrated via the OPA Gatekeeper project.
Applications can delegate policy validation to OPA, thus eliminating the need to tightly couple such logic into their codebase. In practice, an OPA process is often started alongside the application service and exposes a REST API for the application to communicate with. In a Kubernetes deployment, this is usually done via a sidecar container in the same Pod as the service container thus giving an extremely low latency, “localhost” connection but it could be run as an external service as well. There are also other ways to include OPA in your application, at the time of writing this these include a Go API, WebAssembly, and an SDK for embedding OPA inside of a Go application.
Policies in OPA are written as a collection of rules using a language called Rego.
Rego: OPA’s policy rule language
Rego is a high-level declarative language designed for implementing rules against structured documents like JSON. A detailed tutorial on how to author Rego rules is out of scope for this article, but I highly recommend you check out the official documentation and the excellent learning course by Styra, the creators of OPA and Rego.
Rego basics
Rego works with structured data documents in various ways, but the most common pattern is for the process calling it to pass a document, often in JSON or YAML format, as the input. Then the rules are applied by comparing elements of that input document against expressions.
For example, let’s say we have a Kubernetes Pod validator policy that returns true as long as the input’s image
value does not end with ":latest". A simple Rego implementation for this policy might look something like the following:
1package kubernetes.validating.images
2
3import future.keywords.in
4
5default allow = false
6
7allow {
8 input.kind == "Pod"
9some container in input.spec.containers
10not endswith(container.image, ":latest")
11}
If the following JSON is run through that policy, an { “allow”: false }
response would be returned because ":latest" is not at the end of the image.
1{
2 "kind": "Pod",
3 "apiVersion": "v1",
4 "metadata": {
5 "name": "mypod",
6 "labels": { "run": "mypod" }
7 },
8 "spec": {
9 "containers": [
10 {
11 "name": "mypod",
12 "image": "registry.mycorp.com/team-alpha/myapp:v1",
13 }
14 ]
15 }
16}
Rego example walkthrough
Let’s walk through the Rego code from the above example:
1package kubernetes.validating.images
Here we declare what package the rule belongs to, this is similar to other language packaging and serves as a way to group similar rules into the same namespace.
1import future.keywords.in
The import keyword simply tells the Rego parser to bring the in
rule from the future.keyworks
package into the scope of this policy so it can be referred to as simply, in
.
1default allow = false
"This defines the default value of allow
to be "false" if no other assignment expression in the policy sets it otherwise.
1allow {
2 input.kind == "Pod"
3 some container in input.spec.containers
4 not endswith(container.image, ":latest")
5}
Here we see the bulk of the rule logic. When it is executed, allow
will be assigned the evaluation of the contents inside the { }
braces. All of the expressions inside those braces are executed with an implicit "AND", meaning all of them must evaluate to "true" in order to set allow
to "true".
You can interpret the three lines as, “As long as the input.kind
value is "Pod" and we have at least some containers in the input.spec
, return "true" unless any of the containers image
fields end in ":latest".”
Rego Playground
The OPA project hosts an online Rego editor environment called The Rego Playground where you can experiment with rules and run them against input documents at will. It’s a great way to experiment with your rule ideas interactively and share your experiments with anyone.
The above Rego code and input document are available within this example Rego Playground. Go ahead and open that in another tab, execute it, and play around with it.
Writing custom policies with Snyk IaC and Rego
Snyk Infrastructure as Code (Snyk IaC) leverages OPA to do its policy scanning and you can add your own policies to your scans with a few simple commands. Check out the Snyk IaC documentation as well as our article, Developing custom IaC rules with Snyk, to get started.
Getting started with your free Snyk account allows you to identify and fix IaC misconfigurations by embedding security checks, policy guardrails, and developer-friendly fix guidance seamlessly into your workflows.
Secure infrastructure from the source
Snyk automates IaC security and compliance in workflows and detects drifted and missing resources.
Resources to learn more
Now that you have a high-level understanding of OPA and Rego and some of the ways it is being used. I encourage you to explore the many ways you can leverage it for your software:
Learn Rego with Styra’s excellent tutorial course.
Use The Rego Playground to experiment with and create your rules.
Install OPA Gatekeeper to help secure your Kubernetes clusters.
Customize your Snyk IaC scan policies to shift security left on your SDLC pipelines and catch violations before they get to production.
Thanks for reading this and have fun automating policy enforcement!