Admission webhook in k8s

preface

Kubernetes provides three security access control measures for API access: authentication, authorization and permission control. Authentication solves the problem of who the user is, authorization solves the problem of what the user can do, and permission control plays a role in resource management. Through reasonable authority management, the security and reliability of the system can be guaranteed.

This article mainly talks about ValidatingAdmissionWebhook and MutatingAdmissionWebhook in advertising.

AdmissionWebhook

We know that k8s has scalability in all aspects, such as implementing a variety of network models through cni, implementing a variety of storage engines through csi, implementing a variety of container runtime through cri, and so on. The Admission Web hook is another extensible means. In addition to the compiled permission plug-in, you can develop your own permission plug-in as an extension and configure it as webhook at runtime.

Permission webhooks are HTTP callbacks that receive permission requests and do something about them. You can define two types of Admission webhook, ValidatingAdmissionWebhook and MutatingAdmissionWebhook.

If MutatingAdmission is enabled, when creating a k8s resource object, the creation request will be sent to the controller you write, and then we can do a series of operations. For example, in our scenario, we will uniformly make some functional enhancements. When business development creates a new deployment, we will perform some injection operations, such as sensitive information aksk or some optimized init scripts.

Similar to this, the only thing is that ValidatingAdmissionWebhook is whether to allow the creation of resources according to your custom logic. For example, in the actual production k8s cluster, we need to set request and limit for the deployment created in consideration of stability.

How to implement your own AdmissionWebhook Server

prerequisite

Write an admission webhook server

There is an official offer demo . You can study it in detail. The core idea is:
webhook processes the AdmissionReview request sent by apiservers and sends its decision back as an AdmissionReview object.

package main

import (
    "encoding/json"
    "flag"
    "fmt"
    "io/ioutil"
    "net/http"

    "k8s.io/api/admission/v1beta1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/klog"
    // TODO: try this library to see if it generates correct json patch
    // https://github.com/mattbaird/jsonpatch
)

// toAdmissionResponse is a helper function to create an AdmissionResponse
// with an embedded error
func toAdmissionResponse(err error) *v1beta1.AdmissionResponse {
    return &v1beta1.AdmissionResponse{
        Result: &metav1.Status{
            Message: err.Error(),
        },
    }
}

// admitFunc is the type we use for all of our validators and mutators
type admitFunc func(v1beta1.AdmissionReview) *v1beta1.AdmissionResponse

// serve handles the http portion of a request prior to handing to an admit
// function
func serve(w http.ResponseWriter, r *http.Request, admit admitFunc) {
    var body []byte
    if r.Body != nil {
        if data, err := ioutil.ReadAll(r.Body); err == nil {
            body = data
        }
    }

    // verify the content type is accurate
    contentType := r.Header.Get("Content-Type")
    if contentType != "application/json" {
        klog.Errorf("contentType=%s, expect application/json", contentType)
        return
    }

    klog.V(2).Info(fmt.Sprintf("handling request: %s", body))

    // The AdmissionReview that was sent to the webhook
    requestedAdmissionReview := v1beta1.AdmissionReview{}

    // The AdmissionReview that will be returned
    responseAdmissionReview := v1beta1.AdmissionReview{}

    deserializer := codecs.UniversalDeserializer()
    if _, _, err := deserializer.Decode(body, nil, &requestedAdmissionReview); err != nil {
        klog.Error(err)
        responseAdmissionReview.Response = toAdmissionResponse(err)
    } else {
        // pass to admitFunc
        responseAdmissionReview.Response = admit(requestedAdmissionReview)
    }

    // Return the same UID
    responseAdmissionReview.Response.UID = requestedAdmissionReview.Request.UID

    klog.V(2).Info(fmt.Sprintf("sending response: %v", responseAdmissionReview.Response))

    respBytes, err := json.Marshal(responseAdmissionReview)
    if err != nil {
        klog.Error(err)
    }
    if _, err := w.Write(respBytes); err != nil {
        klog.Error(err)
    }
}

func serveAlwaysDeny(w http.ResponseWriter, r *http.Request) {
    serve(w, r, alwaysDeny)
}

func serveAddLabel(w http.ResponseWriter, r *http.Request) {
    serve(w, r, addLabel)
}

func servePods(w http.ResponseWriter, r *http.Request) {
    serve(w, r, admitPods)
}

func serveAttachingPods(w http.ResponseWriter, r *http.Request) {
    serve(w, r, denySpecificAttachment)
}

func serveMutatePods(w http.ResponseWriter, r *http.Request) {
    serve(w, r, mutatePods)
}

func serveConfigmaps(w http.ResponseWriter, r *http.Request) {
    serve(w, r, admitConfigMaps)
}

func serveMutateConfigmaps(w http.ResponseWriter, r *http.Request) {
    serve(w, r, mutateConfigmaps)
}

func serveCustomResource(w http.ResponseWriter, r *http.Request) {
    serve(w, r, admitCustomResource)
}

func serveMutateCustomResource(w http.ResponseWriter, r *http.Request) {
    serve(w, r, mutateCustomResource)
}

func serveCRD(w http.ResponseWriter, r *http.Request) {
    serve(w, r, admitCRD)
}

func main() {
    var config Config
    config.addFlags()
    flag.Parse()

    http.HandleFunc("/always-deny", serveAlwaysDeny)
    http.HandleFunc("/add-label", serveAddLabel)
    http.HandleFunc("/pods", servePods)
    http.HandleFunc("/pods/attach", serveAttachingPods)
    http.HandleFunc("/mutating-pods", serveMutatePods)
    http.HandleFunc("/configmaps", serveConfigmaps)
    http.HandleFunc("/mutating-configmaps", serveMutateConfigmaps)
    http.HandleFunc("/custom-resource", serveCustomResource)
    http.HandleFunc("/mutating-custom-resource", serveMutateCustomResource)
    http.HandleFunc("/crd", serveCRD)
    server := &http.Server{
        Addr:      ":443",
        TLSConfig: configTLS(config),
    }
    server.ListenAndServeTLS("", "")
}

 

Dynamically configure admission webhooks

You can ValidatingWebhookConfiguration or MutatingWebhookConfiguration Dynamically configure which resources are limited by the portal webhooks.

Specific examples are as follows:

apiVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
  name: <name of this configuration object>
webhooks:
- name: <webhook name, e.g., pod-policy.example.io>
  rules:
  - apiGroups:
    - ""
    apiVersions:
    - v1
    operations:
    - CREATE
    resources:
    - pods
    scope: "Namespaced"
  clientConfig:
    service:
      namespace: <namespace of the front-end service>
      name: <name of the front-end service>
    caBundle: <pem encoded ca cert that signs the server cert used by the webhook>
  admissionReviewVersions:
  - v1beta1
  timeoutSeconds: 1

summary

Finally, let's summarize the advantages of webhook Admission:

  • webhook can dynamically expand the ability of advertisement to meet the needs of customized customers
  • You do not need to restart the API Server. You can hot load webhook admission by creating a webhook configuration

Keywords: Kubernetes cloud computing

Added by mligor on Wed, 06 Oct 2021 17:49:18 +0300