Merge pull request #14375 from deads2k/remove-unprefixed-access

Auto commit by PR queue bot
This commit is contained in:
k8s-merge-robot 2015-10-01 04:35:59 -07:00
commit 7178921a96
8 changed files with 233 additions and 109 deletions

View File

@ -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,

View File

@ -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,
@ -2014,12 +2020,13 @@ func TestCreateChecksDecode(t *testing.T) {
func TestUpdateREST(t *testing.T) { func TestUpdateREST(t *testing.T) {
makeGroup := func(storage map[string]rest.Storage) *APIGroupVersion { makeGroup := func(storage map[string]rest.Storage) *APIGroupVersion {
return &APIGroupVersion{ return &APIGroupVersion{
Storage: storage, Storage: storage,
Root: "/api", Root: "/api",
Creater: api.Scheme, APIRequestInfoResolver: newTestAPIRequestInfoResolver(),
Convertor: api.Scheme, Creater: api.Scheme,
Typer: api.Scheme, Convertor: api.Scheme,
Linker: selfLinker, Typer: api.Scheme,
Linker: selfLinker,
Admit: admissionControl, Admit: admissionControl,
Context: requestContextMapper, Context: requestContextMapper,
@ -2096,11 +2103,12 @@ func TestParentResourceIsRequired(t *testing.T) {
Storage: map[string]rest.Storage{ Storage: map[string]rest.Storage{
"simple/sub": storage, "simple/sub": storage,
}, },
Root: "/api", Root: "/api",
Creater: api.Scheme, APIRequestInfoResolver: newTestAPIRequestInfoResolver(),
Convertor: api.Scheme, Creater: api.Scheme,
Typer: api.Scheme, Convertor: api.Scheme,
Linker: selfLinker, Typer: api.Scheme,
Linker: selfLinker,
Admit: admissionControl, Admit: admissionControl,
Context: requestContextMapper, Context: requestContextMapper,
@ -2124,11 +2132,12 @@ func TestParentResourceIsRequired(t *testing.T) {
"simple": &SimpleRESTStorage{}, "simple": &SimpleRESTStorage{},
"simple/sub": storage, "simple/sub": storage,
}, },
Root: "/api", Root: "/api",
Creater: api.Scheme, APIRequestInfoResolver: newTestAPIRequestInfoResolver(),
Convertor: api.Scheme, Creater: api.Scheme,
Typer: api.Scheme, Convertor: api.Scheme,
Linker: selfLinker, Typer: api.Scheme,
Linker: selfLinker,
Admit: admissionControl, Admit: admissionControl,
Context: requestContextMapper, Context: requestContextMapper,

View File

@ -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
}

View File

@ -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}
@ -415,66 +416,67 @@ 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 len(currentParts) > 2 { if !r.GrouplessAPIPrefixes.Has(requestInfo.APIPrefix) {
currentParts = currentParts[2:] // one part (APIPrefix) has already been consumed, so this is actually "do we have four parts?"
} else { if len(currentParts) < 3 {
return requestInfo, fmt.Errorf("Unable to determine kind and namespace from url, %v", req.URL) return requestInfo, fmt.Errorf("a resource request with an API group must have a url with at least four parts, not %v", req.URL)
}
} }
requestInfo.APIGroup = currentParts[0]
currentParts = currentParts[1:]
} }
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
} }

View File

@ -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)

View File

@ -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
}

View File

@ -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
} }
@ -933,7 +937,9 @@ 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,

View File

@ -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) {