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 }