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'
@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