By default, Kubernetes will pull from Docker Hub unless the deployment requests a fully qualified path. For example, if you deploy a pod with the image rancherlabs/swiss-army-knife, Kubernetes will default index.docker.io/rancherlabs/swiss-army-knife. This is works for environments that can pull images from the internet. But in environments that don’t have internet access or if your security team requires all images to be scanned before being deployed. To solve this issue, you’ll need to instruct your application teams to update to use a private registry IE private.example.com/rancherlabs/swiss-army-knife., But there is always a chance that an application team forgets to change their image path in their code. Now you have pods stuck in imagepullbackoff. To prevent this kind of issue, we will set up OPA Gatekeeper to block all deployment requests that are missing our private registry IE private.example.com.

Pre-requisites

  • kubectl access to cluster with admin permissions
  • A private docker registry

Installation

  • For Rancher environments, please see Doc for installation steps.
  • For Non-Rancher environments, please see Doc

Creating policy

  • Create a file named constraint_template.yaml with the following content:
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8sallowedrepos
spec:
  crd:
    spec:
      names:
        kind: K8sAllowedRepos
      validation:
        # Schema for the `parameters` field
        openAPIV3Schema:
          properties:
            repos:
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8sallowedrepos

        violation[{"msg": msg}] {
          container := input.review.object.spec.containers[_]
          satisfied := [good | repo = input.parameters.repos[_] ; good = contains(container.image, repo)]
          not any(satisfied)
          msg := sprintf("container <%v> has an invalid image repo <%v>, allowed repos are %v", [container.name, container.image, input.parameters.repos])
        }

        violation[{"msg": msg}] {
          container := input.review.object.spec.initContainers[_]
          satisfied := [good | repo = input.parameters.repos[_] ; good = contains(container.image, repo)]
          not any(satisfied)
          msg := sprintf("container <%v> has an invalid image repo <%v>, allowed repos are %v", [container.name, container.image, input.parameters.repos])
        }
  • Then apply the ConstraintTemplate to the cluster by running:
kubectl apply -f constraint_template.yaml
  • Create a file named constraint.yaml with the following content:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
  name: allow-only-private-registry
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
  parameters:
    repos:
      - "private.example.com"

NOTE: We’ll want to change private.example.com to match the name of your private registry. Also, you can have more than one registry in the list.

  • Then, apply the Constraint to the cluster by running:
kubectl apply -f constraint.yaml

Test the policy

To test that our new policy is working correctly, we’re going to try deploying two files. allowed.yaml should work with disallowed.yaml being blocked.

  • Create a file named allowed.yaml with the following content:
apiVersion: v1
kind: Pod
metadata:
  name: swiss-army-knife-allowed
spec:
  containers:
    - name: swiss-army-knife
      image: private.example.com/rancherlabs/swiss-army-knife:latest
      resources:
        limits:
          cpu: "100m"
          memory: "30Mi"

NOTE: We’ll want to change private.example.com to match the name of your private registry.

  • Create a file named disallowed.yaml with the following content:
apiVersion: v1
kind: Pod
metadata:
  name: swiss-army-knife-disallowed
spec:
  containers:
    - name: swiss-army-knife
      image: rancherlabs/swiss-army-knife:latest
      resources:
        limits:
          cpu: "100m"
          memory: "30Mi"
  • Then, create the allowed pod to the cluster by running:
kubectl apply -f allowed.yaml
  • We should see the following output:
pod/swiss-army-knife-allowed created
  • Then, create the disallowed pod to the cluster by running:
kubectl apply -f disallowed.yaml
  • We should see the following output:
Error from server ([denied by allow-only-private-registry] container <swiss-army-knife> has an invalid image repo <swiss-army-knife>, allowed repos are ["private.example.com"]): error when creating "disallowed.yaml": admission webhook "validation.gatekeeper.sh" denied the request: [denied by allow-only-private-registry] container <swiss-army-knife> has an invalid image repo <swiss-army-knife>, allowed repos are ["private.example.com"]
  • You should also see that the blocked pod was not created, but the allowed pod was.
NAME                      READY   STATUS             RESTARTS   AGE
swiss-army-knife-allowed  1/1     Running            0          5m