mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-03 09:22:44 +00:00
Merge pull request #6683 from deads2k/deads-handle-subresources
add support for authorizing subresources
This commit is contained in:
commit
0c8a358de0
@ -19,7 +19,6 @@ package apiserver
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strings"
|
"strings"
|
||||||
@ -240,6 +239,10 @@ type APIRequestInfo struct {
|
|||||||
Namespace string
|
Namespace string
|
||||||
// Resource is the name of the resource being requested. This is not the kind. For example: pods
|
// Resource is the name of the resource being requested. This is not the kind. For example: pods
|
||||||
Resource string
|
Resource string
|
||||||
|
// 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.
|
||||||
|
// 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"
|
||||||
|
// (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".
|
||||||
|
Subresource string
|
||||||
// Kind is the type of object being manipulated. For example: Pod
|
// Kind is the type of object being manipulated. For example: Pod
|
||||||
Kind string
|
Kind string
|
||||||
// Name is empty for some verbs, but if the request directly indicates a name (not in body content) then this field is filled in.
|
// Name is empty for some verbs, but if the request directly indicates a name (not in body content) then this field is filled in.
|
||||||
@ -251,22 +254,12 @@ type APIRequestInfo struct {
|
|||||||
Raw []string
|
Raw []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// URLPath returns the URL path for this request, including /{resource}/{name} if present but nothing
|
|
||||||
// following that.
|
|
||||||
func (info APIRequestInfo) URLPath() string {
|
|
||||||
p := info.Parts
|
|
||||||
if n := len(p); n > 2 {
|
|
||||||
// Only take resource and name
|
|
||||||
p = p[:2]
|
|
||||||
}
|
|
||||||
return path.Join("/", path.Join(info.Raw...), path.Join(p...))
|
|
||||||
}
|
|
||||||
|
|
||||||
type APIRequestInfoResolver struct {
|
type APIRequestInfoResolver struct {
|
||||||
APIPrefixes util.StringSet
|
APIPrefixes util.StringSet
|
||||||
RestMapper meta.RESTMapper
|
RestMapper meta.RESTMapper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO write an integration test against the swagger doc to test the APIRequestInfo and match up behavior to responses
|
||||||
// GetAPIRequestInfo returns the information from the http request. If error is not nil, APIRequestInfo holds the information as best it is known before the failure
|
// GetAPIRequestInfo returns the information from the http request. If error is not nil, APIRequestInfo holds the information as best it is known before the failure
|
||||||
// Valid Inputs:
|
// Valid Inputs:
|
||||||
// Storage paths
|
// Storage paths
|
||||||
@ -340,22 +333,20 @@ func (r *APIRequestInfoResolver) GetAPIRequestInfo(req *http.Request) (APIReques
|
|||||||
|
|
||||||
// URL forms: /namespaces/{namespace}/{kind}/*, where parts are adjusted to be relative to kind
|
// URL forms: /namespaces/{namespace}/{kind}/*, where parts are adjusted to be relative to kind
|
||||||
if currentParts[0] == "namespaces" {
|
if currentParts[0] == "namespaces" {
|
||||||
if len(currentParts) < 3 {
|
if len(currentParts) > 1 {
|
||||||
requestInfo.Resource = "namespaces"
|
|
||||||
if len(currentParts) > 1 {
|
|
||||||
requestInfo.Namespace = currentParts[1]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
requestInfo.Resource = currentParts[2]
|
|
||||||
requestInfo.Namespace = currentParts[1]
|
requestInfo.Namespace = currentParts[1]
|
||||||
currentParts = currentParts[2:]
|
|
||||||
|
// if there is another step after the namespace name and it is not a known namespace subresource
|
||||||
|
// move currentParts to include it as a resource in its own right
|
||||||
|
if len(currentParts) > 2 {
|
||||||
|
currentParts = currentParts[2:]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// URL forms: /{resource}/*
|
// URL forms: /{resource}/*
|
||||||
// URL forms: POST /{resource} is a legacy API convention to create in "default" namespace
|
// URL forms: POST /{resource} is a legacy API convention to create in "default" namespace
|
||||||
// URL forms: /{resource}/{resourceName} use the "default" namespace if omitted from query param
|
// URL forms: /{resource}/{resourceName} use the "default" namespace if omitted from query param
|
||||||
// URL forms: /{resource} assume cross-namespace operation if omitted from query param
|
// URL forms: /{resource} assume cross-namespace operation if omitted from query param
|
||||||
requestInfo.Resource = currentParts[0]
|
|
||||||
requestInfo.Namespace = req.URL.Query().Get("namespace")
|
requestInfo.Namespace = req.URL.Query().Get("namespace")
|
||||||
if len(requestInfo.Namespace) == 0 {
|
if len(requestInfo.Namespace) == 0 {
|
||||||
if len(currentParts) > 1 || req.Method == "POST" {
|
if len(currentParts) > 1 || req.Method == "POST" {
|
||||||
@ -371,9 +362,16 @@ func (r *APIRequestInfoResolver) GetAPIRequestInfo(req *http.Request) (APIReques
|
|||||||
// Raw should have everything not in Parts
|
// Raw should have everything not in Parts
|
||||||
requestInfo.Raw = requestInfo.Raw[:len(requestInfo.Raw)-len(currentParts)]
|
requestInfo.Raw = requestInfo.Raw[:len(requestInfo.Raw)-len(currentParts)]
|
||||||
|
|
||||||
// if there's another part remaining after the kind, then that's the resource name
|
// parts look like: resource/resourceName/subresource/other/stuff/we/don't/interpret
|
||||||
if len(requestInfo.Parts) >= 2 {
|
switch {
|
||||||
|
case len(requestInfo.Parts) >= 3:
|
||||||
|
requestInfo.Subresource = requestInfo.Parts[2]
|
||||||
|
fallthrough
|
||||||
|
case len(requestInfo.Parts) == 2:
|
||||||
requestInfo.Name = requestInfo.Parts[1]
|
requestInfo.Name = requestInfo.Parts[1]
|
||||||
|
fallthrough
|
||||||
|
case len(requestInfo.Parts) == 1:
|
||||||
|
requestInfo.Resource = requestInfo.Parts[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there's no name on the request and we thought it was a get before, then the actual verb is a list
|
// if there's no name on the request and we thought it was a get before, then the actual verb is a list
|
||||||
|
@ -135,49 +135,54 @@ func TestReadOnly(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetAPIRequestInfo(t *testing.T) {
|
func TestGetAPIRequestInfo(t *testing.T) {
|
||||||
successCases := []struct {
|
successCases := []struct {
|
||||||
method string
|
method string
|
||||||
url string
|
url string
|
||||||
expectedVerb string
|
expectedVerb string
|
||||||
expectedAPIVersion string
|
expectedAPIVersion string
|
||||||
expectedNamespace string
|
expectedNamespace string
|
||||||
expectedResource string
|
expectedResource string
|
||||||
expectedKind string
|
expectedSubresource string
|
||||||
expectedName string
|
expectedKind string
|
||||||
expectedParts []string
|
expectedName string
|
||||||
|
expectedParts []string
|
||||||
}{
|
}{
|
||||||
|
|
||||||
// resource paths
|
// resource paths
|
||||||
{"GET", "/namespaces", "list", "", "", "namespaces", "Namespace", "", []string{"namespaces"}},
|
{"GET", "/namespaces", "list", "", "", "namespaces", "", "Namespace", "", []string{"namespaces"}},
|
||||||
{"GET", "/namespaces/other", "get", "", "other", "namespaces", "Namespace", "other", []string{"namespaces", "other"}},
|
{"GET", "/namespaces/other", "get", "", "other", "namespaces", "", "Namespace", "other", []string{"namespaces", "other"}},
|
||||||
|
|
||||||
{"GET", "/namespaces/other/pods", "list", "", "other", "pods", "Pod", "", []string{"pods"}},
|
{"GET", "/namespaces/other/pods", "list", "", "other", "pods", "", "Pod", "", []string{"pods"}},
|
||||||
{"GET", "/namespaces/other/pods/foo", "get", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/namespaces/other/pods/foo", "get", "", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/pods", "list", "", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
|
{"GET", "/pods", "list", "", api.NamespaceAll, "pods", "", "Pod", "", []string{"pods"}},
|
||||||
{"POST", "/pods", "create", "", api.NamespaceDefault, "pods", "Pod", "", []string{"pods"}},
|
{"POST", "/pods", "create", "", api.NamespaceDefault, "pods", "", "Pod", "", []string{"pods"}},
|
||||||
{"GET", "/pods/foo", "get", "", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/pods/foo", "get", "", api.NamespaceDefault, "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/pods/foo?namespace=other", "get", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/pods/foo?namespace=other", "get", "", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/pods?namespace=other", "list", "", "other", "pods", "Pod", "", []string{"pods"}},
|
{"GET", "/pods?namespace=other", "list", "", "other", "pods", "", "Pod", "", []string{"pods"}},
|
||||||
|
|
||||||
// special verbs
|
// special verbs
|
||||||
{"GET", "/proxy/namespaces/other/pods/foo", "proxy", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/proxy/namespaces/other/pods/foo", "proxy", "", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/proxy/pods/foo", "proxy", "", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/proxy/pods/foo", "proxy", "", api.NamespaceDefault, "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/redirect/namespaces/other/pods/foo", "redirect", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/redirect/namespaces/other/pods/foo", "redirect", "", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/redirect/pods/foo", "redirect", "", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/redirect/pods/foo", "redirect", "", api.NamespaceDefault, "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/watch/pods", "watch", "", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
|
{"GET", "/watch/pods", "watch", "", api.NamespaceAll, "pods", "", "Pod", "", []string{"pods"}},
|
||||||
{"GET", "/watch/namespaces/other/pods", "watch", "", "other", "pods", "Pod", "", []string{"pods"}},
|
{"GET", "/watch/namespaces/other/pods", "watch", "", "other", "pods", "", "Pod", "", []string{"pods"}},
|
||||||
|
|
||||||
// fully-qualified paths
|
// fully-qualified paths
|
||||||
{"GET", "/api/v1beta1/namespaces/other/pods", "list", "v1beta1", "other", "pods", "Pod", "", []string{"pods"}},
|
{"GET", "/api/v1beta1/namespaces/other/pods", "list", "v1beta1", "other", "pods", "", "Pod", "", []string{"pods"}},
|
||||||
{"GET", "/api/v1beta1/namespaces/other/pods/foo", "get", "v1beta1", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/api/v1beta1/namespaces/other/pods/foo", "get", "v1beta1", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/api/v1beta1/pods", "list", "v1beta1", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
|
{"GET", "/api/v1beta1/pods", "list", "v1beta1", api.NamespaceAll, "pods", "", "Pod", "", []string{"pods"}},
|
||||||
{"POST", "/api/v1beta1/pods", "create", "v1beta1", api.NamespaceDefault, "pods", "Pod", "", []string{"pods"}},
|
{"POST", "/api/v1beta1/pods", "create", "v1beta1", api.NamespaceDefault, "pods", "", "Pod", "", []string{"pods"}},
|
||||||
{"GET", "/api/v1beta1/pods/foo", "get", "v1beta1", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/api/v1beta1/pods/foo", "get", "v1beta1", api.NamespaceDefault, "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/api/v1beta1/pods/foo?namespace=other", "get", "v1beta1", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/api/v1beta1/pods/foo?namespace=other", "get", "v1beta1", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/api/v1beta1/pods?namespace=other", "list", "v1beta1", "other", "pods", "Pod", "", []string{"pods"}},
|
{"GET", "/api/v1beta1/pods?namespace=other", "list", "v1beta1", "other", "pods", "", "Pod", "", []string{"pods"}},
|
||||||
{"GET", "/api/v1beta1/proxy/pods/foo", "proxy", "v1beta1", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/api/v1beta1/proxy/pods/foo", "proxy", "v1beta1", api.NamespaceDefault, "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/api/v1beta1/redirect/pods/foo", "redirect", "v1beta1", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/api/v1beta1/redirect/pods/foo", "redirect", "v1beta1", api.NamespaceDefault, "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/api/v1beta1/watch/pods", "watch", "v1beta1", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
|
{"GET", "/api/v1beta1/watch/pods", "watch", "v1beta1", api.NamespaceAll, "pods", "", "Pod", "", []string{"pods"}},
|
||||||
{"GET", "/api/v1beta1/watch/namespaces/other/pods", "watch", "v1beta1", "other", "pods", "Pod", "", []string{"pods"}},
|
{"GET", "/api/v1beta1/watch/namespaces/other/pods", "watch", "v1beta1", "other", "pods", "", "Pod", "", []string{"pods"}},
|
||||||
|
|
||||||
|
// subresource identification
|
||||||
|
{"GET", "/namespaces/other/pods/foo/status", "get", "", "other", "pods", "status", "Pod", "foo", []string{"pods", "foo", "status"}},
|
||||||
|
{"PUT", "/namespaces/other/finalize", "update", "", "other", "finalize", "", "", "", []string{"finalize"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
apiRequestInfoResolver := &APIRequestInfoResolver{util.NewStringSet("api"), latest.RESTMapper}
|
apiRequestInfoResolver := &APIRequestInfoResolver{util.NewStringSet("api"), latest.RESTMapper}
|
||||||
@ -204,15 +209,15 @@ func TestGetAPIRequestInfo(t *testing.T) {
|
|||||||
if successCase.expectedResource != apiRequestInfo.Resource {
|
if successCase.expectedResource != apiRequestInfo.Resource {
|
||||||
t.Errorf("Unexpected resource for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedResource, apiRequestInfo.Resource)
|
t.Errorf("Unexpected resource for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedResource, apiRequestInfo.Resource)
|
||||||
}
|
}
|
||||||
|
if successCase.expectedSubresource != apiRequestInfo.Subresource {
|
||||||
|
t.Errorf("Unexpected resource for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedSubresource, apiRequestInfo.Subresource)
|
||||||
|
}
|
||||||
if successCase.expectedName != apiRequestInfo.Name {
|
if successCase.expectedName != apiRequestInfo.Name {
|
||||||
t.Errorf("Unexpected name for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedName, apiRequestInfo.Name)
|
t.Errorf("Unexpected name for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedName, apiRequestInfo.Name)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(successCase.expectedParts, apiRequestInfo.Parts) {
|
if !reflect.DeepEqual(successCase.expectedParts, apiRequestInfo.Parts) {
|
||||||
t.Errorf("Unexpected parts for url: %s, expected: %v, actual: %v", successCase.url, successCase.expectedParts, apiRequestInfo.Parts)
|
t.Errorf("Unexpected parts for url: %s, expected: %v, actual: %v", successCase.url, successCase.expectedParts, apiRequestInfo.Parts)
|
||||||
}
|
}
|
||||||
if e, a := strings.Split(successCase.url, "?")[0], apiRequestInfo.URLPath(); e != a {
|
|
||||||
t.Errorf("Expected %v, got %v", e, a)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
errorCases := map[string]string{
|
errorCases := map[string]string{
|
||||||
|
Loading…
Reference in New Issue
Block a user