k8s.io/apiserver@v0.31.1/pkg/endpoints/request/requestinfo.go (about) 1 /* 2 Copyright 2016 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 "context" 21 "fmt" 22 "net/http" 23 "strings" 24 25 "k8s.io/apimachinery/pkg/api/validation/path" 26 metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" 27 metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/util/sets" 30 genericfeatures "k8s.io/apiserver/pkg/features" 31 utilfeature "k8s.io/apiserver/pkg/util/feature" 32 33 "k8s.io/klog/v2" 34 ) 35 36 // LongRunningRequestCheck is a predicate which is true for long-running http requests. 37 type LongRunningRequestCheck func(r *http.Request, requestInfo *RequestInfo) bool 38 39 type RequestInfoResolver interface { 40 NewRequestInfo(req *http.Request) (*RequestInfo, error) 41 } 42 43 // RequestInfo holds information parsed from the http.Request 44 type RequestInfo struct { 45 // IsResourceRequest indicates whether or not the request is for an API resource or subresource 46 IsResourceRequest bool 47 // Path is the URL path of the request 48 Path string 49 // Verb is the kube verb associated with the request for API requests, not the http verb. This includes things like list and watch. 50 // for non-resource requests, this is the lowercase http verb 51 Verb string 52 53 APIPrefix string 54 APIGroup string 55 APIVersion string 56 Namespace string 57 // Resource is the name of the resource being requested. This is not the kind. For example: pods 58 Resource string 59 // Subresource is the name of the subresource being requested. This is a different resource, scoped to the parent resource, but it may have a different kind. 60 // For instance, /pods has the resource "pods" and the kind "Pod", while /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod" 61 // (because status operates on pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource "binding", and kind "Binding". 62 Subresource string 63 // Name is empty for some verbs, but if the request directly indicates a name (not in body content) then this field is filled in. 64 Name string 65 // Parts are the path parts for the request, always starting with /{resource}/{name} 66 Parts []string 67 68 // FieldSelector contains the unparsed field selector from a request. It is only present if the apiserver 69 // honors field selectors for the verb this request is associated with. 70 FieldSelector string 71 // LabelSelector contains the unparsed field selector from a request. It is only present if the apiserver 72 // honors field selectors for the verb this request is associated with. 73 LabelSelector string 74 } 75 76 // specialVerbs contains just strings which are used in REST paths for special actions that don't fall under the normal 77 // CRUDdy GET/POST/PUT/DELETE actions on REST objects. 78 // TODO: find a way to keep this up to date automatically. Maybe dynamically populate list as handlers added to 79 // master's Mux. 80 var specialVerbs = sets.NewString("proxy", "watch") 81 82 // specialVerbsNoSubresources contains root verbs which do not allow subresources 83 var specialVerbsNoSubresources = sets.NewString("proxy") 84 85 // namespaceSubresources contains subresources of namespace 86 // this list allows the parser to distinguish between a namespace subresource, and a namespaced resource 87 var namespaceSubresources = sets.NewString("status", "finalize") 88 89 // verbsWithSelectors is the list of verbs which support fieldSelector and labelSelector parameters 90 var verbsWithSelectors = sets.NewString("list", "watch", "deletecollection") 91 92 // NamespaceSubResourcesForTest exports namespaceSubresources for testing in pkg/controlplane/master_test.go, so we never drift 93 var NamespaceSubResourcesForTest = sets.NewString(namespaceSubresources.List()...) 94 95 type RequestInfoFactory struct { 96 APIPrefixes sets.String // without leading and trailing slashes 97 GrouplessAPIPrefixes sets.String // without leading and trailing slashes 98 } 99 100 // TODO write an integration test against the swagger doc to test the RequestInfo and match up behavior to responses 101 // NewRequestInfo returns the information from the http request. If error is not nil, RequestInfo holds the information as best it is known before the failure 102 // It handles both resource and non-resource requests and fills in all the pertinent information for each. 103 // Valid Inputs: 104 // Resource paths 105 // /apis/{api-group}/{version}/namespaces 106 // /api/{version}/namespaces 107 // /api/{version}/namespaces/{namespace} 108 // /api/{version}/namespaces/{namespace}/{resource} 109 // /api/{version}/namespaces/{namespace}/{resource}/{resourceName} 110 // /api/{version}/{resource} 111 // /api/{version}/{resource}/{resourceName} 112 // 113 // Special verbs without subresources: 114 // /api/{version}/proxy/{resource}/{resourceName} 115 // /api/{version}/proxy/namespaces/{namespace}/{resource}/{resourceName} 116 // 117 // Special verbs with subresources: 118 // /api/{version}/watch/{resource} 119 // /api/{version}/watch/namespaces/{namespace}/{resource} 120 // 121 // NonResource paths 122 // /apis/{api-group}/{version} 123 // /apis/{api-group} 124 // /apis 125 // /api/{version} 126 // /api 127 // /healthz 128 // / 129 func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, error) { 130 // start with a non-resource request until proven otherwise 131 requestInfo := RequestInfo{ 132 IsResourceRequest: false, 133 Path: req.URL.Path, 134 Verb: strings.ToLower(req.Method), 135 } 136 137 currentParts := splitPath(req.URL.Path) 138 if len(currentParts) < 3 { 139 // return a non-resource request 140 return &requestInfo, nil 141 } 142 143 if !r.APIPrefixes.Has(currentParts[0]) { 144 // return a non-resource request 145 return &requestInfo, nil 146 } 147 requestInfo.APIPrefix = currentParts[0] 148 currentParts = currentParts[1:] 149 150 if !r.GrouplessAPIPrefixes.Has(requestInfo.APIPrefix) { 151 // one part (APIPrefix) has already been consumed, so this is actually "do we have four parts?" 152 if len(currentParts) < 3 { 153 // return a non-resource request 154 return &requestInfo, nil 155 } 156 157 requestInfo.APIGroup = currentParts[0] 158 currentParts = currentParts[1:] 159 } 160 161 requestInfo.IsResourceRequest = true 162 requestInfo.APIVersion = currentParts[0] 163 currentParts = currentParts[1:] 164 165 // handle input of form /{specialVerb}/* 166 verbViaPathPrefix := false 167 if specialVerbs.Has(currentParts[0]) { 168 if len(currentParts) < 2 { 169 return &requestInfo, fmt.Errorf("unable to determine kind and namespace from url, %v", req.URL) 170 } 171 172 requestInfo.Verb = currentParts[0] 173 currentParts = currentParts[1:] 174 verbViaPathPrefix = true 175 176 } else { 177 switch req.Method { 178 case "POST": 179 requestInfo.Verb = "create" 180 case "GET", "HEAD": 181 requestInfo.Verb = "get" 182 case "PUT": 183 requestInfo.Verb = "update" 184 case "PATCH": 185 requestInfo.Verb = "patch" 186 case "DELETE": 187 requestInfo.Verb = "delete" 188 default: 189 requestInfo.Verb = "" 190 } 191 } 192 193 // URL forms: /namespaces/{namespace}/{kind}/*, where parts are adjusted to be relative to kind 194 if currentParts[0] == "namespaces" { 195 if len(currentParts) > 1 { 196 requestInfo.Namespace = currentParts[1] 197 198 // if there is another step after the namespace name and it is not a known namespace subresource 199 // move currentParts to include it as a resource in its own right 200 if len(currentParts) > 2 && !namespaceSubresources.Has(currentParts[2]) { 201 currentParts = currentParts[2:] 202 } 203 } 204 } else { 205 requestInfo.Namespace = metav1.NamespaceNone 206 } 207 208 // parsing successful, so we now know the proper value for .Parts 209 requestInfo.Parts = currentParts 210 211 // parts look like: resource/resourceName/subresource/other/stuff/we/don't/interpret 212 switch { 213 case len(requestInfo.Parts) >= 3 && !specialVerbsNoSubresources.Has(requestInfo.Verb): 214 requestInfo.Subresource = requestInfo.Parts[2] 215 fallthrough 216 case len(requestInfo.Parts) >= 2: 217 requestInfo.Name = requestInfo.Parts[1] 218 fallthrough 219 case len(requestInfo.Parts) >= 1: 220 requestInfo.Resource = requestInfo.Parts[0] 221 } 222 223 // if there's no name on the request and we thought it was a get before, then the actual verb is a list or a watch 224 if len(requestInfo.Name) == 0 && requestInfo.Verb == "get" { 225 opts := metainternalversion.ListOptions{} 226 if err := metainternalversionscheme.ParameterCodec.DecodeParameters(req.URL.Query(), metav1.SchemeGroupVersion, &opts); err != nil { 227 // An error in parsing request will result in default to "list" and not setting "name" field. 228 klog.ErrorS(err, "Couldn't parse request", "request", req.URL.Query()) 229 // Reset opts to not rely on partial results from parsing. 230 // However, if watch is set, let's report it. 231 opts = metainternalversion.ListOptions{} 232 if values := req.URL.Query()["watch"]; len(values) > 0 { 233 switch strings.ToLower(values[0]) { 234 case "false", "0": 235 default: 236 opts.Watch = true 237 } 238 } 239 } 240 241 if opts.Watch { 242 requestInfo.Verb = "watch" 243 } else { 244 requestInfo.Verb = "list" 245 } 246 247 if opts.FieldSelector != nil { 248 if name, ok := opts.FieldSelector.RequiresExactMatch("metadata.name"); ok { 249 if len(path.IsValidPathSegmentName(name)) == 0 { 250 requestInfo.Name = name 251 } 252 } 253 } 254 } 255 256 // if there's no name on the request and we thought it was a delete before, then the actual verb is deletecollection 257 if len(requestInfo.Name) == 0 && requestInfo.Verb == "delete" { 258 requestInfo.Verb = "deletecollection" 259 } 260 261 if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.AuthorizeWithSelectors) { 262 // Don't support selector authorization on requests that used the deprecated verb-via-path mechanism, since they don't support selectors consistently. 263 // There are multi-object and single-object watch endpoints, and only the multi-object one supports selectors. 264 if !verbViaPathPrefix && verbsWithSelectors.Has(requestInfo.Verb) { 265 // interestingly these are parsed above, but the current structure there means that if one (or anything) in the 266 // listOptions fails to decode, the field and label selectors are lost. 267 // therefore, do the straight query param read here. 268 if vals := req.URL.Query()["fieldSelector"]; len(vals) > 0 { 269 requestInfo.FieldSelector = vals[0] 270 } 271 if vals := req.URL.Query()["labelSelector"]; len(vals) > 0 { 272 requestInfo.LabelSelector = vals[0] 273 } 274 } 275 } 276 277 return &requestInfo, nil 278 } 279 280 type requestInfoKeyType int 281 282 // requestInfoKey is the RequestInfo key for the context. It's of private type here. Because 283 // keys are interfaces and interfaces are equal when the type and the value is equal, this 284 // does not conflict with the keys defined in pkg/api. 285 const requestInfoKey requestInfoKeyType = iota 286 287 // WithRequestInfo returns a copy of parent in which the request info value is set 288 func WithRequestInfo(parent context.Context, info *RequestInfo) context.Context { 289 return WithValue(parent, requestInfoKey, info) 290 } 291 292 // RequestInfoFrom returns the value of the RequestInfo key on the ctx 293 func RequestInfoFrom(ctx context.Context) (*RequestInfo, bool) { 294 info, ok := ctx.Value(requestInfoKey).(*RequestInfo) 295 return info, ok 296 } 297 298 // splitPath returns the segments for a URL path. 299 func splitPath(path string) []string { 300 path = strings.Trim(path, "/") 301 if path == "" { 302 return []string{} 303 } 304 return strings.Split(path, "/") 305 }