k8s.io/apiserver@v0.31.1/pkg/admission/plugin/webhook/request/admissionreview.go (about) 1 /* 2 Copyright 2017 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 request 18 19 import ( 20 "fmt" 21 22 admissionv1 "k8s.io/api/admission/v1" 23 admissionv1beta1 "k8s.io/api/admission/v1beta1" 24 authenticationv1 "k8s.io/api/authentication/v1" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/runtime" 27 "k8s.io/apimachinery/pkg/types" 28 "k8s.io/apimachinery/pkg/util/uuid" 29 "k8s.io/apiserver/pkg/admission" 30 "k8s.io/apiserver/pkg/admission/plugin/webhook/generic" 31 ) 32 33 // AdmissionResponse contains the fields extracted from an AdmissionReview response 34 type AdmissionResponse struct { 35 AuditAnnotations map[string]string 36 Allowed bool 37 Patch []byte 38 PatchType admissionv1.PatchType 39 Result *metav1.Status 40 Warnings []string 41 } 42 43 // VerifyAdmissionResponse checks the validity of the provided admission review object, and returns the 44 // audit annotations, whether the response allowed the request, any provided patch/patchType/status, 45 // or an error if the provided admission review was not valid. 46 func VerifyAdmissionResponse(uid types.UID, mutating bool, review runtime.Object) (*AdmissionResponse, error) { 47 switch r := review.(type) { 48 case *admissionv1.AdmissionReview: 49 if r.Response == nil { 50 return nil, fmt.Errorf("webhook response was absent") 51 } 52 53 // Verify UID matches 54 if r.Response.UID != uid { 55 return nil, fmt.Errorf("expected response.uid=%q, got %q", uid, r.Response.UID) 56 } 57 58 // Verify GVK 59 v1GVK := admissionv1.SchemeGroupVersion.WithKind("AdmissionReview") 60 if r.GroupVersionKind() != v1GVK { 61 return nil, fmt.Errorf("expected webhook response of %v, got %v", v1GVK.String(), r.GroupVersionKind().String()) 62 } 63 64 patch := []byte(nil) 65 patchType := admissionv1.PatchType("") 66 67 if mutating { 68 // Ensure a mutating webhook provides both patch and patchType together 69 if len(r.Response.Patch) > 0 && r.Response.PatchType == nil { 70 return nil, fmt.Errorf("webhook returned response.patch but not response.patchType") 71 } 72 if len(r.Response.Patch) == 0 && r.Response.PatchType != nil { 73 return nil, fmt.Errorf("webhook returned response.patchType but not response.patch") 74 } 75 patch = r.Response.Patch 76 if r.Response.PatchType != nil { 77 patchType = *r.Response.PatchType 78 if len(patchType) == 0 { 79 return nil, fmt.Errorf("webhook returned invalid response.patchType of %q", patchType) 80 } 81 } 82 } else { 83 // Ensure a validating webhook doesn't return patch or patchType 84 if len(r.Response.Patch) > 0 { 85 return nil, fmt.Errorf("validating webhook may not return response.patch") 86 } 87 if r.Response.PatchType != nil { 88 return nil, fmt.Errorf("validating webhook may not return response.patchType") 89 } 90 } 91 92 return &AdmissionResponse{ 93 AuditAnnotations: r.Response.AuditAnnotations, 94 Allowed: r.Response.Allowed, 95 Patch: patch, 96 PatchType: patchType, 97 Result: r.Response.Result, 98 Warnings: r.Response.Warnings, 99 }, nil 100 101 case *admissionv1beta1.AdmissionReview: 102 if r.Response == nil { 103 return nil, fmt.Errorf("webhook response was absent") 104 } 105 106 // Response GVK and response.uid were not verified in v1beta1 handling, allow any 107 108 patch := []byte(nil) 109 patchType := admissionv1.PatchType("") 110 if mutating { 111 patch = r.Response.Patch 112 if len(r.Response.Patch) > 0 { 113 // patch type was not verified in v1beta1 admissionreview handling. pin to only supported version if a patch is provided. 114 patchType = admissionv1.PatchTypeJSONPatch 115 } 116 } 117 118 return &AdmissionResponse{ 119 AuditAnnotations: r.Response.AuditAnnotations, 120 Allowed: r.Response.Allowed, 121 Patch: patch, 122 PatchType: patchType, 123 Result: r.Response.Result, 124 Warnings: r.Response.Warnings, 125 }, nil 126 127 default: 128 return nil, fmt.Errorf("unexpected response type %T", review) 129 } 130 } 131 132 // CreateAdmissionObjects returns the unique request uid, the AdmissionReview object to send the webhook and to decode the response into, 133 // or an error if the webhook does not support receiving any of the admission review versions we know to send 134 func CreateAdmissionObjects(versionedAttributes *admission.VersionedAttributes, invocation *generic.WebhookInvocation) (uid types.UID, request, response runtime.Object, err error) { 135 for _, version := range invocation.Webhook.GetAdmissionReviewVersions() { 136 switch version { 137 case admissionv1.SchemeGroupVersion.Version: 138 uid := types.UID(uuid.NewUUID()) 139 request := CreateV1AdmissionReview(uid, versionedAttributes, invocation) 140 response := &admissionv1.AdmissionReview{} 141 return uid, request, response, nil 142 143 case admissionv1beta1.SchemeGroupVersion.Version: 144 uid := types.UID(uuid.NewUUID()) 145 request := CreateV1beta1AdmissionReview(uid, versionedAttributes, invocation) 146 response := &admissionv1beta1.AdmissionReview{} 147 return uid, request, response, nil 148 149 } 150 } 151 return "", nil, nil, fmt.Errorf("webhook does not accept known AdmissionReview versions (v1, v1beta1)") 152 } 153 154 // CreateV1AdmissionReview creates an AdmissionReview for the provided admission.Attributes 155 func CreateV1AdmissionReview(uid types.UID, versionedAttributes *admission.VersionedAttributes, invocation *generic.WebhookInvocation) *admissionv1.AdmissionReview { 156 attr := versionedAttributes.Attributes 157 gvk := invocation.Kind 158 gvr := invocation.Resource 159 subresource := invocation.Subresource 160 requestGVK := attr.GetKind() 161 requestGVR := attr.GetResource() 162 requestSubResource := attr.GetSubresource() 163 aUserInfo := attr.GetUserInfo() 164 userInfo := authenticationv1.UserInfo{ 165 Extra: make(map[string]authenticationv1.ExtraValue), 166 Groups: aUserInfo.GetGroups(), 167 UID: aUserInfo.GetUID(), 168 Username: aUserInfo.GetName(), 169 } 170 dryRun := attr.IsDryRun() 171 172 // Convert the extra information in the user object 173 for key, val := range aUserInfo.GetExtra() { 174 userInfo.Extra[key] = authenticationv1.ExtraValue(val) 175 } 176 177 return &admissionv1.AdmissionReview{ 178 Request: &admissionv1.AdmissionRequest{ 179 UID: uid, 180 Kind: metav1.GroupVersionKind{ 181 Group: gvk.Group, 182 Kind: gvk.Kind, 183 Version: gvk.Version, 184 }, 185 Resource: metav1.GroupVersionResource{ 186 Group: gvr.Group, 187 Resource: gvr.Resource, 188 Version: gvr.Version, 189 }, 190 SubResource: subresource, 191 RequestKind: &metav1.GroupVersionKind{ 192 Group: requestGVK.Group, 193 Kind: requestGVK.Kind, 194 Version: requestGVK.Version, 195 }, 196 RequestResource: &metav1.GroupVersionResource{ 197 Group: requestGVR.Group, 198 Resource: requestGVR.Resource, 199 Version: requestGVR.Version, 200 }, 201 RequestSubResource: requestSubResource, 202 Name: attr.GetName(), 203 Namespace: attr.GetNamespace(), 204 Operation: admissionv1.Operation(attr.GetOperation()), 205 UserInfo: userInfo, 206 Object: runtime.RawExtension{ 207 Object: versionedAttributes.VersionedObject, 208 }, 209 OldObject: runtime.RawExtension{ 210 Object: versionedAttributes.VersionedOldObject, 211 }, 212 DryRun: &dryRun, 213 Options: runtime.RawExtension{ 214 Object: attr.GetOperationOptions(), 215 }, 216 }, 217 } 218 } 219 220 // CreateV1beta1AdmissionReview creates an AdmissionReview for the provided admission.Attributes 221 func CreateV1beta1AdmissionReview(uid types.UID, versionedAttributes *admission.VersionedAttributes, invocation *generic.WebhookInvocation) *admissionv1beta1.AdmissionReview { 222 attr := versionedAttributes.Attributes 223 gvk := invocation.Kind 224 gvr := invocation.Resource 225 subresource := invocation.Subresource 226 requestGVK := attr.GetKind() 227 requestGVR := attr.GetResource() 228 requestSubResource := attr.GetSubresource() 229 aUserInfo := attr.GetUserInfo() 230 userInfo := authenticationv1.UserInfo{ 231 Extra: make(map[string]authenticationv1.ExtraValue), 232 Groups: aUserInfo.GetGroups(), 233 UID: aUserInfo.GetUID(), 234 Username: aUserInfo.GetName(), 235 } 236 dryRun := attr.IsDryRun() 237 238 // Convert the extra information in the user object 239 for key, val := range aUserInfo.GetExtra() { 240 userInfo.Extra[key] = authenticationv1.ExtraValue(val) 241 } 242 243 return &admissionv1beta1.AdmissionReview{ 244 Request: &admissionv1beta1.AdmissionRequest{ 245 UID: uid, 246 Kind: metav1.GroupVersionKind{ 247 Group: gvk.Group, 248 Kind: gvk.Kind, 249 Version: gvk.Version, 250 }, 251 Resource: metav1.GroupVersionResource{ 252 Group: gvr.Group, 253 Resource: gvr.Resource, 254 Version: gvr.Version, 255 }, 256 SubResource: subresource, 257 RequestKind: &metav1.GroupVersionKind{ 258 Group: requestGVK.Group, 259 Kind: requestGVK.Kind, 260 Version: requestGVK.Version, 261 }, 262 RequestResource: &metav1.GroupVersionResource{ 263 Group: requestGVR.Group, 264 Resource: requestGVR.Resource, 265 Version: requestGVR.Version, 266 }, 267 RequestSubResource: requestSubResource, 268 Name: attr.GetName(), 269 Namespace: attr.GetNamespace(), 270 Operation: admissionv1beta1.Operation(attr.GetOperation()), 271 UserInfo: userInfo, 272 Object: runtime.RawExtension{ 273 Object: versionedAttributes.VersionedObject, 274 }, 275 OldObject: runtime.RawExtension{ 276 Object: versionedAttributes.VersionedOldObject, 277 }, 278 DryRun: &dryRun, 279 Options: runtime.RawExtension{ 280 Object: attr.GetOperationOptions(), 281 }, 282 }, 283 } 284 }