1
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
32
33 )
34
35 var (
36 certFile string
37 keyFile string
38 port int
39 sidecarImage string
40 )
41
42
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
65 type admitv1beta1Func func(v1beta1.AdmissionReview) *v1beta1.AdmissionResponse
66
67
68 type admitv1Func func(v1.AdmissionReview) *v1.AdmissionResponse
69
70
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
92
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
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 }
239
View as plain text