Open Policy Agent with Kubernetes – Tutorial (Pt. 2)


In my previous articles, we have discussed what policy as code is, why we need it, and how to use the Open Policy Agent (OPA) tool. If you haven’t read the introduction yet, please take a moment to read it first:

What is politics as a symbol? Introduction to open proxy policy

Learn about the benefits of policy as code and start testing your policies for cloud native environments.

Opening Policy Agent with Kubernetes - Tutorial (Part 2)

After the introduction to OPA, I started the first part of the OPA / Kubernetes integration tutorial, which shows how to use OPA to enforce policies within a Kubernetes cluster. Here’s the link to the first part of the tutorial:

Open policy proxy with Kubernetes – Tutorial (point 1)

Let’s treat the policy as code and write our first OPA policies for the Kubernetes environment.

Opening Policy Agent with Kubernetes - Tutorial (Part 2)

TL; DR: We used OPA as an acceptance controller with kube-mgmt sidecar Enforce ConfigMap-based policies.
Today, we’ll look at another way to use OPA in a Kubernetes cluster: Gatekeeper – the Kubernetes policy controller.


Introduction to Gatekeeper

OPA Gatekeeper is a project under the OPA umbrella. Although I didn’t mention Gatekeeper in the previous tutorial, the technique I described there (using OPA with its sidebar kube-mgmt) is also referred to as Gatekeeper v1.0.

Today, we will get to know him Gatekeeper v3 (From now on: “Gatekeeper” only, omitting the “v3” part), which builds a Kubernetes entry console around the policy engine to integrate OPA and Kubernetes API service.

CRD-based policies

Gatekeeper’s most important value is the ability to dynamically configure OPA policies using Gatekeeper Dedicated Resource Definitions (CRDs). Dedicated resources are extensions to the Kubernetes API that allow customization of a Kubernetes installation.

CRD-based policies allow deeper integration of OPA within the Kubernetes ecosystem: it allows creating policy models for Rego policies, creating policies as CRDs, and storing audit results on policy CRDs.

How is Gatekeeper different from OPA?

Comparison of using OPA with its side profile kube-mgmt (also known as Gatekeeper v1.0), Gatekeeper offers the following functionality:

  • Parameterized extensible policy library
  • Native Kubernetes CRDs for instantiating the policy library (aka “constraints”)
  • Kubernetes native CRDs for extending the policy library (aka “constraint templates”)
  • Kubernetes native CRDs to support mutations
  • audit function
  • External data support

This might sound a bit abstract, so let’s get down to the nitty-gritty of how Gatekeeper works.


Install Gatekeeper

First, let’s start the minikube:

minikube start

In this tutorial we will be publishing a released version of Gatekeeper to our minikube collection with a pre-created image:

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

Note: It is also possible to post it with Helm:

helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts 
helm install gatekeeper/gatekeeper --name-template=gatekeeper --namespace gatekeeper-system --create-namespace

After publishing, a new namespace gatekeeper-system The following resources will be created:

[email protected] ~ $ kubectl get deployments
NAME                            READY   UP-TO-DATE   AVAILABLE   AGE
gatekeeper-audit                1/1     1            1           2m20s
gatekeeper-controller-manager   3/3     3            3           2m20s

[email protected] ~ $ kubectl get services
NAME                         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
gatekeeper-webhook-service   ClusterIP   10.103.86.204   <none>        443/TCP   2m22s

[email protected] ~ $ kubectl get crd
NAME                                                 CREATED AT
assign.mutations. Gatekeeper.sh                       2023-01-01T08:59:54Z
assignmetadata.mutations.gatekeeper.sh               2023-01-01T08:59:54Z
configs.config.gatekeeper.sh                         2023-01-01T08:59:55Z
constraintpodstatuses.status.gatekeeper.sh           2023-01-01T08:59:55Z
constrainttemplatepodstatuses.status.gatekeeper.sh   2023-01-01T08:59:55Z
constrainttemplates.templates.gatekeeper.sh          2023-01-01T08:59:55Z
expansiontemplate.expansion. Gatekeeper.sh            2023-01-01T08:59:55Z
modifyset.mutations. Gatekeeper.sh                    2023-01-01T08:59:55Z
mutatorpodstatuses.status. Gatekeeper.sh              2023-01-01T08:59:55Z
providers.externaldata. Gatekeeper.sh                 2023-01-01T08:59:55Z

Gatekeeper Concepts: Constraints and Restrictive Templates

Before moving on to the actual tutorial, let’s look at two basic concepts of Gatekeeper with concrete examples: restrictions And the constraint templates.

In short, constraints use constraint templates to tell Gatekeeper what policies to enforce and how.

I know this sounds a bit confusing, so let’s look at an example:

the ConstraintTemplate The example below contains Rego code that checks if the resource object has a label named ‘team’:

a file constraint_template.yaml:

apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: teamlabel
spec:
  crd:
    spec:
      names:
        kind: TeamLabel
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package teamlabel
 
        labels := input.review.object.metadata.labels
 
        has_team {
          labels.team
        }
 
        violation[{"msg": msg}] {
          not has_team
          msg := "You should have the team label"
        }

the ConstraintTemplate The above object does not trigger the policy on its own. However, it creates a new custom resource in our collection of type TeamLabel. If we want to impose our TeamLabel policy, we create a constraint with this new resource type:

a file constraint.yaml:

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: TeamLabel
metadata:
  name: teampods
spec:
  match:
    kinds:
    - apiGroups: [""]
      kinds: ["Pod"]
    excludedNamespaces:
    - kube-system
  parameters: {}

This constraint uses the TeamLabel constraint template above to allow Gatekeeper to enforce our TeamLabel policy for all pods not in kube-system namespace.

Gatekeeper also constantly and continuously monitors and audits existing collection objects for policy violations.


How to use Gatekeeper in Kubernetes

Create files constraint_template.yaml And the constraint.yaml With the content in the previous section, apply:

kubectl apply -f constraint_template.yaml
kubectl apply -f constraint.yaml

Now let’s deploy a simple pod in the default namespace as a test:

kubectl apply -n default -f https://k8s.io/examples/pods/simple-pod.yaml

We will get the following error:

Error from server (Forbidden): error when creating "https://k8s.io/examples/pods/simple-pod.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [teampods] You should have the team label

Let’s comply and add a “team” label to the simple group:

a file simple-pod.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    team: test
spec:
  containers:
  - name: nginx
    image: nginx:1.14.2
    ports:
    - containerPort: 80

Create this file with the above content and apply it:

kubectl apply -n default -f simple-pod.yaml

We should not encounter any error, and the capsule will be created successfully:

[email protected] ~ $ kubectl get pods -n default
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          34s

How to measure OPA?

So far, we have demonstrated a simple use case of integrating OPA/Kubernetes with Gatekeeper.

However, the policies can be more complex in real world scenarios, and you will have multiple policies. How does OPA work on a much larger scale?

Let’s talk about three important topics on this topic:

  • warehouse structure
  • Test policies
  • other tools

warehouse structure

A well-organized guide helps make managing your code easier:

.
└── team-label-policy
    ├── README.md
    ├── constraint.yaml
    ├── constraint_template.yaml
    ├── simple-pod.yaml
    ├── src.rego
    └── src_test.rego

In the example above, we put everything that belongs to the team-label-policy folder under the “team-label-policy” folder, which contains the core files:

  • src.regoCode: OPA Rego
  • src_test.rego: test cases corresponding to our policy Rego
  • constraint_template.yaml: Enrollment is a model of our policy. Note that this file also contains code from src.rego included, but the OPA tool can’t parse the YAML manifest file, so we need to copy Rego’s code into a separate file for testing. If you use this format for your policies, You must remember to synchronize code changes between the two files.
  • constraint.yamlThe statement of the registration form test
  • simple-pod.yaml: Definition of a simplified pod to prove constraint in practice
  • README.md

How to test a Rego policy

For basic use cases and complex policies, we must also write tests for those policies.

When writing test coverage for your Gatekeeper policy, you’ll want to carefully consider the following points:

  • Which Kubernetes API resource fields are querying my policy? Are any of them optional? Can it appear more than once in a specification?
  • How many positive test cases do I need to write to ensure that my policy will do what I expect it to do?
  • How many negative test cases do I need to write to ensure that my policy won’t produce results I don’t want?

Policy tests are also written in Rego.

By convention, they live in the same directory as the source file. In our case, we can copy the policy from constraint_template.yaml file in src.rego And write the tests in the file src_test.rego.

Note the corresponding package name at the top of each file:

a file src.rego:

package teamlabel

# copied from file constraint_template.yaml

labels := input.review.object.metadata.labels

has_team {
  labels.team
}

violation[{"msg": msg}] {
  not has_team
  msg := "You should have the team label"
}

a file src_test.rego:

package teamlabel
import future.keywords

test_pod_allowed if {
  results := violation with input as {"review": {"object": {"metadata": {"labels": { "team": "test" }}}}}
  count(results) == 0
}

test_pod_denied if {
  results := violation with input as {"review": {"object": {"metadata": {"labels": {}}}}}
  count(results) > 0
}

Test method names must always begin with a prefix test_.

We can use the OPA command line tool to evaluate our tests:

[email protected] ~ $ opa test --explain fails src.rego src_test.rego
src_test.rego:
data.teamlabel.test_pod_allowed: PASS (7.852791ms)
data.teamlabel.test_pod_denied: PASS (301.75µs)
--------------------------------------------------------------------------------
PASS: 2/2

If you haven’t installed the OPA CLI tool, follow the instructions here.

Useful tools

There are some exciting tools available (for example, this one) that can help integrate OPA with your systems and provide ways to write and deploy policies across your infrastructure, as well as tools for unit testing, monitoring policy usage, and more. But, even without it, OPA can be managed on a larger scale.


Go from here

In this two-part OPA tutorial mini-series, we made two demos on integrating OPA with Kubernetes. If you are interested in OPA and OPA / Kubernetes integration, here is some additional reading material for you:

If you like OPA’s intro and tutorials, please like, comment, and subscribe. See you in the next!

*** This is a blog aggregator for the Security Blogger Network from the GitGuardian Blog – Auto-Disclosure of Secrets written by a guest expert. Read the original post at: https://blog.gitguardian.com/open-policy-agent-with-kubernetes-tutorial-pt-2/