Skip to content

Instantly share code, notes, and snippets.

@tsandall
Created February 21, 2018 17:26
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tsandall/f328635433acc5beeb4cb9b36295ee89 to your computer and use it in GitHub Desktop.
Save tsandall/f328635433acc5beeb4cb9b36295ee89 to your computer and use it in GitHub Desktop.

MutatingAdmissionWebhook Example with OPA

This is a quick example of how to use OPA as a Mutating Admission Controller in Kubernetes 1.9.

Steps

  1. Register OPA as a MutatingAdmissionWebhook
  2. Load a policy to test mutation
  3. Exercise the policy

1. Register OPA as a MutatingAdmissionWebhook

The steps in Kubernetes Admission Control show how to deploy OPA as a Validating Admission Controller. To deploy OPA as a Mutating Admission Controller, follow steps 1-3. In step 3, instead of creating a ValidatingWebhookConfiguration, create a MutatingWebhookConfiguration:

kind: MutatingWebhookConfiguration
apiVersion: admissionregistration.k8s.io/v1beta1
metadata:
  name: opa-mutating-webhook
webhooks:
  - name: mutating-webhook.openpolicyagent.org
    rules:
      - operations: ["*"]
        apiGroups: ["*"]
        apiVersions: ["*"]
        resources: ["*"]
    clientConfig:
      service:
        namespace: opa
        name: opa
      caBundle: <CA-BUNDLE>

2. Load a policy to test mutation

Define a policy to exercise mutation.

example.rego:

package system

main = {
    "apiVersion": "admission.k8s.io/v1beta1",
    "kind": "AdmissionReview",
    "response": {
        "allowed": true,
        "patchType": "JSONPatch",
        "patch": patch_bytes,
    }
} {
    # Only apply mutations to objects in create/update operations (not
    # delete/connect operations.)
    is_create_or_update

    # If the resource has the "test-mutation" annotation key, the patch will be
    # generated and applied to the resource.
    input.request.object.metadata.annotations["test-mutation"]

    # Construct JSON Patch for test purposes. kube-apiserver expects changes to
    # be represented as JSON Patch operations against the resource. The JSON
    # Patch must be JSON serialized and base64 encoded.
    patch = [
        {"op": "add", "path": "/metadata/annotations/foo", "value": "bar"},
    ]
    patch_json = json.marshal(patch)
    patch_bytes = base64url.encode(patch_json)
}

is_create_or_update { is_create }
is_create_or_update { is_update }
is_create { input.request.operation == "CREATE" }
is_update { input.request.operation == "UPDATE" }

This policy will mutate resources that define an annotation with the key "test-mutation". The resouces will be updated to include the annotation "foo": "bar".

Load the policy as a ConfigMap:

kubectl create configmap example --from-file example.rego

3. Exercise the policy

First create a Deployment:

kubectl run nginx --image nginx

Check that the Deployment was not mutated:

kubectl get deployment nginx -o json | jq '.metadata'

Annotate the Deployment to indicate that it should be mutated:

kubectl annotate deployment nginx test-mutation=true

Check that the Deployment was mutated:

kubectl get deployment nginx -o json | jq '.metadata'
@edlee2121
Copy link

Thanks for the great example, Torin.
We're currently finding OPA very useful as an admission controller for k8s.

In the above example, what would the 'main' function look like if we use OPA as both a validating and mutating admission controller?
Or would you recommend that we only use it as a mutating admission controller in that case?

Thanks.

@mlbiam
Copy link

mlbiam commented Aug 7, 2018

hey @edlee2121 we just did the same thing. our main looks like:

package system

import data.kubernetes.admission

main = {
         "apiVersion": "admission.k8s.io/v1beta1",
         "kind": "AdmissionReview",
         "response": response,
       }

default response = {"allowed": true}

response = {
    "allowed": false,
    "status": {
        "reason": reason,
    },
} {
    reason = concat(", ", admission.deny)
    reason != ""
}

response = {
  "allowed":true,
  "patchType": "JSONPatch",
  "patch": patch_bytes,
} {
  patch = {xw|xw:=admission.patches[_][_]}
  patch_json = json.marshal(patch)
  patch_bytes = base64.encode(patch_json)
}

then in the kubernetes.admission package we have our blocks:

deny[msg] {
    pv_name = input.request.object.spec.volumeName
    not pv_exists
    msg = sprintf("invalid persistent volume %q", [pv_name])
}

and for mutating:

patches["krb5_sidecar"] = krb5_sidecar {
  is_pod_kerb
  krb5_sidecar = kerb_sidecar_patch
}

@aalmekhlafi0
Copy link

aalmekhlafi0 commented Sep 20, 2018

Thanks for the great example.
I've followed the steps but unfortunately the nginx metadata is not getting annotated with "foo:bar". Is there any missing step not mentioned in here. However, when i applied the validation webhook in this example Kubernetes Admission Control the validation works just fine.

@aalmekhlafi0
Copy link

Thanks for the great example.
I've followed the steps but unfortunately the nginx metadata is not getting annotated with "foo:bar". Is there any missing step not mentioned in here. However, when i applied the validation webhook in this example Kubernetes Admission Control the validation works just fine.

I got this working by removing the main section on the admission-controller.yaml file. this might be something very basic but i think it worth the mention if a beginner like me faced the same issue i had.

@rogeduardo
Copy link

Thanks for the great example.
I've followed the steps but unfortunately the nginx metadata is not getting annotated with "foo:bar". Is there any missing step not mentioned in here. However, when i applied the validation webhook in this example Kubernetes Admission Control the validation works just fine.

I got this working by removing the main section on the admission-controller.yaml file. this might be something very basic but i think it worth the mention if a beginner like me faced the same issue i had.

Excuse me, aalmekhlafi0, but I dont understand what remove from where. Could you put the files here?

@harshal-shah
Copy link

@rogeduardo the admission-controller.yaml is here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment