1
16
17 package admission
18
19 import (
20 "encoding/json"
21 "fmt"
22 "io"
23 "net/http"
24
25 admission "k8s.io/api/admission/v1"
26 meta "k8s.io/apimachinery/pkg/apis/meta/v1"
27 "k8s.io/apimachinery/pkg/runtime"
28 "k8s.io/apimachinery/pkg/runtime/serializer"
29 "k8s.io/apimachinery/pkg/util/validation/field"
30 "k8s.io/klog/v2"
31
32 v1 "sigs.k8s.io/gateway-api/apis/v1"
33 v1Validation "sigs.k8s.io/gateway-api/apis/v1/validation"
34 v1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
35 v1a2Validation "sigs.k8s.io/gateway-api/apis/v1alpha2/validation"
36 v1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
37 v1b1Validation "sigs.k8s.io/gateway-api/apis/v1beta1/validation"
38 )
39
40 const admissionReview = "AdmissionReview"
41
42 var (
43 scheme = runtime.NewScheme()
44 codecs = serializer.NewCodecFactory(scheme)
45 )
46
47 var (
48 v1a2TCPRouteGVP = meta.GroupVersionResource{
49 Group: v1alpha2.GroupVersion.Group,
50 Version: v1alpha2.GroupVersion.Version,
51 Resource: "tcproutes",
52 }
53 v1a2UDPRouteGVP = meta.GroupVersionResource{
54 Group: v1alpha2.GroupVersion.Group,
55 Version: v1alpha2.GroupVersion.Version,
56 Resource: "udproutes",
57 }
58 v1a2TLSRouteGVP = meta.GroupVersionResource{
59 Group: v1alpha2.GroupVersion.Group,
60 Version: v1alpha2.GroupVersion.Version,
61 Resource: "tlsroutes",
62 }
63 v1a2GRPCRouteGVR = meta.GroupVersionResource{
64 Group: v1alpha2.SchemeGroupVersion.Group,
65 Version: v1alpha2.SchemeGroupVersion.Version,
66 Resource: "grpcroutes",
67 }
68 v1b1HTTPRouteGVR = meta.GroupVersionResource{
69 Group: v1beta1.SchemeGroupVersion.Group,
70 Version: v1beta1.SchemeGroupVersion.Version,
71 Resource: "httproutes",
72 }
73 v1b1GatewayGVR = meta.GroupVersionResource{
74 Group: v1beta1.SchemeGroupVersion.Group,
75 Version: v1beta1.SchemeGroupVersion.Version,
76 Resource: "gateways",
77 }
78 v1b1GatewayClassGVR = meta.GroupVersionResource{
79 Group: v1beta1.SchemeGroupVersion.Group,
80 Version: v1beta1.SchemeGroupVersion.Version,
81 Resource: "gatewayclasses",
82 }
83 v1HTTPRouteGVR = meta.GroupVersionResource{
84 Group: v1.SchemeGroupVersion.Group,
85 Version: v1.SchemeGroupVersion.Version,
86 Resource: "httproutes",
87 }
88 v1GatewayGVR = meta.GroupVersionResource{
89 Group: v1.SchemeGroupVersion.Group,
90 Version: v1.SchemeGroupVersion.Version,
91 Resource: "gateways",
92 }
93 v1GatewayClassGVR = meta.GroupVersionResource{
94 Group: v1.SchemeGroupVersion.Group,
95 Version: v1.SchemeGroupVersion.Version,
96 Resource: "gatewayclasses",
97 }
98 )
99
100 func log500(w http.ResponseWriter, err error) {
101 klog.Errorf("failed to process request: %v\n", err)
102 http.Error(w, err.Error(), http.StatusInternalServerError)
103 }
104
105
106
107 func ServeHTTP(w http.ResponseWriter, r *http.Request) {
108 if r.Method != http.MethodPost {
109 w.WriteHeader(http.StatusMethodNotAllowed)
110 http.Error(w, fmt.Sprintf("invalid method %s, only POST requests are allowed", r.Method), http.StatusMethodNotAllowed)
111 return
112 }
113
114 if r.Body == nil {
115 http.Error(w, "admission review object is missing",
116 http.StatusBadRequest)
117 return
118 }
119 data, err := io.ReadAll(r.Body)
120 if err != nil {
121 log500(w, err)
122 return
123 }
124
125 review := admission.AdmissionReview{}
126 err = json.Unmarshal(data, &review)
127 if err != nil {
128 http.Error(w, err.Error(), http.StatusBadRequest)
129 return
130 }
131
132 if review.Kind != admissionReview {
133 http.Error(w, "submitted object is not of kind AdmissionReview", http.StatusBadRequest)
134 return
135 }
136
137 response, err := handleValidation(*review.Request)
138 if err != nil {
139 log500(w, err)
140 return
141 }
142 review.Response = response
143 data, err = json.Marshal(review)
144 if err != nil {
145 log500(w, err)
146 return
147 }
148 _, err = w.Write(data)
149 if err != nil {
150 klog.Errorf("failed to write HTTP response: %v\n", err)
151 return
152 }
153 }
154
155 func handleValidation(request admission.AdmissionRequest) (*admission.AdmissionResponse, error) {
156 var (
157 response admission.AdmissionResponse
158 deserializer = codecs.UniversalDeserializer()
159 fieldErr field.ErrorList
160 )
161
162 if request.Operation == admission.Delete ||
163 request.Operation == admission.Connect {
164 response.UID = request.UID
165 response.Allowed = true
166 return &response, nil
167 }
168
169 switch request.Resource {
170 case v1a2TCPRouteGVP:
171 var tRoute v1alpha2.TCPRoute
172 _, _, err := deserializer.Decode(request.Object.Raw, nil, &tRoute)
173 if err != nil {
174 return nil, err
175 }
176 fieldErr = v1a2Validation.ValidateTCPRoute(&tRoute)
177 case v1a2UDPRouteGVP:
178 var uRoute v1alpha2.UDPRoute
179 _, _, err := deserializer.Decode(request.Object.Raw, nil, &uRoute)
180 if err != nil {
181 return nil, err
182 }
183 fieldErr = v1a2Validation.ValidateUDPRoute(&uRoute)
184 case v1a2TLSRouteGVP:
185 var tRoute v1alpha2.TLSRoute
186 _, _, err := deserializer.Decode(request.Object.Raw, nil, &tRoute)
187 if err != nil {
188 return nil, err
189 }
190 fieldErr = v1a2Validation.ValidateTLSRoute(&tRoute)
191 case v1a2GRPCRouteGVR:
192 var gRoute v1alpha2.GRPCRoute
193 _, _, err := deserializer.Decode(request.Object.Raw, nil, &gRoute)
194 if err != nil {
195 return nil, err
196 }
197
198 fieldErr = v1a2Validation.ValidateGRPCRoute(&gRoute)
199 case v1b1HTTPRouteGVR:
200 var hRoute v1beta1.HTTPRoute
201 _, _, err := deserializer.Decode(request.Object.Raw, nil, &hRoute)
202 if err != nil {
203 return nil, err
204 }
205
206 fieldErr = v1b1Validation.ValidateHTTPRoute(&hRoute)
207 case v1b1GatewayGVR:
208 var gateway v1beta1.Gateway
209 _, _, err := deserializer.Decode(request.Object.Raw, nil, &gateway)
210 if err != nil {
211 return nil, err
212 }
213 fieldErr = v1b1Validation.ValidateGateway(&gateway)
214 case v1b1GatewayClassGVR:
215
216 if request.Operation != admission.Update {
217 break
218 }
219 var gatewayClass v1beta1.GatewayClass
220 _, _, err := deserializer.Decode(request.Object.Raw, nil, &gatewayClass)
221 if err != nil {
222 return nil, err
223 }
224 var gatewayClassOld v1beta1.GatewayClass
225 _, _, err = deserializer.Decode(request.OldObject.Raw, nil, &gatewayClassOld)
226 if err != nil {
227 return nil, err
228 }
229 fieldErr = v1b1Validation.ValidateGatewayClassUpdate(&gatewayClassOld, &gatewayClass)
230 case v1HTTPRouteGVR:
231 var hRoute v1.HTTPRoute
232 _, _, err := deserializer.Decode(request.Object.Raw, nil, &hRoute)
233 if err != nil {
234 return nil, err
235 }
236 fieldErr = v1Validation.ValidateHTTPRoute(&hRoute)
237 case v1GatewayGVR:
238 var gateway v1.Gateway
239 _, _, err := deserializer.Decode(request.Object.Raw, nil, &gateway)
240 if err != nil {
241 return nil, err
242 }
243 fieldErr = v1Validation.ValidateGateway(&gateway)
244 case v1GatewayClassGVR:
245
246 if request.Operation != admission.Update {
247 break
248 }
249 var gatewayClass v1.GatewayClass
250 _, _, err := deserializer.Decode(request.Object.Raw, nil, &gatewayClass)
251 if err != nil {
252 return nil, err
253 }
254 var gatewayClassOld v1.GatewayClass
255 _, _, err = deserializer.Decode(request.OldObject.Raw, nil, &gatewayClassOld)
256 if err != nil {
257 return nil, err
258 }
259 fieldErr = v1Validation.ValidateGatewayClassUpdate(&gatewayClassOld, &gatewayClass)
260 default:
261 return nil, fmt.Errorf("unknown resource '%v'", request.Resource.Resource)
262 }
263
264 if len(fieldErr) > 0 {
265 return &admission.AdmissionResponse{
266 UID: request.UID,
267 Allowed: false,
268 Result: &meta.Status{
269 Message: fmt.Sprintf("%s", fieldErr.ToAggregate()),
270 Code: 400,
271 },
272 }, nil
273 }
274
275 return &admission.AdmissionResponse{
276 UID: request.UID,
277 Allowed: true,
278 Result: &meta.Status{},
279 }, nil
280 }
281
View as plain text