mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-05 02:09:56 +00:00
Merge pull request #14375 from deads2k/remove-unprefixed-access
Auto commit by PR queue bot
This commit is contained in:
commit
7178921a96
@ -42,7 +42,6 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/util"
|
"k8s.io/kubernetes/pkg/util"
|
||||||
"k8s.io/kubernetes/pkg/util/errors"
|
"k8s.io/kubernetes/pkg/util/errors"
|
||||||
"k8s.io/kubernetes/pkg/util/flushwriter"
|
"k8s.io/kubernetes/pkg/util/flushwriter"
|
||||||
"k8s.io/kubernetes/pkg/util/sets"
|
|
||||||
"k8s.io/kubernetes/pkg/version"
|
"k8s.io/kubernetes/pkg/version"
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
"github.com/emicklei/go-restful"
|
||||||
@ -81,6 +80,10 @@ type APIGroupVersion struct {
|
|||||||
Root string
|
Root string
|
||||||
Version string
|
Version string
|
||||||
|
|
||||||
|
// APIRequestInfoResolver is used to parse URLs for the legacy proxy handler. Don't use this for anything else
|
||||||
|
// TODO: refactor proxy handler to use sub resources
|
||||||
|
APIRequestInfoResolver *APIRequestInfoResolver
|
||||||
|
|
||||||
// ServerVersion controls the Kubernetes APIVersion used for common objects in the apiserver
|
// ServerVersion controls the Kubernetes APIVersion used for common objects in the apiserver
|
||||||
// schema like api.Status, api.DeleteOptions, and api.ListOptions. Other implementors may
|
// schema like api.Status, api.DeleteOptions, and api.ListOptions. Other implementors may
|
||||||
// define a version "v1beta1" but want to use the Kubernetes "v1" internal objects. If
|
// define a version "v1beta1" but want to use the Kubernetes "v1" internal objects. If
|
||||||
@ -151,12 +154,10 @@ func (g *APIGroupVersion) UpdateREST(container *restful.Container) error {
|
|||||||
|
|
||||||
// newInstaller is a helper to create the installer. Used by InstallREST and UpdateREST.
|
// newInstaller is a helper to create the installer. Used by InstallREST and UpdateREST.
|
||||||
func (g *APIGroupVersion) newInstaller() *APIInstaller {
|
func (g *APIGroupVersion) newInstaller() *APIInstaller {
|
||||||
info := &APIRequestInfoResolver{sets.NewString(strings.TrimPrefix(g.Root, "/")), g.Mapper}
|
|
||||||
|
|
||||||
prefix := path.Join(g.Root, g.Version)
|
prefix := path.Join(g.Root, g.Version)
|
||||||
installer := &APIInstaller{
|
installer := &APIInstaller{
|
||||||
group: g,
|
group: g,
|
||||||
info: info,
|
info: g.APIRequestInfoResolver,
|
||||||
prefix: prefix,
|
prefix: prefix,
|
||||||
minRequestTimeout: g.MinRequestTimeout,
|
minRequestTimeout: g.MinRequestTimeout,
|
||||||
proxyDialerFn: g.ProxyDialerFn,
|
proxyDialerFn: g.ProxyDialerFn,
|
||||||
|
@ -42,6 +42,7 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/labels"
|
"k8s.io/kubernetes/pkg/labels"
|
||||||
"k8s.io/kubernetes/pkg/runtime"
|
"k8s.io/kubernetes/pkg/runtime"
|
||||||
"k8s.io/kubernetes/pkg/util"
|
"k8s.io/kubernetes/pkg/util"
|
||||||
|
"k8s.io/kubernetes/pkg/util/sets"
|
||||||
"k8s.io/kubernetes/pkg/version"
|
"k8s.io/kubernetes/pkg/version"
|
||||||
"k8s.io/kubernetes/pkg/watch"
|
"k8s.io/kubernetes/pkg/watch"
|
||||||
"k8s.io/kubernetes/plugin/pkg/admission/admit"
|
"k8s.io/kubernetes/plugin/pkg/admission/admit"
|
||||||
@ -190,11 +191,16 @@ func handleLinker(storage map[string]rest.Storage, selfLinker runtime.SelfLinker
|
|||||||
return handleInternal(true, storage, admissionControl, selfLinker)
|
return handleInternal(true, storage, admissionControl, selfLinker)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newTestAPIRequestInfoResolver() *APIRequestInfoResolver {
|
||||||
|
return &APIRequestInfoResolver{sets.NewString("api", "apis"), sets.NewString("api")}
|
||||||
|
}
|
||||||
|
|
||||||
func handleInternal(legacy bool, storage map[string]rest.Storage, admissionControl admission.Interface, selfLinker runtime.SelfLinker) http.Handler {
|
func handleInternal(legacy bool, storage map[string]rest.Storage, admissionControl admission.Interface, selfLinker runtime.SelfLinker) http.Handler {
|
||||||
group := &APIGroupVersion{
|
group := &APIGroupVersion{
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
|
|
||||||
Root: "/api",
|
Root: "/api",
|
||||||
|
APIRequestInfoResolver: newTestAPIRequestInfoResolver(),
|
||||||
|
|
||||||
Creater: api.Scheme,
|
Creater: api.Scheme,
|
||||||
Convertor: api.Scheme,
|
Convertor: api.Scheme,
|
||||||
@ -2016,6 +2022,7 @@ func TestUpdateREST(t *testing.T) {
|
|||||||
return &APIGroupVersion{
|
return &APIGroupVersion{
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
Root: "/api",
|
Root: "/api",
|
||||||
|
APIRequestInfoResolver: newTestAPIRequestInfoResolver(),
|
||||||
Creater: api.Scheme,
|
Creater: api.Scheme,
|
||||||
Convertor: api.Scheme,
|
Convertor: api.Scheme,
|
||||||
Typer: api.Scheme,
|
Typer: api.Scheme,
|
||||||
@ -2097,6 +2104,7 @@ func TestParentResourceIsRequired(t *testing.T) {
|
|||||||
"simple/sub": storage,
|
"simple/sub": storage,
|
||||||
},
|
},
|
||||||
Root: "/api",
|
Root: "/api",
|
||||||
|
APIRequestInfoResolver: newTestAPIRequestInfoResolver(),
|
||||||
Creater: api.Scheme,
|
Creater: api.Scheme,
|
||||||
Convertor: api.Scheme,
|
Convertor: api.Scheme,
|
||||||
Typer: api.Scheme,
|
Typer: api.Scheme,
|
||||||
@ -2125,6 +2133,7 @@ func TestParentResourceIsRequired(t *testing.T) {
|
|||||||
"simple/sub": storage,
|
"simple/sub": storage,
|
||||||
},
|
},
|
||||||
Root: "/api",
|
Root: "/api",
|
||||||
|
APIRequestInfoResolver: newTestAPIRequestInfoResolver(),
|
||||||
Creater: api.Scheme,
|
Creater: api.Scheme,
|
||||||
Convertor: api.Scheme,
|
Convertor: api.Scheme,
|
||||||
Typer: api.Scheme,
|
Typer: api.Scheme,
|
||||||
|
@ -86,3 +86,22 @@ func forbidden(w http.ResponseWriter, req *http.Request) {
|
|||||||
w.WriteHeader(http.StatusForbidden)
|
w.WriteHeader(http.StatusForbidden)
|
||||||
fmt.Fprintf(w, "Forbidden: %#v", req.RequestURI)
|
fmt.Fprintf(w, "Forbidden: %#v", req.RequestURI)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// errAPIPrefixNotFound indicates that a APIRequestInfo resolution failed because the request isn't under
|
||||||
|
// any known API prefixes
|
||||||
|
type errAPIPrefixNotFound struct {
|
||||||
|
SpecifiedPrefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *errAPIPrefixNotFound) Error() string {
|
||||||
|
return fmt.Sprintf("no valid API prefix found matching %v", e.SpecifiedPrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsAPIPrefixNotFound(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := err.(*errAPIPrefixNotFound)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
@ -31,7 +31,6 @@ import (
|
|||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/errors"
|
"k8s.io/kubernetes/pkg/api/errors"
|
||||||
"k8s.io/kubernetes/pkg/api/meta"
|
|
||||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||||
"k8s.io/kubernetes/pkg/httplog"
|
"k8s.io/kubernetes/pkg/httplog"
|
||||||
"k8s.io/kubernetes/pkg/util/sets"
|
"k8s.io/kubernetes/pkg/util/sets"
|
||||||
@ -348,8 +347,8 @@ type requestAttributeGetter struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewAttributeGetter returns an object which implements the RequestAttributeGetter interface.
|
// NewAttributeGetter returns an object which implements the RequestAttributeGetter interface.
|
||||||
func NewRequestAttributeGetter(requestContextMapper api.RequestContextMapper, restMapper meta.RESTMapper, apiRoots ...string) RequestAttributeGetter {
|
func NewRequestAttributeGetter(requestContextMapper api.RequestContextMapper, apiRequestInfoResolver *APIRequestInfoResolver) RequestAttributeGetter {
|
||||||
return &requestAttributeGetter{requestContextMapper, &APIRequestInfoResolver{sets.NewString(apiRoots...), restMapper}}
|
return &requestAttributeGetter{requestContextMapper, apiRequestInfoResolver}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attributes {
|
func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attributes {
|
||||||
@ -367,6 +366,8 @@ func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attrib
|
|||||||
|
|
||||||
apiRequestInfo, _ := r.apiRequestInfoResolver.GetAPIRequestInfo(req)
|
apiRequestInfo, _ := r.apiRequestInfoResolver.GetAPIRequestInfo(req)
|
||||||
|
|
||||||
|
attribs.APIGroup = apiRequestInfo.APIGroup
|
||||||
|
|
||||||
// If a path follows the conventions of the REST object store, then
|
// If a path follows the conventions of the REST object store, then
|
||||||
// we can extract the resource. Otherwise, not.
|
// we can extract the resource. Otherwise, not.
|
||||||
attribs.Resource = apiRequestInfo.Resource
|
attribs.Resource = apiRequestInfo.Resource
|
||||||
@ -395,6 +396,8 @@ func WithAuthorizationCheck(handler http.Handler, getAttribs RequestAttributeGet
|
|||||||
type APIRequestInfo struct {
|
type APIRequestInfo struct {
|
||||||
// Verb is the kube verb associated with the request, not the http verb. This includes things like list and watch.
|
// Verb is the kube verb associated with the request, not the http verb. This includes things like list and watch.
|
||||||
Verb string
|
Verb string
|
||||||
|
APIPrefix string
|
||||||
|
APIGroup string
|
||||||
APIVersion string
|
APIVersion string
|
||||||
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
|
||||||
@ -403,8 +406,6 @@ type APIRequestInfo struct {
|
|||||||
// 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"
|
// 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".
|
// (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
|
Subresource string
|
||||||
// Kind is the type of object being manipulated. For example: Pod
|
|
||||||
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.
|
||||||
Name string
|
Name string
|
||||||
// Parts are the path parts for the request, always starting with /{resource}/{name}
|
// Parts are the path parts for the request, always starting with /{resource}/{name}
|
||||||
@ -416,65 +417,66 @@ type APIRequestInfo struct {
|
|||||||
|
|
||||||
type APIRequestInfoResolver struct {
|
type APIRequestInfoResolver struct {
|
||||||
APIPrefixes sets.String
|
APIPrefixes sets.String
|
||||||
RestMapper meta.RESTMapper
|
GrouplessAPIPrefixes sets.String
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO write an integration test against the swagger doc to test the APIRequestInfo and match up behavior to responses
|
// 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
|
||||||
// /namespaces
|
// /apis/{api-group}/{version}/namespaces
|
||||||
// /namespaces/{namespace}
|
// /api/{version}/namespaces
|
||||||
// /namespaces/{namespace}/{resource}
|
// /api/{version}/namespaces/{namespace}
|
||||||
// /namespaces/{namespace}/{resource}/{resourceName}
|
// /api/{version}/namespaces/{namespace}/{resource}
|
||||||
// /{resource}
|
// /api/{version}/namespaces/{namespace}/{resource}/{resourceName}
|
||||||
// /{resource}/{resourceName}
|
// /api/{version}/{resource}
|
||||||
|
// /api/{version}/{resource}/{resourceName}
|
||||||
//
|
//
|
||||||
// Special verbs:
|
// Special verbs:
|
||||||
// /proxy/{resource}/{resourceName}
|
// /api/{version}/proxy/{resource}/{resourceName}
|
||||||
// /proxy/namespaces/{namespace}/{resource}/{resourceName}
|
// /api/{version}/proxy/namespaces/{namespace}/{resource}/{resourceName}
|
||||||
// /redirect/namespaces/{namespace}/{resource}/{resourceName}
|
// /api/{version}/redirect/namespaces/{namespace}/{resource}/{resourceName}
|
||||||
// /redirect/{resource}/{resourceName}
|
// /api/{version}/redirect/{resource}/{resourceName}
|
||||||
// /watch/{resource}
|
// /api/{version}/watch/{resource}
|
||||||
// /watch/namespaces/{namespace}/{resource}
|
// /api/{version}/watch/namespaces/{namespace}/{resource}
|
||||||
//
|
|
||||||
// Fully qualified paths for above:
|
|
||||||
// /api/{version}/*
|
|
||||||
// /api/{version}/*
|
|
||||||
func (r *APIRequestInfoResolver) GetAPIRequestInfo(req *http.Request) (APIRequestInfo, error) {
|
func (r *APIRequestInfoResolver) GetAPIRequestInfo(req *http.Request) (APIRequestInfo, error) {
|
||||||
requestInfo := APIRequestInfo{
|
requestInfo := APIRequestInfo{
|
||||||
Raw: splitPath(req.URL.Path),
|
Raw: splitPath(req.URL.Path),
|
||||||
}
|
}
|
||||||
|
|
||||||
currentParts := requestInfo.Raw
|
currentParts := requestInfo.Raw
|
||||||
if len(currentParts) < 1 {
|
if len(currentParts) < 3 {
|
||||||
return requestInfo, fmt.Errorf("Unable to determine kind and namespace from an empty URL path")
|
return requestInfo, fmt.Errorf("a resource request must have a url with at least three parts, not %v", req.URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, currPrefix := range r.APIPrefixes.List() {
|
if !r.APIPrefixes.Has(currentParts[0]) {
|
||||||
// handle input of form /api/{version}/* by adjusting special paths
|
return requestInfo, &errAPIPrefixNotFound{currentParts[0]}
|
||||||
if currentParts[0] == currPrefix {
|
}
|
||||||
if len(currentParts) > 1 {
|
requestInfo.APIPrefix = currentParts[0]
|
||||||
requestInfo.APIVersion = currentParts[1]
|
currentParts = currentParts[1:]
|
||||||
|
|
||||||
|
if !r.GrouplessAPIPrefixes.Has(requestInfo.APIPrefix) {
|
||||||
|
// one part (APIPrefix) has already been consumed, so this is actually "do we have four parts?"
|
||||||
|
if len(currentParts) < 3 {
|
||||||
|
return requestInfo, fmt.Errorf("a resource request with an API group must have a url with at least four parts, not %v", req.URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(currentParts) > 2 {
|
requestInfo.APIGroup = currentParts[0]
|
||||||
currentParts = currentParts[2:]
|
currentParts = currentParts[1:]
|
||||||
} else {
|
|
||||||
return requestInfo, fmt.Errorf("Unable to determine kind and namespace from url, %v", req.URL)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestInfo.APIVersion = currentParts[0]
|
||||||
|
currentParts = currentParts[1:]
|
||||||
|
|
||||||
// handle input of form /{specialVerb}/*
|
// handle input of form /{specialVerb}/*
|
||||||
if _, ok := specialVerbs[currentParts[0]]; ok {
|
if _, ok := specialVerbs[currentParts[0]]; ok {
|
||||||
requestInfo.Verb = currentParts[0]
|
if len(currentParts) < 2 {
|
||||||
|
return requestInfo, fmt.Errorf("unable to determine kind and namespace from url, %v", req.URL)
|
||||||
if len(currentParts) > 1 {
|
|
||||||
currentParts = currentParts[1:]
|
|
||||||
} else {
|
|
||||||
return requestInfo, fmt.Errorf("Unable to determine kind and namespace from url, %v", req.URL)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestInfo.Verb = currentParts[0]
|
||||||
|
currentParts = currentParts[1:]
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
case "POST":
|
case "POST":
|
||||||
@ -528,10 +530,5 @@ func (r *APIRequestInfoResolver) GetAPIRequestInfo(req *http.Request) (APIReques
|
|||||||
requestInfo.Verb = "list"
|
requestInfo.Verb = "list"
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we have a resource, we have a good shot at being able to determine kind
|
|
||||||
if len(requestInfo.Resource) > 0 {
|
|
||||||
_, requestInfo.Kind, _ = r.RestMapper.VersionAndKindForResource(requestInfo.Resource)
|
|
||||||
}
|
|
||||||
|
|
||||||
return requestInfo, nil
|
return requestInfo, nil
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,6 @@ import (
|
|||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/errors"
|
"k8s.io/kubernetes/pkg/api/errors"
|
||||||
"k8s.io/kubernetes/pkg/api/testapi"
|
"k8s.io/kubernetes/pkg/api/testapi"
|
||||||
"k8s.io/kubernetes/pkg/util/sets"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeRL bool
|
type fakeRL bool
|
||||||
@ -199,52 +198,49 @@ func TestGetAPIRequestInfo(t *testing.T) {
|
|||||||
method string
|
method string
|
||||||
url string
|
url string
|
||||||
expectedVerb string
|
expectedVerb string
|
||||||
|
expectedAPIPrefix string
|
||||||
|
expectedAPIGroup string
|
||||||
expectedAPIVersion string
|
expectedAPIVersion string
|
||||||
expectedNamespace string
|
expectedNamespace string
|
||||||
expectedResource string
|
expectedResource string
|
||||||
expectedSubresource string
|
expectedSubresource string
|
||||||
expectedKind string
|
|
||||||
expectedName string
|
expectedName string
|
||||||
expectedParts []string
|
expectedParts []string
|
||||||
}{
|
}{
|
||||||
|
|
||||||
// resource paths
|
// resource paths
|
||||||
{"GET", "/namespaces", "list", "", "", "namespaces", "", "Namespace", "", []string{"namespaces"}},
|
{"GET", "/api/v1/namespaces", "list", "api", "", "v1", "", "namespaces", "", "", []string{"namespaces"}},
|
||||||
{"GET", "/namespaces/other", "get", "", "other", "namespaces", "", "Namespace", "other", []string{"namespaces", "other"}},
|
{"GET", "/api/v1/namespaces/other", "get", "api", "", "v1", "other", "namespaces", "", "other", []string{"namespaces", "other"}},
|
||||||
|
|
||||||
{"GET", "/namespaces/other/pods", "list", "", "other", "pods", "", "Pod", "", []string{"pods"}},
|
{"GET", "/api/v1/namespaces/other/pods", "list", "api", "", "v1", "other", "pods", "", "", []string{"pods"}},
|
||||||
{"GET", "/namespaces/other/pods/foo", "get", "", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/api/v1/namespaces/other/pods/foo", "get", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/pods", "list", "", api.NamespaceAll, "pods", "", "Pod", "", []string{"pods"}},
|
{"GET", "/api/v1/pods", "list", "api", "", "v1", api.NamespaceAll, "pods", "", "", []string{"pods"}},
|
||||||
{"GET", "/namespaces/other/pods/foo", "get", "", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/api/v1/namespaces/other/pods/foo", "get", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/namespaces/other/pods", "list", "", "other", "pods", "", "Pod", "", []string{"pods"}},
|
{"GET", "/api/v1/namespaces/other/pods", "list", "api", "", "v1", "other", "pods", "", "", []string{"pods"}},
|
||||||
|
|
||||||
// special verbs
|
// special verbs
|
||||||
{"GET", "/proxy/namespaces/other/pods/foo", "proxy", "", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/api/v1/proxy/namespaces/other/pods/foo", "proxy", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/redirect/namespaces/other/pods/foo", "redirect", "", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
{"GET", "/api/v1/redirect/namespaces/other/pods/foo", "redirect", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
|
||||||
{"GET", "/watch/pods", "watch", "", api.NamespaceAll, "pods", "", "Pod", "", []string{"pods"}},
|
{"GET", "/api/v1/watch/pods", "watch", "api", "", "v1", api.NamespaceAll, "pods", "", "", []string{"pods"}},
|
||||||
{"GET", "/watch/namespaces/other/pods", "watch", "", "other", "pods", "", "Pod", "", []string{"pods"}},
|
{"GET", "/api/v1/watch/namespaces/other/pods", "watch", "api", "", "v1", "other", "pods", "", "", []string{"pods"}},
|
||||||
|
|
||||||
// fully-qualified paths
|
|
||||||
{"GET", getPath("pods", "other", ""), "list", testapi.Default.Version(), "other", "pods", "", "Pod", "", []string{"pods"}},
|
|
||||||
{"GET", getPath("pods", "other", "foo"), "get", testapi.Default.Version(), "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
|
||||||
{"GET", getPath("pods", "", ""), "list", testapi.Default.Version(), api.NamespaceAll, "pods", "", "Pod", "", []string{"pods"}},
|
|
||||||
{"POST", getPath("pods", "", ""), "create", testapi.Default.Version(), api.NamespaceAll, "pods", "", "Pod", "", []string{"pods"}},
|
|
||||||
{"GET", getPath("pods", "", "foo"), "get", testapi.Default.Version(), api.NamespaceAll, "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
|
||||||
{"GET", pathWithPrefix("proxy", "pods", "", "foo"), "proxy", testapi.Default.Version(), api.NamespaceAll, "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
|
||||||
{"GET", pathWithPrefix("watch", "pods", "", ""), "watch", testapi.Default.Version(), api.NamespaceAll, "pods", "", "Pod", "", []string{"pods"}},
|
|
||||||
{"GET", pathWithPrefix("redirect", "pods", "", ""), "redirect", testapi.Default.Version(), api.NamespaceAll, "pods", "", "Pod", "", []string{"pods"}},
|
|
||||||
{"GET", pathWithPrefix("watch", "pods", "other", ""), "watch", testapi.Default.Version(), "other", "pods", "", "Pod", "", []string{"pods"}},
|
|
||||||
|
|
||||||
// subresource identification
|
// subresource identification
|
||||||
{"GET", "/namespaces/other/pods/foo/status", "get", "", "other", "pods", "status", "Pod", "foo", []string{"pods", "foo", "status"}},
|
{"GET", "/api/v1/namespaces/other/pods/foo/status", "get", "api", "", "v1", "other", "pods", "status", "foo", []string{"pods", "foo", "status"}},
|
||||||
{"PUT", "/namespaces/other/finalize", "update", "", "other", "finalize", "", "", "", []string{"finalize"}},
|
{"PUT", "/api/v1/namespaces/other/finalize", "update", "api", "", "v1", "other", "finalize", "", "", []string{"finalize"}},
|
||||||
|
|
||||||
// verb identification
|
// verb identification
|
||||||
{"PATCH", "/namespaces/other/pods/foo", "patch", "", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
{"PATCH", "/api/v1/namespaces/other/pods/foo", "patch", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
|
||||||
{"DELETE", "/namespaces/other/pods/foo", "delete", "", "other", "pods", "", "Pod", "foo", []string{"pods", "foo"}},
|
{"DELETE", "/api/v1/namespaces/other/pods/foo", "delete", "api", "", "v1", "other", "pods", "", "foo", []string{"pods", "foo"}},
|
||||||
|
{"POST", "/api/v1/namespaces/other/pods", "create", "api", "", "v1", "other", "pods", "", "", []string{"pods"}},
|
||||||
|
|
||||||
|
// api group identification
|
||||||
|
{"POST", "/apis/experimental/v1/namespaces/other/pods", "create", "api", "experimental", "v1", "other", "pods", "", "", []string{"pods"}},
|
||||||
|
|
||||||
|
// api version identification
|
||||||
|
{"POST", "/apis/experimental/v1beta3/namespaces/other/pods", "create", "api", "experimental", "v1beta3", "other", "pods", "", "", []string{"pods"}},
|
||||||
}
|
}
|
||||||
|
|
||||||
apiRequestInfoResolver := &APIRequestInfoResolver{sets.NewString("api"), testapi.Default.RESTMapper()}
|
apiRequestInfoResolver := newTestAPIRequestInfoResolver()
|
||||||
|
|
||||||
for _, successCase := range successCases {
|
for _, successCase := range successCases {
|
||||||
req, _ := http.NewRequest(successCase.method, successCase.url, nil)
|
req, _ := http.NewRequest(successCase.method, successCase.url, nil)
|
||||||
@ -262,9 +258,6 @@ func TestGetAPIRequestInfo(t *testing.T) {
|
|||||||
if successCase.expectedNamespace != apiRequestInfo.Namespace {
|
if successCase.expectedNamespace != apiRequestInfo.Namespace {
|
||||||
t.Errorf("Unexpected namespace for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedNamespace, apiRequestInfo.Namespace)
|
t.Errorf("Unexpected namespace for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedNamespace, apiRequestInfo.Namespace)
|
||||||
}
|
}
|
||||||
if successCase.expectedKind != apiRequestInfo.Kind {
|
|
||||||
t.Errorf("Unexpected kind for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedKind, apiRequestInfo.Kind)
|
|
||||||
}
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
@ -282,7 +275,10 @@ func TestGetAPIRequestInfo(t *testing.T) {
|
|||||||
errorCases := map[string]string{
|
errorCases := map[string]string{
|
||||||
"no resource path": "/",
|
"no resource path": "/",
|
||||||
"just apiversion": "/api/version/",
|
"just apiversion": "/api/version/",
|
||||||
|
"just prefix, group, version": "/apis/group/version/",
|
||||||
"apiversion with no resource": "/api/version/",
|
"apiversion with no resource": "/api/version/",
|
||||||
|
"bad prefix": "/badprefix/version/resource",
|
||||||
|
"missing api group": "/apis/version/resource",
|
||||||
}
|
}
|
||||||
for k, v := range errorCases {
|
for k, v := range errorCases {
|
||||||
req, err := http.NewRequest("GET", v, nil)
|
req, err := http.NewRequest("GET", v, nil)
|
||||||
|
@ -41,6 +41,9 @@ type Attributes interface {
|
|||||||
|
|
||||||
// The kind of object, if a request is for a REST object.
|
// The kind of object, if a request is for a REST object.
|
||||||
GetResource() string
|
GetResource() string
|
||||||
|
|
||||||
|
// The group of the resource, if a request is for a REST object.
|
||||||
|
GetAPIGroup() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authorizer makes an authorization decision based on information gained by making
|
// Authorizer makes an authorization decision based on information gained by making
|
||||||
@ -61,6 +64,7 @@ type AttributesRecord struct {
|
|||||||
User user.Info
|
User user.Info
|
||||||
ReadOnly bool
|
ReadOnly bool
|
||||||
Namespace string
|
Namespace string
|
||||||
|
APIGroup string
|
||||||
Resource string
|
Resource string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,3 +87,7 @@ func (a AttributesRecord) GetNamespace() string {
|
|||||||
func (a AttributesRecord) GetResource() string {
|
func (a AttributesRecord) GetResource() string {
|
||||||
return a.Resource
|
return a.Resource
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetAPIGroup() string {
|
||||||
|
return a.APIGroup
|
||||||
|
}
|
||||||
|
@ -572,9 +572,7 @@ func (m *Master) init(c *Config) {
|
|||||||
|
|
||||||
apiserver.InstallSupport(m.muxHelper, m.rootWebService, c.EnableProfiling, healthzChecks...)
|
apiserver.InstallSupport(m.muxHelper, m.rootWebService, c.EnableProfiling, healthzChecks...)
|
||||||
apiserver.AddApiWebService(m.handlerContainer, c.APIPrefix, apiVersions)
|
apiserver.AddApiWebService(m.handlerContainer, c.APIPrefix, apiVersions)
|
||||||
defaultVersion := m.defaultAPIGroupVersion()
|
apiserver.InstallServiceErrorHandler(m.handlerContainer, m.newAPIRequestInfoResolver(), apiVersions)
|
||||||
requestInfoResolver := &apiserver.APIRequestInfoResolver{APIPrefixes: sets.NewString(strings.TrimPrefix(defaultVersion.Root, "/")), RestMapper: defaultVersion.Mapper}
|
|
||||||
apiserver.InstallServiceErrorHandler(m.handlerContainer, requestInfoResolver, apiVersions)
|
|
||||||
|
|
||||||
// allGroups records all supported groups at /apis
|
// allGroups records all supported groups at /apis
|
||||||
allGroups := []api.APIGroup{}
|
allGroups := []api.APIGroup{}
|
||||||
@ -608,8 +606,7 @@ func (m *Master) init(c *Config) {
|
|||||||
}
|
}
|
||||||
apiserver.AddGroupWebService(m.handlerContainer, c.APIGroupPrefix+"/"+latest.GroupOrDie("experimental").Group+"/", group)
|
apiserver.AddGroupWebService(m.handlerContainer, c.APIGroupPrefix+"/"+latest.GroupOrDie("experimental").Group+"/", group)
|
||||||
allGroups = append(allGroups, group)
|
allGroups = append(allGroups, group)
|
||||||
expRequestInfoResolver := &apiserver.APIRequestInfoResolver{APIPrefixes: sets.NewString(strings.TrimPrefix(expVersion.Root, "/")), RestMapper: expVersion.Mapper}
|
apiserver.InstallServiceErrorHandler(m.handlerContainer, m.newAPIRequestInfoResolver(), []string{expVersion.Version})
|
||||||
apiserver.InstallServiceErrorHandler(m.handlerContainer, expRequestInfoResolver, []string{expVersion.Version})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This should be done after all groups are registered
|
// This should be done after all groups are registered
|
||||||
@ -652,7 +649,7 @@ func (m *Master) init(c *Config) {
|
|||||||
|
|
||||||
m.InsecureHandler = handler
|
m.InsecureHandler = handler
|
||||||
|
|
||||||
attributeGetter := apiserver.NewRequestAttributeGetter(m.requestContextMapper, latest.GroupOrDie("").RESTMapper, "api")
|
attributeGetter := apiserver.NewRequestAttributeGetter(m.requestContextMapper, m.newAPIRequestInfoResolver())
|
||||||
handler = apiserver.WithAuthorizationCheck(handler, attributeGetter, m.authorizer)
|
handler = apiserver.WithAuthorizationCheck(handler, attributeGetter, m.authorizer)
|
||||||
|
|
||||||
// Install Authenticator
|
// Install Authenticator
|
||||||
@ -778,9 +775,17 @@ func (m *Master) getServersToValidate(c *Config) map[string]apiserver.Server {
|
|||||||
return serversToValidate
|
return serversToValidate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Master) newAPIRequestInfoResolver() *apiserver.APIRequestInfoResolver {
|
||||||
|
return &apiserver.APIRequestInfoResolver{
|
||||||
|
sets.NewString(strings.Trim(m.apiPrefix, "/"), strings.Trim(thirdpartyprefix, "/")), // all possible API prefixes
|
||||||
|
sets.NewString(strings.Trim(m.apiPrefix, "/")), // APIPrefixes that won't have groups (legacy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Master) defaultAPIGroupVersion() *apiserver.APIGroupVersion {
|
func (m *Master) defaultAPIGroupVersion() *apiserver.APIGroupVersion {
|
||||||
return &apiserver.APIGroupVersion{
|
return &apiserver.APIGroupVersion{
|
||||||
Root: m.apiPrefix,
|
Root: m.apiPrefix,
|
||||||
|
APIRequestInfoResolver: m.newAPIRequestInfoResolver(),
|
||||||
|
|
||||||
Mapper: latest.GroupOrDie("").RESTMapper,
|
Mapper: latest.GroupOrDie("").RESTMapper,
|
||||||
|
|
||||||
@ -918,8 +923,7 @@ func (m *Master) InstallThirdPartyResource(rsrc *expapi.ThirdPartyResource) erro
|
|||||||
}
|
}
|
||||||
apiserver.AddGroupWebService(m.handlerContainer, path, apiGroup)
|
apiserver.AddGroupWebService(m.handlerContainer, path, apiGroup)
|
||||||
m.addThirdPartyResourceStorage(path, thirdparty.Storage[strings.ToLower(kind)+"s"].(*thirdpartyresourcedataetcd.REST))
|
m.addThirdPartyResourceStorage(path, thirdparty.Storage[strings.ToLower(kind)+"s"].(*thirdpartyresourcedataetcd.REST))
|
||||||
thirdPartyRequestInfoResolver := &apiserver.APIRequestInfoResolver{APIPrefixes: sets.NewString(strings.TrimPrefix(group, "/")), RestMapper: thirdparty.Mapper}
|
apiserver.InstallServiceErrorHandler(m.handlerContainer, m.newAPIRequestInfoResolver(), []string{thirdparty.Version})
|
||||||
apiserver.InstallServiceErrorHandler(m.handlerContainer, thirdPartyRequestInfoResolver, []string{thirdparty.Version})
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -934,6 +938,8 @@ func (m *Master) thirdpartyapi(group, kind, version string) *apiserver.APIGroupV
|
|||||||
|
|
||||||
return &apiserver.APIGroupVersion{
|
return &apiserver.APIGroupVersion{
|
||||||
Root: apiRoot,
|
Root: apiRoot,
|
||||||
|
Version: version,
|
||||||
|
APIRequestInfoResolver: m.newAPIRequestInfoResolver(),
|
||||||
|
|
||||||
Creater: thirdpartyresourcedata.NewObjectCreator(version, api.Scheme),
|
Creater: thirdpartyresourcedata.NewObjectCreator(version, api.Scheme),
|
||||||
Convertor: api.Scheme,
|
Convertor: api.Scheme,
|
||||||
@ -943,7 +949,6 @@ func (m *Master) thirdpartyapi(group, kind, version string) *apiserver.APIGroupV
|
|||||||
Codec: thirdpartyresourcedata.NewCodec(latest.GroupOrDie("experimental").Codec, kind),
|
Codec: thirdpartyresourcedata.NewCodec(latest.GroupOrDie("experimental").Codec, kind),
|
||||||
Linker: latest.GroupOrDie("experimental").SelfLinker,
|
Linker: latest.GroupOrDie("experimental").SelfLinker,
|
||||||
Storage: storage,
|
Storage: storage,
|
||||||
Version: version,
|
|
||||||
ServerVersion: latest.GroupOrDie("").GroupVersion,
|
ServerVersion: latest.GroupOrDie("").GroupVersion,
|
||||||
|
|
||||||
Context: m.requestContextMapper,
|
Context: m.requestContextMapper,
|
||||||
@ -990,6 +995,7 @@ func (m *Master) experimental(c *Config) *apiserver.APIGroupVersion {
|
|||||||
|
|
||||||
return &apiserver.APIGroupVersion{
|
return &apiserver.APIGroupVersion{
|
||||||
Root: m.apiGroupPrefix,
|
Root: m.apiGroupPrefix,
|
||||||
|
APIRequestInfoResolver: m.newAPIRequestInfoResolver(),
|
||||||
|
|
||||||
Creater: api.Scheme,
|
Creater: api.Scheme,
|
||||||
Convertor: api.Scheme,
|
Convertor: api.Scheme,
|
||||||
|
@ -792,6 +792,94 @@ func newAuthorizerWithContents(t *testing.T, contents string) authorizer.Authori
|
|||||||
return pl
|
return pl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type trackingAuthorizer struct {
|
||||||
|
requestAttributes []authorizer.Attributes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *trackingAuthorizer) Authorize(attributes authorizer.Attributes) error {
|
||||||
|
a.requestAttributes = append(a.requestAttributes, attributes)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAuthorizationAttributeDetermination tests that authorization attributes are built correctly
|
||||||
|
func TestAuthorizationAttributeDetermination(t *testing.T) {
|
||||||
|
framework.DeleteAllEtcdKeys()
|
||||||
|
|
||||||
|
etcdStorage, err := framework.NewEtcdStorage()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
trackingAuthorizer := &trackingAuthorizer{}
|
||||||
|
|
||||||
|
var m *master.Master
|
||||||
|
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
m.Handler.ServeHTTP(w, req)
|
||||||
|
}))
|
||||||
|
defer s.Close()
|
||||||
|
|
||||||
|
m = master.New(&master.Config{
|
||||||
|
DatabaseStorage: etcdStorage,
|
||||||
|
KubeletClient: client.FakeKubeletClient{},
|
||||||
|
EnableCoreControllers: true,
|
||||||
|
EnableLogsSupport: false,
|
||||||
|
EnableUISupport: false,
|
||||||
|
EnableIndex: true,
|
||||||
|
APIPrefix: "/api",
|
||||||
|
Authenticator: getTestTokenAuth(),
|
||||||
|
Authorizer: trackingAuthorizer,
|
||||||
|
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||||
|
StorageVersions: map[string]string{"": testapi.Default.Version()},
|
||||||
|
})
|
||||||
|
|
||||||
|
transport := http.DefaultTransport
|
||||||
|
|
||||||
|
requests := map[string]struct {
|
||||||
|
verb string
|
||||||
|
URL string
|
||||||
|
expectedAttributes authorizer.Attributes
|
||||||
|
}{
|
||||||
|
"prefix/version/resource": {"GET", "/api/v1/pods", authorizer.AttributesRecord{APIGroup: "", Resource: "pods"}},
|
||||||
|
"prefix/group/version/resource": {"GET", "/apis/experimental/v1/pods", authorizer.AttributesRecord{APIGroup: "experimental", Resource: "pods"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
currentAuthorizationAttributesIndex := 0
|
||||||
|
|
||||||
|
for testName, r := range requests {
|
||||||
|
token := BobToken
|
||||||
|
req, err := http.NewRequest(r.verb, s.URL+r.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("case %v", testName)
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
|
||||||
|
func() {
|
||||||
|
resp, err := transport.RoundTrip(req)
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Logf("case %v", r)
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for i := currentAuthorizationAttributesIndex; i < len(trackingAuthorizer.requestAttributes); i++ {
|
||||||
|
if trackingAuthorizer.requestAttributes[i].GetAPIGroup() == r.expectedAttributes.GetAPIGroup() &&
|
||||||
|
trackingAuthorizer.requestAttributes[i].GetResource() == r.expectedAttributes.GetResource() {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("%#v did not match %#v", r.expectedAttributes, trackingAuthorizer.requestAttributes[i].(*authorizer.AttributesRecord))
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
t.Errorf("did not find %#v in %#v", r.expectedAttributes, trackingAuthorizer.requestAttributes[currentAuthorizationAttributesIndex:])
|
||||||
|
}
|
||||||
|
|
||||||
|
currentAuthorizationAttributesIndex = len(trackingAuthorizer.requestAttributes)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestNamespaceAuthorization tests that authorization can be controlled
|
// TestNamespaceAuthorization tests that authorization can be controlled
|
||||||
// by namespace.
|
// by namespace.
|
||||||
func TestNamespaceAuthorization(t *testing.T) {
|
func TestNamespaceAuthorization(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user