Validating Admission Requests in a Validating Admission Webhook

muaazsaleem

Muaaz Saleem

Posted on January 17, 2021

Validating Admission Requests in a Validating Admission Webhook

Last time, I talked about Parsing Admission Requests in a Validating Admission Webhook. This time I wanna talk about actually validating the admission requests i.e allowing or rejecting new or updated resources.

We learned how to parse a Validation Request in an AdmissionReview.Request Object and serialize (encode) a Validation Response from an AdmissionReview.Response Object.

Now that we know how to parse the HTTP requests, a validating admission webhook is just a go func of the signature:

func validate(req *AdmissionRequest) *AdmissionResponse
Enter fullscreen mode Exit fullscreen mode

Another way to phrase validating an Object is "Admitting an Object". In which case the signature above becomes:

func Admit(req *AdmissionRequest) *AdmissionResponse
Enter fullscreen mode Exit fullscreen mode

Parsing the Object

The first thing to do in the Admit func is to parse the Object we're hoping to validate:

err := json.Unmarshal(req.Object.Raw, &admittedObj)
if err != nil {
    // ...
}
Enter fullscreen mode Exit fullscreen mode

In the example above, I am reading the RouteGroup from AdmissionReview.Request.Object.Raw. Note that you could also deserialize the object from AdmissionReview.Request.Object, I hope to cover that in some other post.

Validating the Object

This is where we finally decide whether an Object is good enough to allow or not. The specific logic will depend on what you want to achieve but as an example suppose our admittedObj above was a deployment and we only wanted to allow it if it had an application label.

The validation would look something like:

err = checkAppLabel(&deployment.Spec.Template)
if err != nil {
    // ..
}
Enter fullscreen mode Exit fullscreen mode

Here's checkAppLabel:

import (
    corev1 "k8s.io/api/core/v1"
)

var (
    errNoApplicationLabel = errors.New(
        "missing application label in  pod template",
    )
)

func checkAppLabel(podSpec *corev1.PodTemplateSpec)
    application, ok := podSpec.Labels["application"]
    if !ok {
        return errNoApplicationLabel
    }

    return registry.CheckApplication(application)
}
Enter fullscreen mode Exit fullscreen mode

So cool, right!

Rejecting an Object

Rejecting an Object is just a matter of filling out the AdmissionResponse object with Allowed: false:

if err != nil {
    emsg := fmt.Sprintf("the object was no good, cuz: %v", err)
    log.Error(emsg)
    return &admissionsv1.AdmissionResponse{
        // The UID is needed to identify which request 
        // you are responsing to
        UID:     req.UID,
        Allowed: false,
        Result: &metav1.Status{
        // The Message is helpful to the users to figure out 
        // what's they did wrong!
            Message: emsg,
        },
    }, nil
}
Enter fullscreen mode Exit fullscreen mode

Allowing an Object

At this point, allowing in object is pretty simple:

    return &admissionsv1.AdmissionResponse{
        UID:     req.UID,
        Allowed: true,
    }, nil
Enter fullscreen mode Exit fullscreen mode

That's all! You can find a complete example of an Admit func from the Validating Admission Webhook in Skipper. I've also included an abridged version at the end of this blog post.

Admission Webhooks are the Swiss Army Knife of a Kubernetes Operator. Once you know how to write one, they can be an easy way to programmatically enforce internal policies cluster wide.

Further Resources

If you'd like to learn more on the topic:

I also like to look at the Kubernetes API reference. You can find the ValidatingAdmissionConfiguration spec there.

Reference

An example Admit func from the Validating Admission Webhook in Skipper for RouteGroups:

func Admit(req *admissionsv1.AdmissionRequest) (*admissionsv1.AdmissionResponse, error) {
    rgItem := definitions.RouteGroupItem{}
    err := json.Unmarshal(req.Object.Raw, &rgItem)
    if err != nil {
        emsg := fmt.Sprintf("could not parse RouteGroup, %v", err)
        log.Error(emsg)
        return &admissionsv1.AdmissionResponse{
            UID:     req.UID,
            Allowed: false,
            Result: &metav1.Status{
                Message: emsg,
            },
        }, nil
    }

    err = definitions.ValidateRouteGroup(&rgItem)
    if err != nil {
        emsg := fmt.Sprintf("could not validate RouteGroup, %v", err)
        log.Error(emsg)
        return &admissionsv1.AdmissionResponse{
            UID:     req.UID,
            Allowed: false,
            Result: &metav1.Status{
                Message: emsg,
            },
        }, nil
    }

    return &admissionsv1.AdmissionResponse{
        UID:     req.UID,
        Allowed: true,
    }, nil
}

Enter fullscreen mode Exit fullscreen mode

💖 💪 🙅 🚩
muaazsaleem
Muaaz Saleem

Posted on January 17, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related