1
16
17 package admission
18
19 import (
20 "encoding/json"
21 "errors"
22 "fmt"
23 "io"
24 "net/http"
25
26 v1 "k8s.io/api/admission/v1"
27 "k8s.io/api/admission/v1beta1"
28 "k8s.io/apimachinery/pkg/runtime"
29 "k8s.io/apimachinery/pkg/runtime/schema"
30 "k8s.io/apimachinery/pkg/runtime/serializer"
31 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
32 )
33
34 var admissionScheme = runtime.NewScheme()
35 var admissionCodecs = serializer.NewCodecFactory(admissionScheme)
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 const maxRequestSize = int64(7 * 1024 * 1024)
56
57 func init() {
58 utilruntime.Must(v1.AddToScheme(admissionScheme))
59 utilruntime.Must(v1beta1.AddToScheme(admissionScheme))
60 }
61
62 var _ http.Handler = &Webhook{}
63
64 func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) {
65 ctx := r.Context()
66 if wh.WithContextFunc != nil {
67 ctx = wh.WithContextFunc(ctx, r)
68 }
69
70 if r.Body == nil || r.Body == http.NoBody {
71 err := errors.New("request body is empty")
72 wh.getLogger(nil).Error(err, "bad request")
73 wh.writeResponse(w, Errored(http.StatusBadRequest, err))
74 return
75 }
76
77 defer r.Body.Close()
78 limitedReader := &io.LimitedReader{R: r.Body, N: maxRequestSize}
79 body, err := io.ReadAll(limitedReader)
80 if err != nil {
81 wh.getLogger(nil).Error(err, "unable to read the body from the incoming request")
82 wh.writeResponse(w, Errored(http.StatusBadRequest, err))
83 return
84 }
85 if limitedReader.N <= 0 {
86 err := fmt.Errorf("request entity is too large; limit is %d bytes", maxRequestSize)
87 wh.getLogger(nil).Error(err, "unable to read the body from the incoming request; limit reached")
88 wh.writeResponse(w, Errored(http.StatusRequestEntityTooLarge, err))
89 return
90 }
91
92
93 if contentType := r.Header.Get("Content-Type"); contentType != "application/json" {
94 err = fmt.Errorf("contentType=%s, expected application/json", contentType)
95 wh.getLogger(nil).Error(err, "unable to process a request with unknown content type")
96 wh.writeResponse(w, Errored(http.StatusBadRequest, err))
97 return
98 }
99
100
101
102
103
104
105
106 req := Request{}
107 ar := unversionedAdmissionReview{}
108
109 ar.Request = &req.AdmissionRequest
110 ar.SetGroupVersionKind(v1.SchemeGroupVersion.WithKind("AdmissionReview"))
111 _, actualAdmRevGVK, err := admissionCodecs.UniversalDeserializer().Decode(body, nil, &ar)
112 if err != nil {
113 wh.getLogger(nil).Error(err, "unable to decode the request")
114 wh.writeResponse(w, Errored(http.StatusBadRequest, err))
115 return
116 }
117 wh.getLogger(&req).V(5).Info("received request")
118
119 wh.writeResponseTyped(w, wh.Handle(ctx, req), actualAdmRevGVK)
120 }
121
122
123 func (wh *Webhook) writeResponse(w io.Writer, response Response) {
124 wh.writeAdmissionResponse(w, v1.AdmissionReview{Response: &response.AdmissionResponse})
125 }
126
127
128
129 func (wh *Webhook) writeResponseTyped(w io.Writer, response Response, admRevGVK *schema.GroupVersionKind) {
130 ar := v1.AdmissionReview{
131 Response: &response.AdmissionResponse,
132 }
133
134
135
136 if admRevGVK == nil || *admRevGVK == (schema.GroupVersionKind{}) {
137 ar.SetGroupVersionKind(v1.SchemeGroupVersion.WithKind("AdmissionReview"))
138 } else {
139 ar.SetGroupVersionKind(*admRevGVK)
140 }
141 wh.writeAdmissionResponse(w, ar)
142 }
143
144
145 func (wh *Webhook) writeAdmissionResponse(w io.Writer, ar v1.AdmissionReview) {
146 if err := json.NewEncoder(w).Encode(ar); err != nil {
147 wh.getLogger(nil).Error(err, "unable to encode and write the response")
148
149
150
151
152
153 serverError := Errored(http.StatusInternalServerError, err)
154 if err = json.NewEncoder(w).Encode(v1.AdmissionReview{Response: &serverError.AdmissionResponse}); err != nil {
155 wh.getLogger(nil).Error(err, "still unable to encode and write the InternalServerError response")
156 }
157 } else {
158 res := ar.Response
159 if log := wh.getLogger(nil); log.V(5).Enabled() {
160 if res.Result != nil {
161 log = log.WithValues("code", res.Result.Code, "reason", res.Result.Reason, "message", res.Result.Message)
162 }
163 log.V(5).Info("wrote response", "requestID", res.UID, "allowed", res.Allowed)
164 }
165 }
166 }
167
168
169 type unversionedAdmissionReview struct {
170 v1.AdmissionReview
171 }
172
173 var _ runtime.Object = &unversionedAdmissionReview{}
174
View as plain text