k8s.io/kubernetes@v1.29.3/test/images/agnhost/webhook/main.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package webhook
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"io"
    23  	"net/http"
    24  
    25  	"github.com/spf13/cobra"
    26  
    27  	v1 "k8s.io/api/admission/v1"
    28  	"k8s.io/api/admission/v1beta1"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"k8s.io/klog/v2"
    31  	// TODO: try this library to see if it generates correct json patch
    32  	// https://github.com/mattbaird/jsonpatch
    33  )
    34  
    35  var (
    36  	certFile     string
    37  	keyFile      string
    38  	port         int
    39  	sidecarImage string
    40  )
    41  
    42  // CmdWebhook is used by agnhost Cobra.
    43  var CmdWebhook = &cobra.Command{
    44  	Use:   "webhook",
    45  	Short: "Starts a HTTP server, useful for testing MutatingAdmissionWebhook and ValidatingAdmissionWebhook",
    46  	Long: `Starts a HTTP server, useful for testing MutatingAdmissionWebhook and ValidatingAdmissionWebhook.
    47  After deploying it to Kubernetes cluster, the Administrator needs to create a ValidatingWebhookConfiguration
    48  in the Kubernetes cluster to register remote webhook admission controllers.`,
    49  	Args: cobra.MaximumNArgs(0),
    50  	Run:  main,
    51  }
    52  
    53  func init() {
    54  	CmdWebhook.Flags().StringVar(&certFile, "tls-cert-file", "",
    55  		"File containing the default x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert).")
    56  	CmdWebhook.Flags().StringVar(&keyFile, "tls-private-key-file", "",
    57  		"File containing the default x509 private key matching --tls-cert-file.")
    58  	CmdWebhook.Flags().IntVar(&port, "port", 443,
    59  		"Secure port that the webhook listens on")
    60  	CmdWebhook.Flags().StringVar(&sidecarImage, "sidecar-image", "",
    61  		"Image to be used as the injected sidecar")
    62  }
    63  
    64  // admitv1beta1Func handles a v1beta1 admission
    65  type admitv1beta1Func func(v1beta1.AdmissionReview) *v1beta1.AdmissionResponse
    66  
    67  // admitv1beta1Func handles a v1 admission
    68  type admitv1Func func(v1.AdmissionReview) *v1.AdmissionResponse
    69  
    70  // admitHandler is a handler, for both validators and mutators, that supports multiple admission review versions
    71  type admitHandler struct {
    72  	v1beta1 admitv1beta1Func
    73  	v1      admitv1Func
    74  }
    75  
    76  func newDelegateToV1AdmitHandler(f admitv1Func) admitHandler {
    77  	return admitHandler{
    78  		v1beta1: delegateV1beta1AdmitToV1(f),
    79  		v1:      f,
    80  	}
    81  }
    82  
    83  func delegateV1beta1AdmitToV1(f admitv1Func) admitv1beta1Func {
    84  	return func(review v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
    85  		in := v1.AdmissionReview{Request: convertAdmissionRequestToV1(review.Request)}
    86  		out := f(in)
    87  		return convertAdmissionResponseToV1beta1(out)
    88  	}
    89  }
    90  
    91  // serve handles the http portion of a request prior to handing to an admit
    92  // function
    93  func serve(w http.ResponseWriter, r *http.Request, admit admitHandler) {
    94  	var body []byte
    95  	if r.Body != nil {
    96  		if data, err := io.ReadAll(r.Body); err == nil {
    97  			body = data
    98  		}
    99  	}
   100  
   101  	// verify the content type is accurate
   102  	contentType := r.Header.Get("Content-Type")
   103  	if contentType != "application/json" {
   104  		klog.Errorf("contentType=%s, expect application/json", contentType)
   105  		return
   106  	}
   107  
   108  	klog.V(2).Info(fmt.Sprintf("handling request: %s", body))
   109  
   110  	deserializer := codecs.UniversalDeserializer()
   111  	obj, gvk, err := deserializer.Decode(body, nil, nil)
   112  	if err != nil {
   113  		msg := fmt.Sprintf("Request could not be decoded: %v", err)
   114  		klog.Error(msg)
   115  		http.Error(w, msg, http.StatusBadRequest)
   116  		return
   117  	}
   118  
   119  	var responseObj runtime.Object
   120  	switch *gvk {
   121  	case v1beta1.SchemeGroupVersion.WithKind("AdmissionReview"):
   122  		requestedAdmissionReview, ok := obj.(*v1beta1.AdmissionReview)
   123  		if !ok {
   124  			klog.Errorf("Expected v1beta1.AdmissionReview but got: %T", obj)
   125  			return
   126  		}
   127  		responseAdmissionReview := &v1beta1.AdmissionReview{}
   128  		responseAdmissionReview.SetGroupVersionKind(*gvk)
   129  		responseAdmissionReview.Response = admit.v1beta1(*requestedAdmissionReview)
   130  		responseAdmissionReview.Response.UID = requestedAdmissionReview.Request.UID
   131  		responseObj = responseAdmissionReview
   132  	case v1.SchemeGroupVersion.WithKind("AdmissionReview"):
   133  		requestedAdmissionReview, ok := obj.(*v1.AdmissionReview)
   134  		if !ok {
   135  			klog.Errorf("Expected v1.AdmissionReview but got: %T", obj)
   136  			return
   137  		}
   138  		responseAdmissionReview := &v1.AdmissionReview{}
   139  		responseAdmissionReview.SetGroupVersionKind(*gvk)
   140  		responseAdmissionReview.Response = admit.v1(*requestedAdmissionReview)
   141  		responseAdmissionReview.Response.UID = requestedAdmissionReview.Request.UID
   142  		responseObj = responseAdmissionReview
   143  	default:
   144  		msg := fmt.Sprintf("Unsupported group version kind: %v", gvk)
   145  		klog.Error(msg)
   146  		http.Error(w, msg, http.StatusBadRequest)
   147  		return
   148  	}
   149  
   150  	klog.V(2).Info(fmt.Sprintf("sending response: %v", responseObj))
   151  	respBytes, err := json.Marshal(responseObj)
   152  	if err != nil {
   153  		klog.Error(err)
   154  		http.Error(w, err.Error(), http.StatusInternalServerError)
   155  		return
   156  	}
   157  	w.Header().Set("Content-Type", "application/json")
   158  	if _, err := w.Write(respBytes); err != nil {
   159  		klog.Error(err)
   160  	}
   161  }
   162  
   163  func serveAlwaysAllowDelayFiveSeconds(w http.ResponseWriter, r *http.Request) {
   164  	serve(w, r, newDelegateToV1AdmitHandler(alwaysAllowDelayFiveSeconds))
   165  }
   166  
   167  func serveAlwaysDeny(w http.ResponseWriter, r *http.Request) {
   168  	serve(w, r, newDelegateToV1AdmitHandler(alwaysDeny))
   169  }
   170  
   171  func serveAddLabel(w http.ResponseWriter, r *http.Request) {
   172  	serve(w, r, newDelegateToV1AdmitHandler(addLabel))
   173  }
   174  
   175  func servePods(w http.ResponseWriter, r *http.Request) {
   176  	serve(w, r, newDelegateToV1AdmitHandler(admitPods))
   177  }
   178  
   179  func serveAttachingPods(w http.ResponseWriter, r *http.Request) {
   180  	serve(w, r, newDelegateToV1AdmitHandler(denySpecificAttachment))
   181  }
   182  
   183  func serveMutatePods(w http.ResponseWriter, r *http.Request) {
   184  	serve(w, r, newDelegateToV1AdmitHandler(mutatePods))
   185  }
   186  
   187  func serveMutatePodsSidecar(w http.ResponseWriter, r *http.Request) {
   188  	serve(w, r, newDelegateToV1AdmitHandler(mutatePodsSidecar))
   189  }
   190  
   191  func serveConfigmaps(w http.ResponseWriter, r *http.Request) {
   192  	serve(w, r, newDelegateToV1AdmitHandler(admitConfigMaps))
   193  }
   194  
   195  func serveMutateConfigmaps(w http.ResponseWriter, r *http.Request) {
   196  	serve(w, r, newDelegateToV1AdmitHandler(mutateConfigmaps))
   197  }
   198  
   199  func serveCustomResource(w http.ResponseWriter, r *http.Request) {
   200  	serve(w, r, newDelegateToV1AdmitHandler(admitCustomResource))
   201  }
   202  
   203  func serveMutateCustomResource(w http.ResponseWriter, r *http.Request) {
   204  	serve(w, r, newDelegateToV1AdmitHandler(mutateCustomResource))
   205  }
   206  
   207  func serveCRD(w http.ResponseWriter, r *http.Request) {
   208  	serve(w, r, newDelegateToV1AdmitHandler(admitCRD))
   209  }
   210  
   211  func main(cmd *cobra.Command, args []string) {
   212  	config := Config{
   213  		CertFile: certFile,
   214  		KeyFile:  keyFile,
   215  	}
   216  
   217  	http.HandleFunc("/always-allow-delay-5s", serveAlwaysAllowDelayFiveSeconds)
   218  	http.HandleFunc("/always-deny", serveAlwaysDeny)
   219  	http.HandleFunc("/add-label", serveAddLabel)
   220  	http.HandleFunc("/pods", servePods)
   221  	http.HandleFunc("/pods/attach", serveAttachingPods)
   222  	http.HandleFunc("/mutating-pods", serveMutatePods)
   223  	http.HandleFunc("/mutating-pods-sidecar", serveMutatePodsSidecar)
   224  	http.HandleFunc("/configmaps", serveConfigmaps)
   225  	http.HandleFunc("/mutating-configmaps", serveMutateConfigmaps)
   226  	http.HandleFunc("/custom-resource", serveCustomResource)
   227  	http.HandleFunc("/mutating-custom-resource", serveMutateCustomResource)
   228  	http.HandleFunc("/crd", serveCRD)
   229  	http.HandleFunc("/readyz", func(w http.ResponseWriter, req *http.Request) { w.Write([]byte("ok")) })
   230  	server := &http.Server{
   231  		Addr:      fmt.Sprintf(":%d", port),
   232  		TLSConfig: configTLS(config),
   233  	}
   234  	err := server.ListenAndServeTLS("", "")
   235  	if err != nil {
   236  		panic(err)
   237  	}
   238  }