Validating Admission Requests in a Validating Admission Webhook
Tags: kubernetes, go
Published: 2021-01-17 17:19:14

Last time, I talked about [Parsing Admission Requests in a Validating Admission Webhook](https://www.learningkubeonaws.com/blog/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:

```go
func validate(req *AdmissionRequest) *AdmissionResponse
```

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

```go
func Admit(req *AdmissionRequest) *AdmissionResponse
```

### Parsing the Object

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

```go
err := json.Unmarshal(req.Object.Raw, &admittedObj)
if err != nil {
    // ...
}
```

In the example above, I am reading the [RouteGroup](https://opensource.zalando.com/skipper/kubernetes/routegroups/) 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:

```go
err = checkAppLabel(&deployment.Spec.Template)
if err != nil {
    // ..
}
```

Here's `checkAppLabel`:

```go
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)
}
```

So cool, right!

### Rejecting an Object

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

```go
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
}
```

### Allowing an Object

At this point, allowing in object is pretty simple:

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

That's all! You can find a complete example of an `Admit` func from the Validating Admission Webhook in [Skipper](https://github.com/zalando/skipper/blob/master/cmd/webhook/admission/admission.go#L75). 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:

- Banzi Cloud has a [great guide on admission controllers](evernote:///view/216723888/s393/af633578-8b03-475f-8bad-2bb64efa7bf9/af633578-8b03-475f-8bad-2bb64efa7bf9/).
- There's also the [Dynamic Admission Control][5] guide in the [Kubernetes docs][6].

- You can find the definitions of the `AdmissionReview` object in [k8s.io/api][7] repository.

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

### Reference

An example `Admit` func from the Validating Admission Webhook in [Skipper](https://github.com/zalando/skipper/blob/master/cmd/webhook/admission/admission.go#L75) for [RouteGroups](https://opensource.zalando.com/skipper/kubernetes/routegroups/):

```go
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
}

```

---


  [1]: https://v1-18.docs.kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#validatingwebhookconfiguration-v1-admissionregistration-k8s-io
  [2]: https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/
  [3]: https://creativecommons.org/licenses/by/4.0/
  [4]: https://github.com/kubernetes/api/blob/c59710ee51e70325197298cd6243d32f1aa85df0/admission/v1/types.go#L33
  [5]: https://kubernetes.io/docs/reference/access-authn-authz/extensible-admission-controllers/
  [6]: https://kubernetes.io/docs/home/
  [7]: https://github.com/kubernetes/api/blob/c59710ee51e70325197298cd6243d32f1aa85df0/admission/v1/types.go#L40