Comprehensive Guide on Integrating Open Policy Agent (OPA) With Kubernetes

Raghunathan Bakkianathan
DevOps Learners
Published in
13 min readFeb 16, 2022

This article was originally published in Techblost

Open policy agent (OPA, pronounced “oh-pa”) is a tool that provides a unified framework and language for declaring, implementing, and controlling the policies of each component in the cloud-native solution. It also supports policy as code of various platforms including Kubernetes.

OPA provides declarative Rego language to describe policies, and offloads policy decisions to OPA, thereby reducing the decision-making process, which is decoupled from other responsibilities of an application.

In this article, I have listed steps on Integrating Open Policy Agent (OPA) With Kubernetes.

Before getting into the integration part, we will go through some of the key benefits of using OPA.

What is Policy?

A policy is a series of rules governing the behavior of software applications and services. Example: Whether it is application layer or Kubernetes or host level infrastructure or cloud infrastructure or end-user data handled or where workloads are deployed you have to deal with policy all the time in day-to-day work.

Usually, organizations have different strategies in forming policies that are based on legal requirements, authorization rules, technical constraints, architecture characteristics, Network policies, and repeated mistakes.

Most of all policies will be contributed by different stakeholders like Product Owners, Architects, Developers, Security engineers, etc in an organization. A typical policy might be looking something like below

  1. Rules that define which users (identity) can perform which type of operations on which resource
  2. Rules that define only specific subnets are allowed for external traffic
  3. Which clusters are allowed to deploy workloads?
  4. What OS functions the container can perform.
  5. When will the system be accessed at what time of day?
  6. Enforcing authorization in a microservice API

Difference between configuration vs policy

For beginners, there will be a slight confusion between configuration and policy. It is as simple as that, Configuration is a set of settings to parameterize the software, whereas policy is a set of rules and regulations that are enforced when we deploy, release or use software, etc.

In short, a configuration is the set of settings, and a policy is a thing that enforces those settings?

NoteXACML (OASIS standards) / ALFA: Extensible Access Control Markup Language is an open standard XML-based protocol designed to express control policies for enterprise security applications widely adopted since 2001. In the case of OPA, it has Redefined the Rule Engine.

3 Reasons Why do you need an Open Policy Agent (OPA)?

Policy enforcement is the fundamental problem of an organization. If we don’t have a good solution for policy enforcement in the organization, then we’re gonna risk massive fines in terms of security breaches, outages, downtime, and a lot of chaos. Let’s see some scenarios on why to use an open policy agent.

No Policy

Years back, a newbie did a small POC in the AWS cloud in XYZ company. He got access to a corporate AWS account and created an EC2 instance with Public IP and deployed ECS from there for his work. Since there is no policy available to restrict the instance creation with public IP, it invited hackers to access the VM, and eventually, they started mining cryptocurrencies.

To conclude, Public IP should not be allowed for any instance in the corporate network. This would be a good example of why we need the policy to be considered for security breaches.

Setting up Custom Policy engine from Ground Zero

Say you need to implement the same strategy from ground zero. The required components, such as strategy language (Syntax and Semantics) and evaluation engine, need to be brainstormed, designed, implemented, tested, documented, and then maintained to ensure a positive user experience and expected behavior from the new strategy. This is a lot of work !!!

Policy hardcoded in application services

Say, you implement the policy which is tightly coupled with your microservices, it makes it very difficult for the policy to load new rules dynamically and flexibly change based on business requirements. Moreover, different services would have different descriptions of the policy, such as JSON, YAML, etc, so it will be very expensive and painful to manage the policy in the form of code (Policy as Code).

BottomLine: Above all the scenarios will have a lot of challenges. In order to have a more flexible and consistent architecture, we must decouple the policy decision-making process from the complex business logic. Open policy agent does a very good job in terms of decoupling the decision-making to a separate process. This is the core concept of the OPA architecture explained below.

What is an Open Policy Agent (OPA)?

OPA is a Lightweight general-purpose policy engine that abstracts strategy decisions from your application into a common model and decouples the decision-making step from the complex business logic.

In layman terms, You can implement OPA in any layer of the stack in the system and it will help you to offload the policy decision from your service. So you are decoupling the policy decision from the enforcement piece. It further allows declaratively specified policies to change based on business needs, update without recompilation or redeployment, and more importantly reduce the risk of human error.

As shown in the below diagram, OPA can be abstracted into the following 4 steps.

  1. Use Rego language to define the Policy and purpose.
  2. When your service needs a decision (should this request be “allowed” or “denied”) from OPA, this action (Query with the request method, the path, the user) is handed over to OPA to process to execute the Policy.
  3. OPA validates the attributes against the data (already provided).
  4. OPA executes the Rego code (in step 1) under specific input data (Data) and returns the result of Policy execution to the original service. The answer would be either “Allow” or “Deny”

OPA aims to help architects, developers, security practitioners to express policy as code and it can coexist with services. Opa can be integrated into third-party technologies such as Kubernetes, Terraform, Envoy, Docker, etc. The integration method can be sidecar, Standalone service, or library introduction. If you want to integrate OPA, you have to write the policies through a high-level declarative language (Rego) provided by OPA. Refer to the official document for OPA philosophy docs for details

What are the benefits of Policy As Code?

Managing policy as code has a lot of benefits, you can put them in source control and keep track of all changes happening to any given policy, like rollback, track changes in your policy code across versions, etc, can be managed very well. Some of the other benefits include

  1. Global unified policy description: The description of the policy is expressed in a unified language instead of embedding in the business logic code, nor described in a variety of different forms.
  2. Improve operation and maintenance efficiency: Different policy codes can be easily reviewed and integrated according to the requirements. A typical example would be, we can enforce company-level policy code before developing its own business-related policies.
  3. Improve the flexibility of business architecture: Multiple components can share the same policy to execute related logic and also we can dynamically update and load new policies without a change in the business logic code.

Integrating Open Policy Agent (OPA)

Hope you got an idea of what an open policy agent is. Let us take a look at the application scenarios of OPA. So In this article, I have focused on a small use case on how to integrate OPA Gatekeeper (hereafter Gatekeeper) as an admission controller with Kubernetes. Please note OPA is not tied to Kubernetes alone, neither is Kubernetes mandatory for using OPA. There are a lot of use cases available around to get OPA Integrated into anywhere in the stack to solve a variety of different policy-related issues.

Below are the two packages which enable Kubernetes to support OPA control.

Before jumping into Gatekeeper, we have to get an idea of what is an admission controller and the benefits of deploying OPA as an admission controller.

What is An Admission Controller?

An admission controller is a piece of code used to intercept the request in the Kube-API server for authenticating and authorizing the request before the object is persisted and when the resources are created, updated, and deleted, all decisions are made through admission controllers. Among the series of admission controllers, there are two special ones, which are the focus of our attention today. More information can be found in the official documentation.

  1. MutatingAdmissionWebhook: Mainly used to parse and modify the request object before the request before forwarding it down the chain. Example: AlwaysPullImages admission controller
  2. ValidatingAdmissionWebhook: Mainly used for data format verification against specific data. Example: Namespace Admission controller

The benefits of deploying OPA as an admission controller in Kubernetes can meet the following requirements.

Security perspective

  1. We can prohibit the container from running as root or ensure that the container’s root file system is always mounted as read-only.
  2. Only pull images from a given private Image Registry.
  3. Reject deployments that do not meet security standards. For example, a container with a privileged flag can avoid many security checks. Can be verified through the OPA strategy.

Resource control Perspective

  1. OPA can define policies to allow cluster users to enforce certain conventions, such as having meaningful labels, comments, resource limits, or other settings.
  2. All Pods are required to add resource limits and minimum replicas when spun up.
  3. Prevent the creation of conflicting Ingress objects.

Access control Perspective

What is an Open Policy Agent (OPA) Gatekeeper

The Gatekeeper is a relatively new project which helps to integrate between OPA and Kubernetes, Gatekeeper can be started as a Pod in Kubernetes and will be registered as Dynamic Admission Controller with API Server after startup. Essentially, Gatekeeper is used as a Webhook Server. When a user uses kubectl or other methods to send a CURD request for a resource to the API Server, the request will be sent to the admission controller after Authentication and Authorization, and finally, AdmissionReview sent to the Gatekeeper in the form of a request. Gatekeeper makes a decision on this request according to the policy of the corresponding service ( configured in the form of CRD ), and AdmissionReview returns the response to the API Server.

Its main architecture is shown in the following figure:

In the current design, Gatekeeper has the following CRD’s named ConstraintTemplate and Constraint which (is our primary focus today) can be considered similar to the relationship between classes and instances.

  1. ConstraintTemplate: The rego field of ConstraintTemplate specifically describes the Policy in Rego language, but does not specify the specific parameters in the Policy.
  2. Constraint: Constraint can be considered as an instantiation of a ConstraintTemplate, in which the unspecified parameters of the ConstraintTemplate are specifically configured. Therefore, multiple Constraints can be generated for the same ConstraintTemplate with different parameter configurations.

Gatekeeper vs OPA? Compared to OPA, Gatekeeper offers more features like 1. Scalable and parameterized strategy definition method. 2. Through CRD defined constraints (constraints), you can easily create a common strategy. 3. A constraint template is defined through CRD, which adds some flexibility. 4. Provides an audit function.

Prerequisites ( Prerequisites )

  1. Kubernetes version 1.14 or later
  2. The Kubernetes cluster should be up and running.

You Might also Like: Kubernetes Learning Path For Beginners

Integrating Open Policy Agent (OPA) with Kubernetes

Installation Steps

Step 1: Install OPA Gatekeeper CRD.

Step 2: Create a ConstraintTemplate manifest.

Step 3: Create a constraint manifest.

Step 4: Excercise the policy.

Step 5: Check the violation with the describe command.

Install OPA Gatekeeper CRD

kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/master/deploy/gatekeeper.yaml

Make sure Gatekeeper CRD is installed.

kubectl get crd kubectl get pod,svc -n gatekeeper-system

Example of Deny all pod creation

This example enforces policy for all pods being rejected from being created.

Gatekeeper consists of ConstraintTemplate and Constraint CRD. Before defining a Constraint you have to define a Constraint Template. A ConstraintTemplate defines the policy that is used to enforce the constraint and also, the general schema of the constraint. So in Gatekeeper ConstraintTemplate, we defined the schema for parameters. And defined the actual parameter value in Constraint. This allows one to use the same template and define multiple constraints with different parameter values.

The syntax expressed using Rego, I know it is still a bit annoying, but fortunately, the official source code provides some sample rules and basic code libraries for reference. In addition, you can also use Rego Playground for online debugging to write a slightly more complicated strategy.

Step 1: Create ConstraintTemplate manifest

vim template.yamlapiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8salwaysdeny
spec:
crd:
spec:
names:
kind: K8sAlwaysDeny
validation:
# Schema for the 'parameters' field
openAPIV3Schema:
properties:
message:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8salwaysdeny
violation[{"msg": msg}] {
1 > 0
msg := input.parameters.message
}
  1. violation[{“msg”: msg}] { } — It defines a message and other details that would be returned to the user if the policy is violated. The rule is covered with the curly braces {}.
  2. count(missing) > 0 or 1 > 0 — if the total count of items are more than Zero, then it means it is a violation of the policy
  3. msg := input.parameters.message — If it is a violation, the custom message variable will be returned to the client with a respective message.

Step 2: Create a template

kubectl create -f template.yaml

Note: spec.crd.spec.names.kind - Defines the CRD of the constraint created in If there are multiple conditions in rego's violation, it will be a violation if all the conditions are true.

Step 3: Check the template

kubectl get constrainttemplates NAME AGE k8salwaysdeny 54s

Step 4: Create a constraint manifest

Now, using this ConstraintTemplate, create a Constraint as follows

vim constraints.yaml 
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAlwaysDeny
metadata:
name: pod-always-deny
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
parameters:
message: "You are restricted to Create Pods"

We can define one or more constraints for the constraint template. In the above configuration, the “Kind” must match the value defined in the constraint template. Also in the “Kinds” part, we should define which k8 resource this policy will apply to. In our case, we are targeting that any apiGroup that has the Pod kind is a valid match.

Step 5: Create a constraint

kubectl create -f constraints.yaml

Step 6: Check the constraint

kubectl get k8sAlwaysDeny

Step 7: Exercise the policy

If you try to create a pod, you will get an error similar to below.

kubectl run pod --image=nginxError from server ([pod-always-deny] You are restricted to Create Pods): admission webhook "validation.gatekeeper.sh" denied the request: [pod-always-deny] You are restricted to Create Pods

Step 6: Check total Violations count and messages

Check the Status part of the describe command, you can check the current violation status.

kubectl describe k8sAlwaysDeny pod-always-deny

Step 7: Modify the policy and exercise the changes.

Change the contrainttemplate by adding below false conditions. Repeat the steps from 1 to 6 and you should get Total Violations as 0.

violation[{"msg": msg}] {

1 > 2 # false
msg := input.parameters.message
}

Step 8: Delete the resource.

kubectl delete -f template.yaml
kubectl delete -f constraint.yaml

Example of enforcing a label in Namespace

This example enforces a label in Namespaces.

Step 1: Create ConstraintTemplate manifest.

vim template_label.yaml 

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
# Schema for the "parameters" field
openAPIV3Schema:
properties:
labels:
type: array
items: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels

violation[{"msg": msg, "details": {"missing_labels": missing}}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("you must provide labels: %v", [missing])
}Step 2: Create a template.kubectl create -f template_label.yaml constrainttemplate.templates.gatekeeper.sh/k8srequiredlabels created

Step 3: Create a constraint manifest

vim all_namespace_must_have_ns_constraints.yamlapiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: ns-must-have-ns
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
parameters:
labels: ["ns"]

Step 4: Create a constraint

kubectl create -f all_namespace_must_have_ns_constraints.yaml kubectl get K8sRequiredLabels

Step 5: Exercise the policy

If you try to create a Namespace without the label “ns”, you will get an error similar to below.

kubectl create namespace test Error from server ([ns-must-have-ns] you must provide labels: {"ns"}): admission webhook "validation.gatekeeper.sh" denied the request: [ns-must-have-ns] you must provide labels: {"ns"}

Step 6: Check the violation with the describe command.

kubectl describe K8sRequiredLabels ns-must-have-ns

Step 7: Modify the policy and exercise the changes.

Create a new YAML file called with the required label value “ns” similar to below.

vim namespace-dev.yaml{
"apiVersion": "v1",
"kind": "Namespace",
"metadata": {
"name": "development",
"labels": {
"ns": "development"
}
}
}

Step 8: Namespace is created successfully (policy is honored).

kubectl create -f namespace-dev.yaml
namespace/development created

This example of enforcing a minimum number of replicas (2) in Deployment

Step 1: Create ConstraintTemplate manifest.

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8sminreplicacount
spec:
crd:
spec:
names:
kind: K8sMinReplicaCount
validation:
# Schema for the `parameters` field
openAPIV3Schema:
properties:
min:
type: integer
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sminreplicacount

violation[{"msg": msg, "details": {"missing_replicas": missing}}] {
provided := input.review.object.spec.replicas
required := input.parameters.min
missing := required - provided
missing > 0
msg := sprintf("you must provide %v more replicas", [missing])
}

Step 2: Create a template.

kubectl create -f template_replicas.yaml 
constrainttemplate.templates.gatekeeper.sh/k8sminreplicacount created

Step 3: Create a constraint manifest

vim all_deployment_must_have_min_replicacount.yamlapiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sMinReplicaCount
metadata:
name: deployment-must-have-min-replicas
spec:
match:
kinds:
- apiGroups: ["apps"]
kinds: ["Deployment"]
parameters:
min: 2

Step 4: Create a constraint

kubectl create -f all_deployment_must_have_min_replicacount.yaml 
k8sminreplicacount.constraints.gatekeeper.sh/deployment-must-have-min-replicas created

Step 5: Exercise the policy

If you try creating a deployment with replicas less than 2, you will get an error similar to below.

kubectl create deploy test --image=nginx
error: failed to create deployment: admission webhook "validation.gatekeeper.sh" denied the request: [deployment-must-have-min-replicas] you must provide 1 more replicas

Step 6: Modify the policy and excercise the changes.

Create a new deployment YAML file with replicas value as 2 similar to below.

vim deployment.yamlapiVersion: apps/v1
kind: Deployment
metadata:
name: test-nginx-deployment
labels:
app: testnginx
spec:
replicas: 2
selector:
matchLabels:
app: testnginx
template:
metadata:
labels:
app: testnginx
spec:
containers:
- name: testnginx
image: nginx:latest
ports:
- containerPort: 80

Step 8: Deployment is created successfully (policy is honored).

kubectl create -f deployment.yaml

Open Policy Agent (OPA) Takeaways

Gatekeeper is more extensible than Open Policy Agent and can manage policies by a template. Implementing OPA in your application stack can bring a certain learning cost in the early stage, but the value of the later period is to allow the Policy to have similar characteristics of code like Reusability, testable, versioning, abstraction, etc.

  1. Decouple the policy decision-making process from the complex business logic
  2. Unify & centralize the policy definition and facilitate the enforcement of those policies the entire stack.
  3. Manage policy as code.

References

  1. OPA Documentation
  2. Gatekeeper Documentation
  3. OPA Gatekeeper: Policy and Governance for Kubernetes
  4. OPA Gatekeeper Introduction
  5. A Guide to Kubernetes Admission Controllers for a quick primer on admission controllers.
  6. Dynamic Admission Control for details on configuring external admission controllers.
  7. Gatekeeper samples Library for Rules.
  8. OPA Samples library for Rules
  9. OPA Constraint Framework
  10. Grammer: Policy Language , Policy Reference
  11. How to deploy OPA as an admission controller from scratch

--

--

Raghunathan Bakkianathan
DevOps Learners

Passionate about tech, programming & innovation. Excited to solve business problems & provide end-to-end solutions from ideation to realization.