Kubernetes mutating webhook patch issue

7/13/2019

I am trying to get my head around Kubernetes Admission Controllers and trying to mutate a pod to include an init container and a volume in which all containers will mount.

The volume gets created and the init container mounts this volume but the original containers I can not get to mount the new volume. I am using Golang to write the webhook and using Json patch to mutate the pod. If anyone could point me in the right direction that would be great!

func serve(w http.ResponseWriter, r *http.Request) {
    body, err := ioutil.ReadAll(r.Body)

    if err != nil {
        glog.Error(err.Error())
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    if len(body) == 0 {
        glog.Error("the request body is empty")
        http.Error(w, "the request body is empty", http.StatusBadRequest)
        return
    }

    if r.Header.Get("Content-Type") != "application/json" {
        glog.Errorf("the content-type is %s, but expect application/json", r.Header.Get("Content-Type"))
        http.Error(w, "invalid content-type, expect `application/json`", http.StatusUnsupportedMediaType)
        return
    }

    request := v1beta1.AdmissionReview{}

    if _, _, err := universalDeserializer.Decode(body, nil, &request); err != nil {
        glog.Errorf("could not deserialize request: %s", err)
        w.WriteHeader(http.StatusBadRequest)
        return
    }

    if request.Request == nil {
        glog.Error("could not find admission review in the request body")
        w.WriteHeader(http.StatusBadRequest)
        return
    }

    response := v1beta1.AdmissionReview{Response: &v1beta1.AdmissionResponse{UID: request.Request.UID}}
    response.Response.Allowed = true

    var pod corev1.Pod
    if err := json.Unmarshal(request.Request.Object.Raw, &pod); err != nil {
        glog.Errorf("Could not unmarshal raw object to a pod object: %v", err)
        w.WriteHeader(http.StatusBadRequest)
        return
    }

    required, err := mutationRequired(&pod.ObjectMeta)

    if err != nil {
        glog.Error(err)
    }

    if required {
        pbytes, _ := createPatch(&pod)
        response.Response.Patch = pbytes
    }

    reviewBytes, err := json.Marshal(response)

    if err != nil {
        glog.Error(err)
        http.Error(w, fmt.Sprintf("problem marshaling json: %v", err), http.StatusInternalServerError)
    }

    if _, err := w.Write(reviewBytes); err != nil {
        glog.Errorf("error writting response back to kubernetes: %v", err)
        http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
    }
}

func mutationRequired(m *metav1.ObjectMeta) (bool, error) {
    for _, n := range config.AllowedNamespaces {
        if m.Namespace != n {
            glog.Infof("request from pod %s is from namespace %s which is allowed", m.Name, m.Namespace)
            a := m.GetAnnotations()

            if len(a) < 1 {
                glog.Infof("pod %s has no annotations so mutation is not required", m.Name)
                return false, nil
            }

            if _, ok := a[enableKey]; ok {
                glog.Infof("annotation: %s found with value %s", enableKey, a[enableKey])
                b, err := strconv.ParseBool(a[enableKey])

                if err != nil {
                    glog.Errorf("was unable to parse the annotation value: %s", err)
                    return false, err
                }
                return b, nil
            }
            glog.Infof("pod %s has does not have the enable annotation so mutation not required", m.Name)
        }
    }
    glog.Infof("request from %s is coming from namespace %s which is not allowed", m.Name, m.Namespace)
    return false, nil
}

func addCredentialVolume() (patch patchOperation) {
    volume := corev1.Volume{
        Name: "vault-creds",
        VolumeSource: corev1.VolumeSource{
            EmptyDir: &corev1.EmptyDirVolumeSource{
                Medium: "Memory",
            },
        },
    }

    value := []corev1.Volume{volume}

    return patchOperation{
        Op:    "add",
        Path:  "/spec/volumes",
        Value: value,
    }
}
// this is the part not working 
func updateVolumeMounts(p *corev1.Pod) (patch patchOperation) {
    vmount := corev1.VolumeMount{
        Name:      "vault-creds",
        MountPath: "/creds",
        ReadOnly:  true,
    }
    for _, c := range p.Spec.Containers {
        c.VolumeMounts = append(c.VolumeMounts, vmount)
    }
    return patchOperation{
        Op:    "replace",
        Path:  "/spec/containers",
        Value: p.Spec.Containers,
    }
}

func addInitContainer(p *corev1.Pod) (patch patchOperation) {

    req := corev1.ResourceList{
        "cpu":    resource.MustParse("10m"),
        "memory": resource.MustParse("20Mi"),
    }

    lim := corev1.ResourceList{
        "cpu":    resource.MustParse("30m"),
        "memory": resource.MustParse("50Mi"),
    }

    vault := corev1.Container{
        Name:            "vault-init",
        Image:           config.Image,
        ImagePullPolicy: "Always",

        Resources: corev1.ResourceRequirements{
            Requests: req,
            Limits:   lim,
        },
        VolumeMounts: []corev1.VolumeMount{
            corev1.VolumeMount{
                Name:      "vault-creds",
                MountPath: "/creds",
            },
        },
        Command: []string{"echo", "'Hello' > /creds/cred.txt"},
    }
    p.Spec.InitContainers = append(p.Spec.InitContainers, vault)

    return patchOperation{
        Op:    "add",
        Path:  "/spec/initContainers",
        Value: p.Spec.InitContainers,
    }
}

func createPatch(p *corev1.Pod) ([]byte, error) {
    var patch []patchOperation
    patch = append(patch, addInitContainer(p))
    patch = append(patch, addCredentialVolume())
    patch = append(patch, updateVolumeMounts(p))
    return json.Marshal(patch)
}
-- S Macklin
go
kubernetes
patch
webhooks

0 Answers