package webhook import ( "crypto/tls" "encoding/base64" "encoding/json" "fmt" "io/ioutil" "net/http" "os" "github.com/spf13/cobra" v1 "k8s.io/api/admission/v1" "k8s.io/api/admission/v1beta1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/klog/v2" ) // admitv1beta1Func handles a v1beta1 admission type admitv1beta1Func func(v1beta1.AdmissionReview) *v1beta1.AdmissionResponse // admitv1beta1Func handles a v1 admission type admitv1Func func(v1.AdmissionReview) *v1.AdmissionResponse // admitHandler is a handler, for both validators and mutators, that supports multiple admission review versions type admitHandler struct { v1beta1 admitv1beta1Func v1 admitv1Func } func newDelegateToV1AdmitHandler(f admitv1Func) admitHandler { return admitHandler{ v1beta1: delegateV1beta1AdmitToV1(f), v1: f, } } func delegateV1beta1AdmitToV1(f admitv1Func) admitv1beta1Func { return func(review v1beta1.AdmissionReview) *v1beta1.AdmissionResponse { in := v1.AdmissionReview{Request: convertAdmissionRequestToV1(review.Request)} out := f(in) return convertAdmissionResponseToV1beta1(out) } } // serve handles the http portion of a request prior to handing to an admit // function func serve(w http.ResponseWriter, r *http.Request, admit admitHandler) { 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)) deserializer := codecs.UniversalDeserializer() obj, gvk, err := deserializer.Decode(body, nil, nil) if err != nil { msg := fmt.Sprintf("Request could not be decoded: %v", err) klog.Error(msg) http.Error(w, msg, http.StatusBadRequest) return } var responseObj runtime.Object switch *gvk { case v1beta1.SchemeGroupVersion.WithKind("AdmissionReview"): requestedAdmissionReview, ok := obj.(*v1beta1.AdmissionReview) if !ok { klog.Errorf("Expected v1beta1.AdmissionReview but got: %T", obj) return } responseAdmissionReview := &v1beta1.AdmissionReview{} responseAdmissionReview.SetGroupVersionKind(*gvk) responseAdmissionReview.Response = admit.v1beta1(*requestedAdmissionReview) responseAdmissionReview.Response.UID = requestedAdmissionReview.Request.UID responseObj = responseAdmissionReview case v1.SchemeGroupVersion.WithKind("AdmissionReview"): requestedAdmissionReview, ok := obj.(*v1.AdmissionReview) if !ok { klog.Errorf("Expected v1.AdmissionReview but got: %T", obj) return } responseAdmissionReview := &v1.AdmissionReview{} responseAdmissionReview.SetGroupVersionKind(*gvk) responseAdmissionReview.Response = admit.v1(*requestedAdmissionReview) responseAdmissionReview.Response.UID = requestedAdmissionReview.Request.UID responseObj = responseAdmissionReview default: msg := fmt.Sprintf("Unsupported group version kind: %v", gvk) klog.Error(msg) http.Error(w, msg, http.StatusBadRequest) return } klog.V(2).Info(fmt.Sprintf("sending response: %v", responseObj)) respBytes, err := json.Marshal(responseObj) if err != nil { klog.Error(err) http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") if _, err := w.Write(respBytes); err != nil { klog.Error(err) } } func servePods(w http.ResponseWriter, r *http.Request) { serve(w, r, newDelegateToV1AdmitHandler(admitPods)) } func Main(cmd *cobra.Command, args []string) { http.HandleFunc("/pods", servePods) http.HandleFunc("/readyz", func(w http.ResponseWriter, req *http.Request) { w.Write([]byte("ok")) }) cert, _ := base64.StdEncoding.DecodeString(os.Getenv("CERT")) key, _ := base64.StdEncoding.DecodeString(os.Getenv("KEY")) pair, _ := tls.X509KeyPair(cert, key) t := &tls.Config{Certificates: []tls.Certificate{pair}} server := &http.Server{ Addr: fmt.Sprintf(":%d", 80), TLSConfig: t, } err := server.ListenAndServeTLS("", "") if err != nil { panic(err) } }