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
Another way to phrase validating an Object is "Admitting an Object". In which case the signature above becomes:
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:
err := json.Unmarshal(req.Object.Raw, &admittedObj)
if err != nil {
// ...
}
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 {
// ..
}
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)
}
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
}
Allowing an Object
At this point, allowing in object is pretty simple:
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. 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.
There's also the Dynamic Admission Control guide in the Kubernetes docs.
You can find the definitions of the
AdmissionReview
object in k8s.io/api repository.
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
}
Top comments (0)