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  }