sigs.k8s.io/gateway-api@v1.0.0/pkg/admission/server.go (about) 1 /* 2 Copyright 2021 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 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 // ServeHTTP parses AdmissionReview requests and responds back 106 // with the validation result of the entity. 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 // runs only for updates 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 // runs only for updates 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 }