Writing Your First Kubernetes OPA Policy With Gatekeeper
Tags: kubernetes, devops, cloud, git
Published: 2022-09-14 11:21:27

In a previous blog post which you can find [here](https://dev.to/thenjdevopsguy/open-policy-agent-opa-for-kubernetes-5895), OPA was explained in the sense of why it’s important and how it changes the game for policy management in today’s world.

Now that you know the theory from that blog post, let’s talk about how to get hands-on.

In this blog post, you’ll learn about installing Gatekeeper and how to configure policies for your Kubernetes cluster.

## Gatekeeper Install

First, you’ll need to install Gatekeeper on your Kubernetes cluster. To do that, you can use Helm.

Add the Gatekeeper repo.

```jsx
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
```

Next, run the installation.

```jsx
helm install gatekeeper/gatekeeper --name-template=gatekeeper --namespace gatekeeper-system --create-namespace
```

You should see an output similar to the one below.

```jsx
NAME: gatekeeper
LAST DEPLOYED: Thu Sep  8 12:36:20 2022
NAMESPACE: gatekeeper-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
```

## Config Implementation

Once Gatekeeper is installed, you’ll have to configure it. There are three types of configurations:

- The config for Gatekeeper itself
- The template
- The constraint implementation

### Config

The config is your definition of what Gatekeeper is allowed to create policies for. For example, in the `config.yaml` below, Gatekeeper knows that it can only specify Policies for Pods, but no other Kubernetes resources.

```jsx
apiVersion: config.gatekeeper.sh/v1alpha1
kind: Config
metadata:
  name: config
  namespace: "gatekeeper-system"
spec:
  sync:
    syncOnly:
      - group: ""
        version: "v1"
        kind: "Pod"
```

The config also ensures that the policy is set across clusters, not just the node where Gatekeeper was installed.

For example, if you have Pods running on multiple worker nodes, you'd need a Config so Gatekeeper can have access to the Pods across worker nodes.

### Constraint Template

The Constraint Template is the rule/policy that you want to configure for your environment. It's a template, so you can use it across multiple constraints.

For example, there's a Rego policy in the constraint template in this directory that ensures no one can utilize the `latest` tag of a container image.

Copy the below constraint template and add it to a YAML file. Once complete, run `kubectl apply -f` on the YAML file.

```jsx
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: blocklatesttag
  annotations:
    description: Blocks container images from using the latest tag
spec:
  crd:
    spec:
      names:
        kind: blocklatesttag # this must be the same name as the name on metadata.name (line 4)
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package blocklatesttag
        violation[{"msg": msg, "details": {}}]{
        input.review.object.kind == "Pod"
        imagename := input.review.object.spec.containers[_].image
        endswith(imagename,"latest")
        msg := "Images with tag the tag \"latest\" is not allowed"
        }
```

### Constraint Config

The constraint itself is taking the template that you created above and bringing it to life. It allows you to utilize the template to create your own policy inside of a Kubernetes cluster.

Copy the below constraint template and add it to a YAML file. Once complete, run `kubectl apply -f` on the YAML file.

```jsx
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: blocklatesttag
metadata:
  name: nolatestcontainerimage
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
  parameters:
    annotation: "no-latest-tag-used"
```

## Testing The Config

To confirm that the OPA policy that you implemented works, you can test it out with the two Kubernetes Manifests below.

The Manifest with the `latest` tag won’t work because you created a policy in the previous step to ensure that `latest` tags cannot be used. The deployment itself will deploy, but the Pods won’t come online. 

```jsx
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginxdeployment
  replicas: 2
  template:
    metadata:
      labels:
        app: nginxdeployment
    spec:
      containers:
      - name: nginxdeployment
        image: nginx:latest
        ports:
        - containerPort: 80
```

The Manifest below will work and the Pods will come online because a container image version number is specified.

```jsx
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginxdeployment
  replicas: 2
  template:
    metadata:
      labels:
        app: nginxdeployment
    spec:
      containers:
      - name: nginxdeployment
        image: nginx:1.23.1
        ports:
        - containerPort: 80
```

Congrats! You have successfully set up a full OPA integration.