k8s.io/apiserver@v0.31.1/pkg/endpoints/handlers/rest.go (about) 1 /* 2 Copyright 2014 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 handlers 18 19 import ( 20 "context" 21 "encoding/hex" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "net/http" 26 "net/url" 27 "strings" 28 "time" 29 30 grpccodes "google.golang.org/grpc/codes" 31 grpcstatus "google.golang.org/grpc/status" 32 33 apiequality "k8s.io/apimachinery/pkg/api/equality" 34 "k8s.io/apimachinery/pkg/api/errors" 35 "k8s.io/apimachinery/pkg/api/meta" 36 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 37 metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" 38 "k8s.io/apimachinery/pkg/runtime" 39 "k8s.io/apimachinery/pkg/runtime/schema" 40 "k8s.io/apimachinery/pkg/types" 41 "k8s.io/apimachinery/pkg/util/managedfields" 42 "k8s.io/apiserver/pkg/admission" 43 "k8s.io/apiserver/pkg/authorization/authorizer" 44 requestmetrics "k8s.io/apiserver/pkg/endpoints/handlers/metrics" 45 "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" 46 "k8s.io/apiserver/pkg/endpoints/metrics" 47 "k8s.io/apiserver/pkg/endpoints/request" 48 "k8s.io/apiserver/pkg/registry/rest" 49 "k8s.io/apiserver/pkg/warning" 50 ) 51 52 const ( 53 // 34 chose as a number close to 30 that is likely to be unique enough to jump out at me the next time I see a timeout. 54 // Everyone chooses 30. 55 requestTimeoutUpperBound = 34 * time.Second 56 // DuplicateOwnerReferencesWarningFormat is the warning that a client receives when a create/update request contains 57 // duplicate owner reference entries. 58 DuplicateOwnerReferencesWarningFormat = ".metadata.ownerReferences contains duplicate entries; API server dedups owner references in 1.20+, and may reject such requests as early as 1.24; please fix your requests; duplicate UID(s) observed: %v" 59 // DuplicateOwnerReferencesAfterMutatingAdmissionWarningFormat indicates the duplication was observed 60 // after mutating admission. 61 // NOTE: For CREATE and UPDATE requests the API server dedups both before and after mutating admission. 62 // For PATCH request the API server only dedups after mutating admission. 63 DuplicateOwnerReferencesAfterMutatingAdmissionWarningFormat = ".metadata.ownerReferences contains duplicate entries after mutating admission happens; API server dedups owner references in 1.20+, and may reject such requests as early as 1.24; please fix your requests; duplicate UID(s) observed: %v" 64 // shortPrefix is one possible beginning of yaml unmarshal strict errors. 65 shortPrefix = "yaml: unmarshal errors:\n" 66 // longPrefix is the other possible beginning of yaml unmarshal strict errors. 67 longPrefix = "error converting YAML to JSON: yaml: unmarshal errors:\n" 68 ) 69 70 // RequestScope encapsulates common fields across all RESTful handler methods. 71 type RequestScope struct { 72 Namer ScopeNamer 73 74 Serializer runtime.NegotiatedSerializer 75 runtime.ParameterCodec 76 77 // StandardSerializers, if set, restricts which serializers can be used when 78 // we aren't transforming the output (into Table or PartialObjectMetadata). 79 // Used only by CRDs which do not yet support Protobuf. 80 StandardSerializers []runtime.SerializerInfo 81 82 Creater runtime.ObjectCreater 83 Convertor runtime.ObjectConvertor 84 Defaulter runtime.ObjectDefaulter 85 Typer runtime.ObjectTyper 86 UnsafeConvertor runtime.ObjectConvertor 87 Authorizer authorizer.Authorizer 88 89 EquivalentResourceMapper runtime.EquivalentResourceMapper 90 91 TableConvertor rest.TableConvertor 92 FieldManager *managedfields.FieldManager 93 94 Resource schema.GroupVersionResource 95 Kind schema.GroupVersionKind 96 97 // AcceptsGroupVersionDelegate is an optional delegate that can be queried about whether a given GVK 98 // can be accepted in create or update requests. If nil, only scope.Kind is accepted. 99 // Note that this does not enable multi-version support for reads from a single endpoint. 100 AcceptsGroupVersionDelegate rest.GroupVersionAcceptor 101 102 Subresource string 103 104 MetaGroupVersion schema.GroupVersion 105 106 // HubGroupVersion indicates what version objects read from etcd or incoming requests should be converted to for in-memory handling. 107 HubGroupVersion schema.GroupVersion 108 109 MaxRequestBodyBytes int64 110 } 111 112 func (scope *RequestScope) err(err error, w http.ResponseWriter, req *http.Request) { 113 responsewriters.ErrorNegotiated(err, scope.Serializer, scope.Kind.GroupVersion(), w, req) 114 } 115 116 // AcceptsGroupVersion returns true if the specified GroupVersion is allowed 117 // in create and update requests. 118 func (scope *RequestScope) AcceptsGroupVersion(gv schema.GroupVersion) bool { 119 // If there's a custom acceptor, delegate to it. This is extremely rare. 120 if scope.AcceptsGroupVersionDelegate != nil { 121 return scope.AcceptsGroupVersionDelegate.AcceptsGroupVersion(gv) 122 } 123 // Fall back to only allowing the singular Kind. This is the typical behavior. 124 return gv == scope.Kind.GroupVersion() 125 } 126 127 func (scope *RequestScope) AllowsMediaTypeTransform(mimeType, mimeSubType string, gvk *schema.GroupVersionKind) bool { 128 // some handlers like CRDs can't serve all the mime types that PartialObjectMetadata or Table can - if 129 // gvk is nil (no conversion) allow StandardSerializers to further restrict the set of mime types. 130 if gvk == nil { 131 if len(scope.StandardSerializers) == 0 { 132 return true 133 } 134 for _, info := range scope.StandardSerializers { 135 if info.MediaTypeType == mimeType && info.MediaTypeSubType == mimeSubType { 136 return true 137 } 138 } 139 return false 140 } 141 142 // TODO: this is temporary, replace with an abstraction calculated at endpoint installation time 143 if gvk.GroupVersion() == metav1beta1.SchemeGroupVersion || gvk.GroupVersion() == metav1.SchemeGroupVersion { 144 switch gvk.Kind { 145 case "Table": 146 return scope.TableConvertor != nil && 147 mimeType == "application" && 148 (mimeSubType == "json" || mimeSubType == "yaml") 149 case "PartialObjectMetadata", "PartialObjectMetadataList": 150 // TODO: should delineate between lists and non-list endpoints 151 return true 152 default: 153 return false 154 } 155 } 156 return false 157 } 158 159 func (scope *RequestScope) AllowsServerVersion(version string) bool { 160 return version == scope.MetaGroupVersion.Version 161 } 162 163 func (scope *RequestScope) AllowsStreamSchema(s string) bool { 164 return s == "watch" 165 } 166 167 var _ admission.ObjectInterfaces = &RequestScope{} 168 169 func (r *RequestScope) GetObjectCreater() runtime.ObjectCreater { return r.Creater } 170 func (r *RequestScope) GetObjectTyper() runtime.ObjectTyper { return r.Typer } 171 func (r *RequestScope) GetObjectDefaulter() runtime.ObjectDefaulter { return r.Defaulter } 172 func (r *RequestScope) GetObjectConvertor() runtime.ObjectConvertor { return r.Convertor } 173 func (r *RequestScope) GetEquivalentResourceMapper() runtime.EquivalentResourceMapper { 174 return r.EquivalentResourceMapper 175 } 176 177 // ConnectResource returns a function that handles a connect request on a rest.Storage object. 178 func ConnectResource(connecter rest.Connecter, scope *RequestScope, admit admission.Interface, restPath string, isSubresource bool) http.HandlerFunc { 179 return func(w http.ResponseWriter, req *http.Request) { 180 if isDryRun(req.URL) { 181 scope.err(errors.NewBadRequest("dryRun is not supported"), w, req) 182 return 183 } 184 185 namespace, name, err := scope.Namer.Name(req) 186 if err != nil { 187 scope.err(err, w, req) 188 return 189 } 190 ctx := req.Context() 191 ctx = request.WithNamespace(ctx, namespace) 192 admit = admission.WithAudit(admit) 193 194 opts, subpath, subpathKey := connecter.NewConnectOptions() 195 if err := getRequestOptions(req, scope, opts, subpath, subpathKey, isSubresource); err != nil { 196 err = errors.NewBadRequest(err.Error()) 197 scope.err(err, w, req) 198 return 199 } 200 if admit != nil && admit.Handles(admission.Connect) { 201 userInfo, _ := request.UserFrom(ctx) 202 // TODO: remove the mutating admission here as soon as we have ported all plugin that handle CONNECT 203 if mutatingAdmission, ok := admit.(admission.MutationInterface); ok { 204 err = mutatingAdmission.Admit(ctx, admission.NewAttributesRecord(opts, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, nil, false, userInfo), scope) 205 if err != nil { 206 scope.err(err, w, req) 207 return 208 } 209 } 210 if validatingAdmission, ok := admit.(admission.ValidationInterface); ok { 211 err = validatingAdmission.Validate(ctx, admission.NewAttributesRecord(opts, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, nil, false, userInfo), scope) 212 if err != nil { 213 scope.err(err, w, req) 214 return 215 } 216 } 217 } 218 requestInfo, _ := request.RequestInfoFrom(ctx) 219 metrics.RecordLongRunning(req, requestInfo, metrics.APIServerComponent, func() { 220 handler, err := connecter.Connect(ctx, name, opts, &responder{scope: scope, req: req, w: w}) 221 if err != nil { 222 scope.err(err, w, req) 223 return 224 } 225 handler.ServeHTTP(w, req) 226 }) 227 } 228 } 229 230 // responder implements rest.Responder for assisting a connector in writing objects or errors. 231 type responder struct { 232 scope *RequestScope 233 req *http.Request 234 w http.ResponseWriter 235 } 236 237 func (r *responder) Object(statusCode int, obj runtime.Object) { 238 responsewriters.WriteObjectNegotiated(r.scope.Serializer, r.scope, r.scope.Kind.GroupVersion(), r.w, r.req, statusCode, obj, false) 239 } 240 241 func (r *responder) Error(err error) { 242 r.scope.err(err, r.w, r.req) 243 } 244 245 // transformDecodeError adds additional information into a bad-request api error when a decode fails. 246 func transformDecodeError(typer runtime.ObjectTyper, baseErr error, into runtime.Object, gvk *schema.GroupVersionKind, body []byte) error { 247 objGVKs, _, err := typer.ObjectKinds(into) 248 if err != nil { 249 return errors.NewBadRequest(err.Error()) 250 } 251 objGVK := objGVKs[0] 252 if gvk != nil && len(gvk.Kind) > 0 { 253 return errors.NewBadRequest(fmt.Sprintf("%s in version %q cannot be handled as a %s: %v", gvk.Kind, gvk.Version, objGVK.Kind, baseErr)) 254 } 255 summary := summarizeData(body, 30) 256 return errors.NewBadRequest(fmt.Sprintf("the object provided is unrecognized (must be of type %s): %v (%s)", objGVK.Kind, baseErr, summary)) 257 } 258 259 func hasUID(obj runtime.Object) (bool, error) { 260 if obj == nil { 261 return false, nil 262 } 263 accessor, err := meta.Accessor(obj) 264 if err != nil { 265 return false, errors.NewInternalError(err) 266 } 267 if len(accessor.GetUID()) == 0 { 268 return false, nil 269 } 270 return true, nil 271 } 272 273 // checkName checks the provided name against the request 274 func checkName(obj runtime.Object, name, namespace string, namer ScopeNamer) error { 275 objNamespace, objName, err := namer.ObjectName(obj) 276 if err != nil { 277 return errors.NewBadRequest(fmt.Sprintf( 278 "the name of the object (%s based on URL) was undeterminable: %v", name, err)) 279 } 280 if objName != name { 281 return errors.NewBadRequest(fmt.Sprintf( 282 "the name of the object (%s) does not match the name on the URL (%s)", objName, name)) 283 } 284 if len(namespace) > 0 { 285 if len(objNamespace) > 0 && objNamespace != namespace { 286 return errors.NewBadRequest(fmt.Sprintf( 287 "the namespace of the object (%s) does not match the namespace on the request (%s)", objNamespace, namespace)) 288 } 289 } 290 291 return nil 292 } 293 294 // dedupOwnerReferences dedups owner references over the entire entry. 295 // NOTE: We don't know enough about the existing cases of owner references 296 // sharing the same UID but different fields. Nor do we know what might break. 297 // In the future we may just dedup/reject owner references with the same UID. 298 func dedupOwnerReferences(refs []metav1.OwnerReference) ([]metav1.OwnerReference, []string) { 299 var result []metav1.OwnerReference 300 var duplicates []string 301 seen := make(map[types.UID]struct{}) 302 for _, ref := range refs { 303 _, ok := seen[ref.UID] 304 // Short-circuit if we haven't seen the UID before. Otherwise 305 // check the entire list we have so far. 306 if !ok || !hasOwnerReference(result, ref) { 307 seen[ref.UID] = struct{}{} 308 result = append(result, ref) 309 } else { 310 duplicates = append(duplicates, string(ref.UID)) 311 } 312 } 313 return result, duplicates 314 } 315 316 // hasOwnerReference returns true if refs has an item equal to ref. The function 317 // focuses on semantic equality instead of memory equality, to catch duplicates 318 // with different pointer addresses. The function uses apiequality.Semantic 319 // instead of implementing its own comparison, to tolerate API changes to 320 // metav1.OwnerReference. 321 // NOTE: This is expensive, but we accept it because we've made sure it only 322 // happens to owner references containing duplicate UIDs, plus typically the 323 // number of items in the list should be small. 324 func hasOwnerReference(refs []metav1.OwnerReference, ref metav1.OwnerReference) bool { 325 for _, r := range refs { 326 if apiequality.Semantic.DeepEqual(r, ref) { 327 return true 328 } 329 } 330 return false 331 } 332 333 // dedupOwnerReferencesAndAddWarning dedups owner references in the object metadata. 334 // If duplicates are found, the function records a warning to the provided context. 335 func dedupOwnerReferencesAndAddWarning(obj runtime.Object, requestContext context.Context, afterMutatingAdmission bool) { 336 accessor, err := meta.Accessor(obj) 337 if err != nil { 338 // The object doesn't have metadata. Nothing we need to do here. 339 return 340 } 341 refs := accessor.GetOwnerReferences() 342 deduped, duplicates := dedupOwnerReferences(refs) 343 if len(duplicates) > 0 { 344 // NOTE: For CREATE and UPDATE requests the API server dedups both before and after mutating admission. 345 // For PATCH request the API server only dedups after mutating admission. 346 format := DuplicateOwnerReferencesWarningFormat 347 if afterMutatingAdmission { 348 format = DuplicateOwnerReferencesAfterMutatingAdmissionWarningFormat 349 } 350 warning.AddWarning(requestContext, "", fmt.Sprintf(format, 351 strings.Join(duplicates, ", "))) 352 accessor.SetOwnerReferences(deduped) 353 } 354 } 355 356 func summarizeData(data []byte, maxLength int) string { 357 switch { 358 case len(data) == 0: 359 return "<empty>" 360 case data[0] == '{': 361 if len(data) > maxLength { 362 return string(data[:maxLength]) + " ..." 363 } 364 return string(data) 365 default: 366 if len(data) > maxLength { 367 return hex.EncodeToString(data[:maxLength]) + " ..." 368 } 369 return hex.EncodeToString(data) 370 } 371 } 372 373 func limitedReadBody(req *http.Request, limit int64) ([]byte, error) { 374 defer req.Body.Close() 375 if limit <= 0 { 376 return ioutil.ReadAll(req.Body) 377 } 378 lr := &io.LimitedReader{ 379 R: req.Body, 380 N: limit + 1, 381 } 382 data, err := ioutil.ReadAll(lr) 383 if err != nil { 384 return nil, err 385 } 386 if lr.N <= 0 { 387 return nil, errors.NewRequestEntityTooLargeError(fmt.Sprintf("limit is %d", limit)) 388 } 389 return data, nil 390 } 391 392 func limitedReadBodyWithRecordMetric(ctx context.Context, req *http.Request, limit int64, resourceGroup string, verb requestmetrics.RequestBodyVerb) ([]byte, error) { 393 readBody, err := limitedReadBody(req, limit) 394 if err == nil { 395 // only record if we've read successfully 396 requestmetrics.RecordRequestBodySize(ctx, resourceGroup, verb, len(readBody)) 397 } 398 return readBody, err 399 } 400 401 func isDryRun(url *url.URL) bool { 402 return len(url.Query()["dryRun"]) != 0 403 } 404 405 // fieldValidation checks that the field validation feature is enabled 406 // and returns a valid directive of either 407 // - Ignore 408 // - Warn (default) 409 // - Strict 410 func fieldValidation(directive string) string { 411 if directive == "" { 412 return metav1.FieldValidationWarn 413 } 414 return directive 415 } 416 417 // parseYAMLWarnings takes the strict decoding errors from the yaml decoder's output 418 // and parses each individual warnings, or leaves the warning as is if 419 // it does not look like a yaml strict decoding error. 420 func parseYAMLWarnings(errString string) []string { 421 var trimmedString string 422 if trimmedShortString := strings.TrimPrefix(errString, shortPrefix); len(trimmedShortString) < len(errString) { 423 trimmedString = trimmedShortString 424 } else if trimmedLongString := strings.TrimPrefix(errString, longPrefix); len(trimmedLongString) < len(errString) { 425 trimmedString = trimmedLongString 426 } else { 427 // not a yaml error, return as-is 428 return []string{errString} 429 } 430 431 splitStrings := strings.Split(trimmedString, "\n") 432 for i, s := range splitStrings { 433 splitStrings[i] = strings.TrimSpace(s) 434 } 435 return splitStrings 436 } 437 438 // addStrictDecodingWarnings confirms that the error is a strict decoding error 439 // and if so adds a warning for each strict decoding violation. 440 func addStrictDecodingWarnings(requestContext context.Context, errs []error) { 441 for _, e := range errs { 442 yamlWarnings := parseYAMLWarnings(e.Error()) 443 for _, w := range yamlWarnings { 444 warning.AddWarning(requestContext, "", w) 445 } 446 } 447 } 448 449 type etcdError interface { 450 Code() grpccodes.Code 451 Error() string 452 } 453 454 type grpcError interface { 455 GRPCStatus() *grpcstatus.Status 456 } 457 458 func isTooLargeError(err error) bool { 459 if err != nil { 460 if etcdErr, ok := err.(etcdError); ok { 461 if etcdErr.Code() == grpccodes.InvalidArgument && etcdErr.Error() == "etcdserver: request is too large" { 462 return true 463 } 464 } 465 if grpcErr, ok := err.(grpcError); ok { 466 if grpcErr.GRPCStatus().Code() == grpccodes.ResourceExhausted && strings.Contains(grpcErr.GRPCStatus().Message(), "trying to send message larger than max") { 467 return true 468 } 469 } 470 } 471 return false 472 }