mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 19:31:44 +00:00
Put user in context, map requests to context above resthandler layer
This commit is contained in:
parent
ec66e5147e
commit
083ce268e0
115
pkg/api/requestcontext.go
Normal file
115
pkg/api/requestcontext.go
Normal file
@ -0,0 +1,115 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// RequestContextMapper keeps track of the context associated with a particular request
|
||||
type RequestContextMapper interface {
|
||||
// Get returns the context associated with the given request (if any), and true if the request has an associated context, and false if it does not.
|
||||
Get(req *http.Request) (Context, bool)
|
||||
// Update maps the request to the given context. If no context was previously associated with the request, an error is returned.
|
||||
// Update should only be called with a descendant context of the previously associated context.
|
||||
// Updating to an unrelated context may return an error in the future.
|
||||
// The context associated with a request should only be updated by a limited set of callers.
|
||||
// Valid examples include the authentication layer, or an audit/tracing layer.
|
||||
Update(req *http.Request, context Context) error
|
||||
}
|
||||
|
||||
type requestContextMap struct {
|
||||
contexts map[*http.Request]Context
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
// NewRequestContextMapper returns a new RequestContextMapper.
|
||||
// The returned mapper must be added as a request filter using NewRequestContextFilter.
|
||||
func NewRequestContextMapper() RequestContextMapper {
|
||||
return &requestContextMap{
|
||||
contexts: make(map[*http.Request]Context),
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the context associated with the given request (if any), and true if the request has an associated context, and false if it does not.
|
||||
// Get will only return a valid context when called from inside the filter chain set up by NewRequestContextFilter()
|
||||
func (c *requestContextMap) Get(req *http.Request) (Context, bool) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
context, ok := c.contexts[req]
|
||||
return context, ok
|
||||
}
|
||||
|
||||
// Update maps the request to the given context.
|
||||
// If no context was previously associated with the request, an error is returned and the context is ignored.
|
||||
func (c *requestContextMap) Update(req *http.Request, context Context) error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if _, ok := c.contexts[req]; !ok {
|
||||
return errors.New("No context associated")
|
||||
}
|
||||
// TODO: ensure the new context is a descendant of the existing one
|
||||
c.contexts[req] = context
|
||||
return nil
|
||||
}
|
||||
|
||||
// init maps the request to the given context and returns true if there was no context associated with the request already.
|
||||
// if a context was already associated with the request, it ignores the given context and returns false.
|
||||
// init is intentionally unexported to ensure that all init calls are paired with a remove after a request is handled
|
||||
func (c *requestContextMap) init(req *http.Request, context Context) bool {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if _, exists := c.contexts[req]; exists {
|
||||
return false
|
||||
}
|
||||
c.contexts[req] = context
|
||||
return true
|
||||
}
|
||||
|
||||
// remove is intentionally unexported to ensure that the context is not removed until a request is handled
|
||||
func (c *requestContextMap) remove(req *http.Request) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
delete(c.contexts, req)
|
||||
}
|
||||
|
||||
// NewRequestContextFilter ensures there is a Context object associated with the request before calling the passed handler.
|
||||
// After the passed handler runs, the context is cleaned up.
|
||||
func NewRequestContextFilter(mapper RequestContextMapper, handler http.Handler) (http.Handler, error) {
|
||||
if mapper, ok := mapper.(*requestContextMap); ok {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if mapper.init(req, NewContext()) {
|
||||
// If we were the ones to successfully initialize, pair with a remove
|
||||
defer mapper.remove(req)
|
||||
}
|
||||
handler.ServeHTTP(w, req)
|
||||
}), nil
|
||||
} else {
|
||||
return handler, errors.New("Unknown RequestContextMapper implementation.")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// IsEmpty returns true if there are no contexts registered, or an error if it could not be determined. Intended for use by tests.
|
||||
func IsEmpty(requestsToContexts RequestContextMapper) (bool, error) {
|
||||
if requestsToContexts, ok := requestsToContexts.(*requestContextMap); ok {
|
||||
return len(requestsToContexts.contexts) == 0, nil
|
||||
}
|
||||
return true, errors.New("Unknown RequestContextMapper implementation")
|
||||
}
|
@ -62,8 +62,8 @@ func (a *APIInstaller) Install() (ws *restful.WebService, errors []error) {
|
||||
linker: a.group.linker,
|
||||
info: a.group.info,
|
||||
})
|
||||
redirectHandler := (&RedirectHandler{a.group.storage, a.group.codec, a.group.info})
|
||||
proxyHandler := (&ProxyHandler{a.prefix + "/proxy/", a.group.storage, a.group.codec, a.group.info})
|
||||
redirectHandler := (&RedirectHandler{a.group.storage, a.group.codec, a.group.context, a.group.info})
|
||||
proxyHandler := (&ProxyHandler{a.prefix + "/proxy/", a.group.storage, a.group.codec, a.group.context, a.group.info})
|
||||
|
||||
for path, storage := range a.group.storage {
|
||||
if err := a.registerResourceHandlers(path, storage, ws, watchHandler, redirectHandler, proxyHandler); err != nil {
|
||||
@ -88,6 +88,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
||||
codec := a.group.codec
|
||||
admit := a.group.admit
|
||||
linker := a.group.linker
|
||||
context := a.group.context
|
||||
resource := path
|
||||
|
||||
object := storage.New()
|
||||
@ -147,6 +148,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
||||
storageVerbs["Redirector"] = true
|
||||
}
|
||||
|
||||
var ctxFn ContextFunc
|
||||
var namespaceFn ResourceNamespaceFunc
|
||||
var nameFn ResourceNameFunc
|
||||
var generateLinkFn linkFunc
|
||||
@ -154,6 +156,12 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
||||
linkFn := func(req *restful.Request, obj runtime.Object) error {
|
||||
return setSelfLink(obj, req.Request, a.group.linker, generateLinkFn)
|
||||
}
|
||||
ctxFn = func(req *restful.Request) api.Context {
|
||||
if ctx, ok := context.Get(req.Request); ok {
|
||||
return ctx
|
||||
}
|
||||
return api.NewContext()
|
||||
}
|
||||
|
||||
allowWatchList := storageVerbs["ResourceWatcher"] && storageVerbs["RESTLister"] // watching on lists is allowed only for kinds that support both watch and list.
|
||||
scope := mapping.Scope
|
||||
@ -324,7 +332,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
||||
m := monitorFilter(action.Verb, resource)
|
||||
switch action.Verb {
|
||||
case "GET": // Get a resource.
|
||||
route := ws.GET(action.Path).To(GetResource(getter, nameFn, linkFn, codec)).
|
||||
route := ws.GET(action.Path).To(GetResource(getter, ctxFn, nameFn, linkFn, codec)).
|
||||
Filter(m).
|
||||
Doc("read the specified " + kind).
|
||||
Operation("read" + kind).
|
||||
@ -332,7 +340,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
||||
addParams(route, action.Params)
|
||||
ws.Route(route)
|
||||
case "LIST": // List all resources of a kind.
|
||||
route := ws.GET(action.Path).To(ListResource(lister, namespaceFn, linkFn, codec)).
|
||||
route := ws.GET(action.Path).To(ListResource(lister, ctxFn, namespaceFn, linkFn, codec)).
|
||||
Filter(m).
|
||||
Doc("list objects of kind " + kind).
|
||||
Operation("list" + kind).
|
||||
@ -340,7 +348,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
||||
addParams(route, action.Params)
|
||||
ws.Route(route)
|
||||
case "PUT": // Update a resource.
|
||||
route := ws.PUT(action.Path).To(UpdateResource(updater, nameFn, objNameFn, linkFn, codec, resource, admit)).
|
||||
route := ws.PUT(action.Path).To(UpdateResource(updater, ctxFn, nameFn, objNameFn, linkFn, codec, resource, admit)).
|
||||
Filter(m).
|
||||
Doc("update the specified " + kind).
|
||||
Operation("update" + kind).
|
||||
@ -348,7 +356,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
||||
addParams(route, action.Params)
|
||||
ws.Route(route)
|
||||
case "POST": // Create a resource.
|
||||
route := ws.POST(action.Path).To(CreateResource(creater, namespaceFn, linkFn, codec, resource, admit)).
|
||||
route := ws.POST(action.Path).To(CreateResource(creater, ctxFn, namespaceFn, linkFn, codec, resource, admit)).
|
||||
Filter(m).
|
||||
Doc("create a " + kind).
|
||||
Operation("create" + kind).
|
||||
@ -356,7 +364,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage RESTStorage
|
||||
addParams(route, action.Params)
|
||||
ws.Route(route)
|
||||
case "DELETE": // Delete a resource.
|
||||
route := ws.DELETE(action.Path).To(DeleteResource(deleter, nameFn, linkFn, codec, resource, kind, admit)).
|
||||
route := ws.DELETE(action.Path).To(DeleteResource(deleter, ctxFn, nameFn, linkFn, codec, resource, kind, admit)).
|
||||
Filter(m).
|
||||
Doc("delete a " + kind).
|
||||
Operation("delete" + kind)
|
||||
|
@ -98,9 +98,9 @@ type defaultAPIServer struct {
|
||||
// as RESTful resources at prefix, serialized by codec, and also includes the support
|
||||
// http resources.
|
||||
// Note: This method is used only in tests.
|
||||
func Handle(storage map[string]RESTStorage, codec runtime.Codec, root string, version string, linker runtime.SelfLinker, admissionControl admission.Interface, mapper meta.RESTMapper) http.Handler {
|
||||
func Handle(storage map[string]RESTStorage, codec runtime.Codec, root string, version string, linker runtime.SelfLinker, admissionControl admission.Interface, contextMapper api.RequestContextMapper, mapper meta.RESTMapper) http.Handler {
|
||||
prefix := path.Join(root, version)
|
||||
group := NewAPIGroupVersion(storage, codec, root, prefix, linker, admissionControl, mapper)
|
||||
group := NewAPIGroupVersion(storage, codec, root, prefix, linker, admissionControl, contextMapper, mapper)
|
||||
container := restful.NewContainer()
|
||||
container.Router(restful.CurlyRouter{})
|
||||
mux := container.ServeMux
|
||||
@ -121,6 +121,7 @@ type APIGroupVersion struct {
|
||||
prefix string
|
||||
linker runtime.SelfLinker
|
||||
admit admission.Interface
|
||||
context api.RequestContextMapper
|
||||
mapper meta.RESTMapper
|
||||
// TODO: put me into a cleaner interface
|
||||
info *APIRequestInfoResolver
|
||||
@ -131,13 +132,14 @@ type APIGroupVersion struct {
|
||||
// This is a helper method for registering multiple sets of REST handlers under different
|
||||
// prefixes onto a server.
|
||||
// TODO: add multitype codec serialization
|
||||
func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, root, prefix string, linker runtime.SelfLinker, admissionControl admission.Interface, mapper meta.RESTMapper) *APIGroupVersion {
|
||||
func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, root, prefix string, linker runtime.SelfLinker, admissionControl admission.Interface, contextMapper api.RequestContextMapper, mapper meta.RESTMapper) *APIGroupVersion {
|
||||
return &APIGroupVersion{
|
||||
storage: storage,
|
||||
codec: codec,
|
||||
prefix: prefix,
|
||||
linker: linker,
|
||||
admit: admissionControl,
|
||||
context: contextMapper,
|
||||
mapper: mapper,
|
||||
info: &APIRequestInfoResolver{util.NewStringSet(root), latest.RESTMapper},
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ var versioner runtime.ResourceVersioner = accessor
|
||||
var selfLinker runtime.SelfLinker = accessor
|
||||
var mapper, namespaceMapper, legacyNamespaceMapper meta.RESTMapper // The mappers with namespace and with legacy namespace scopes.
|
||||
var admissionControl admission.Interface
|
||||
var requestContextMapper api.RequestContextMapper
|
||||
|
||||
func interfacesFor(version string) (*meta.VersionInterfaces, error) {
|
||||
switch version {
|
||||
@ -111,6 +112,7 @@ func init() {
|
||||
legacyNamespaceMapper = legacyNsMapper
|
||||
namespaceMapper = nsMapper
|
||||
admissionControl = admit.NewAlwaysAdmit()
|
||||
requestContextMapper = api.NewRequestContextMapper()
|
||||
}
|
||||
|
||||
type Simple struct {
|
||||
@ -283,7 +285,7 @@ func TestNotFound(t *testing.T) {
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": &SimpleRESTStorage{},
|
||||
}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||
}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
client := http.Client{}
|
||||
@ -335,7 +337,7 @@ func TestUnimplementedRESTStorage(t *testing.T) {
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": UnimplementedRESTStorage{},
|
||||
}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||
}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
client := http.Client{}
|
||||
@ -360,7 +362,7 @@ func TestUnimplementedRESTStorage(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
handler := Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||
handler := Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
client := http.Client{}
|
||||
@ -395,7 +397,7 @@ func TestSimpleList(t *testing.T) {
|
||||
namespace: "other",
|
||||
expectedSet: "/prefix/version/simple?namespace=other",
|
||||
}
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -418,7 +420,7 @@ func TestErrorList(t *testing.T) {
|
||||
errors: map[string]error{"list": fmt.Errorf("test Error")},
|
||||
}
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -444,7 +446,7 @@ func TestNonEmptyList(t *testing.T) {
|
||||
},
|
||||
}
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -492,7 +494,7 @@ func TestGet(t *testing.T) {
|
||||
namespace: "default",
|
||||
}
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -531,7 +533,7 @@ func TestGetAlternateSelfLink(t *testing.T) {
|
||||
namespace: "test",
|
||||
}
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, legacyNamespaceMapper)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, legacyNamespaceMapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -569,7 +571,7 @@ func TestGetNamespaceSelfLink(t *testing.T) {
|
||||
namespace: "foo",
|
||||
}
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, namespaceMapper)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, namespaceMapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -598,7 +600,7 @@ func TestGetMissing(t *testing.T) {
|
||||
errors: map[string]error{"get": apierrs.NewNotFound("simple", "id")},
|
||||
}
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -617,7 +619,7 @@ func TestDelete(t *testing.T) {
|
||||
simpleStorage := SimpleRESTStorage{}
|
||||
ID := "id"
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -640,7 +642,7 @@ func TestDeleteInvokesAdmissionControl(t *testing.T) {
|
||||
simpleStorage := SimpleRESTStorage{}
|
||||
ID := "id"
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), mapper)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -662,7 +664,7 @@ func TestDeleteMissing(t *testing.T) {
|
||||
errors: map[string]error{"delete": apierrs.NewNotFound("simple", ID)},
|
||||
}
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -689,7 +691,7 @@ func TestUpdate(t *testing.T) {
|
||||
name: ID,
|
||||
namespace: api.NamespaceDefault,
|
||||
}
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -726,7 +728,7 @@ func TestUpdateInvokesAdmissionControl(t *testing.T) {
|
||||
simpleStorage := SimpleRESTStorage{}
|
||||
ID := "id"
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), mapper)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -759,7 +761,7 @@ func TestUpdateRequiresMatchingName(t *testing.T) {
|
||||
simpleStorage := SimpleRESTStorage{}
|
||||
ID := "id"
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), mapper)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -788,7 +790,7 @@ func TestUpdateAllowsMissingNamespace(t *testing.T) {
|
||||
simpleStorage := SimpleRESTStorage{}
|
||||
ID := "id"
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -820,7 +822,7 @@ func TestUpdatePreventsMismatchedNamespace(t *testing.T) {
|
||||
simpleStorage := SimpleRESTStorage{}
|
||||
ID := "id"
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -855,7 +857,7 @@ func TestUpdateMissing(t *testing.T) {
|
||||
errors: map[string]error{"update": apierrs.NewNotFound("simple", ID)},
|
||||
}
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -889,7 +891,7 @@ func TestCreateNotFound(t *testing.T) {
|
||||
// See https://github.com/GoogleCloudPlatform/kubernetes/pull/486#discussion_r15037092.
|
||||
errors: map[string]error{"create": apierrs.NewNotFound("simple", "id")},
|
||||
},
|
||||
}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||
}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
client := http.Client{}
|
||||
@ -957,7 +959,7 @@ func TestCreate(t *testing.T) {
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": &storage,
|
||||
}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||
}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
client := http.Client{}
|
||||
@ -1015,7 +1017,7 @@ func TestCreateInvokesAdmissionControl(t *testing.T) {
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": &storage,
|
||||
}, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), mapper)
|
||||
}, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny(), requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
client := http.Client{}
|
||||
@ -1075,7 +1077,7 @@ func TestDelayReturnsError(t *testing.T) {
|
||||
return nil, apierrs.NewAlreadyExists("foo", "bar")
|
||||
},
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||
handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -1141,7 +1143,7 @@ func TestCreateTimeout(t *testing.T) {
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": &storage,
|
||||
}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper)
|
||||
}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -1173,7 +1175,7 @@ func TestCORSAllowedOrigins(t *testing.T) {
|
||||
}
|
||||
|
||||
handler := CORS(
|
||||
Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl, mapper),
|
||||
Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl, requestContextMapper, mapper),
|
||||
allowedOriginRegexps, nil, nil, "true",
|
||||
)
|
||||
server := httptest.NewServer(handler)
|
||||
|
@ -27,7 +27,6 @@ import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer"
|
||||
authhandlers "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/handlers"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
"github.com/golang/glog"
|
||||
@ -154,21 +153,24 @@ type RequestAttributeGetter interface {
|
||||
}
|
||||
|
||||
type requestAttributeGetter struct {
|
||||
userContexts authhandlers.RequestContext
|
||||
requestContextMapper api.RequestContextMapper
|
||||
apiRequestInfoResolver *APIRequestInfoResolver
|
||||
}
|
||||
|
||||
// NewAttributeGetter returns an object which implements the RequestAttributeGetter interface.
|
||||
func NewRequestAttributeGetter(userContexts authhandlers.RequestContext, restMapper meta.RESTMapper, apiRoots ...string) RequestAttributeGetter {
|
||||
return &requestAttributeGetter{userContexts, &APIRequestInfoResolver{util.NewStringSet(apiRoots...), restMapper}}
|
||||
func NewRequestAttributeGetter(requestContextMapper api.RequestContextMapper, restMapper meta.RESTMapper, apiRoots ...string) RequestAttributeGetter {
|
||||
return &requestAttributeGetter{requestContextMapper, &APIRequestInfoResolver{util.NewStringSet(apiRoots...), restMapper}}
|
||||
}
|
||||
|
||||
func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attributes {
|
||||
attribs := authorizer.AttributesRecord{}
|
||||
|
||||
user, ok := r.userContexts.Get(req)
|
||||
ctx, ok := r.requestContextMapper.Get(req)
|
||||
if ok {
|
||||
attribs.User = user
|
||||
user, ok := api.UserFrom(ctx)
|
||||
if ok {
|
||||
attribs.User = user
|
||||
}
|
||||
}
|
||||
|
||||
attribs.ReadOnly = IsReadOnlyReq(*req)
|
||||
|
@ -78,6 +78,7 @@ type ProxyHandler struct {
|
||||
prefix string
|
||||
storage map[string]RESTStorage
|
||||
codec runtime.Codec
|
||||
context api.RequestContextMapper
|
||||
apiRequestInfoResolver *APIRequestInfoResolver
|
||||
}
|
||||
|
||||
@ -97,7 +98,11 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
verb = requestInfo.Verb
|
||||
namespace, resource, parts := requestInfo.Namespace, requestInfo.Resource, requestInfo.Parts
|
||||
|
||||
ctx := api.WithNamespace(api.NewContext(), namespace)
|
||||
ctx, ok := r.context.Get(req)
|
||||
if !ok {
|
||||
ctx = api.NewContext()
|
||||
}
|
||||
ctx = api.WithNamespace(ctx, namespace)
|
||||
if len(parts) < 2 {
|
||||
notFound(w, req)
|
||||
httpCode = http.StatusNotFound
|
||||
|
@ -281,12 +281,12 @@ func TestProxy(t *testing.T) {
|
||||
|
||||
namespaceHandler := Handle(map[string]RESTStorage{
|
||||
"foo": simpleStorage,
|
||||
}, codec, "/prefix", "version", selfLinker, admissionControl, namespaceMapper)
|
||||
}, codec, "/prefix", "version", selfLinker, admissionControl, requestContextMapper, namespaceMapper)
|
||||
namespaceServer := httptest.NewServer(namespaceHandler)
|
||||
defer namespaceServer.Close()
|
||||
legacyNamespaceHandler := Handle(map[string]RESTStorage{
|
||||
"foo": simpleStorage,
|
||||
}, codec, "/prefix", "version", selfLinker, admissionControl, legacyNamespaceMapper)
|
||||
}, codec, "/prefix", "version", selfLinker, admissionControl, requestContextMapper, legacyNamespaceMapper)
|
||||
legacyNamespaceServer := httptest.NewServer(legacyNamespaceHandler)
|
||||
defer legacyNamespaceServer.Close()
|
||||
|
||||
|
@ -29,6 +29,7 @@ import (
|
||||
type RedirectHandler struct {
|
||||
storage map[string]RESTStorage
|
||||
codec runtime.Codec
|
||||
context api.RequestContextMapper
|
||||
apiRequestInfoResolver *APIRequestInfoResolver
|
||||
}
|
||||
|
||||
@ -47,7 +48,11 @@ func (r *RedirectHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
verb = requestInfo.Verb
|
||||
resource, parts := requestInfo.Resource, requestInfo.Parts
|
||||
ctx := api.WithNamespace(api.NewContext(), requestInfo.Namespace)
|
||||
ctx, ok := r.context.Get(req)
|
||||
if !ok {
|
||||
ctx = api.NewContext()
|
||||
}
|
||||
ctx = api.WithNamespace(ctx, requestInfo.Namespace)
|
||||
|
||||
// redirection requires /resource/resourceName path parts
|
||||
if len(parts) != 2 || req.Method != "GET" {
|
||||
|
@ -31,7 +31,7 @@ func TestRedirect(t *testing.T) {
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": simpleStorage,
|
||||
}, codec, "/prefix", "version", selfLinker, admissionControl, mapper)
|
||||
}, codec, "/prefix", "version", selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -84,7 +84,7 @@ func TestRedirectWithNamespaces(t *testing.T) {
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": simpleStorage,
|
||||
}, codec, "/prefix", "version", selfLinker, admissionControl, namespaceMapper)
|
||||
}, codec, "/prefix", "version", selfLinker, admissionControl, requestContextMapper, namespaceMapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
|
@ -29,6 +29,9 @@ import (
|
||||
"github.com/emicklei/go-restful"
|
||||
)
|
||||
|
||||
// ContextFunc returns a Context given a request - a context must be returned
|
||||
type ContextFunc func(req *restful.Request) api.Context
|
||||
|
||||
// ResourceNameFunc returns a name (and optional namespace) given a request - if no name is present
|
||||
// an error must be returned.
|
||||
type ResourceNameFunc func(req *restful.Request) (namespace, name string, err error)
|
||||
@ -45,7 +48,7 @@ type ResourceNamespaceFunc func(req *restful.Request) (namespace string, err err
|
||||
type LinkResourceFunc func(req *restful.Request, obj runtime.Object) error
|
||||
|
||||
// GetResource returns a function that handles retrieving a single resource from a RESTStorage object.
|
||||
func GetResource(r RESTGetter, nameFn ResourceNameFunc, linkFn LinkResourceFunc, codec runtime.Codec) restful.RouteFunction {
|
||||
func GetResource(r RESTGetter, ctxFn ContextFunc, nameFn ResourceNameFunc, linkFn LinkResourceFunc, codec runtime.Codec) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
w := res.ResponseWriter
|
||||
namespace, name, err := nameFn(req)
|
||||
@ -53,7 +56,7 @@ func GetResource(r RESTGetter, nameFn ResourceNameFunc, linkFn LinkResourceFunc,
|
||||
notFound(w, req.Request)
|
||||
return
|
||||
}
|
||||
ctx := api.NewContext()
|
||||
ctx := ctxFn(req)
|
||||
if len(namespace) > 0 {
|
||||
ctx = api.WithNamespace(ctx, namespace)
|
||||
}
|
||||
@ -71,7 +74,7 @@ func GetResource(r RESTGetter, nameFn ResourceNameFunc, linkFn LinkResourceFunc,
|
||||
}
|
||||
|
||||
// ListResource returns a function that handles retrieving a list of resources from a RESTStorage object.
|
||||
func ListResource(r RESTLister, namespaceFn ResourceNamespaceFunc, linkFn LinkResourceFunc, codec runtime.Codec) restful.RouteFunction {
|
||||
func ListResource(r RESTLister, ctxFn ContextFunc, namespaceFn ResourceNamespaceFunc, linkFn LinkResourceFunc, codec runtime.Codec) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
w := res.ResponseWriter
|
||||
|
||||
@ -80,7 +83,7 @@ func ListResource(r RESTLister, namespaceFn ResourceNamespaceFunc, linkFn LinkRe
|
||||
notFound(w, req.Request)
|
||||
return
|
||||
}
|
||||
ctx := api.NewContext()
|
||||
ctx := ctxFn(req)
|
||||
if len(namespace) > 0 {
|
||||
ctx = api.WithNamespace(ctx, namespace)
|
||||
}
|
||||
@ -109,7 +112,7 @@ func ListResource(r RESTLister, namespaceFn ResourceNamespaceFunc, linkFn LinkRe
|
||||
}
|
||||
|
||||
// CreateResource returns a function that will handle a resource creation.
|
||||
func CreateResource(r RESTCreater, namespaceFn ResourceNamespaceFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction {
|
||||
func CreateResource(r RESTCreater, ctxFn ContextFunc, namespaceFn ResourceNamespaceFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
w := res.ResponseWriter
|
||||
|
||||
@ -121,7 +124,7 @@ func CreateResource(r RESTCreater, namespaceFn ResourceNamespaceFunc, linkFn Lin
|
||||
notFound(w, req.Request)
|
||||
return
|
||||
}
|
||||
ctx := api.NewContext()
|
||||
ctx := ctxFn(req)
|
||||
if len(namespace) > 0 {
|
||||
ctx = api.WithNamespace(ctx, namespace)
|
||||
}
|
||||
@ -162,7 +165,7 @@ func CreateResource(r RESTCreater, namespaceFn ResourceNamespaceFunc, linkFn Lin
|
||||
}
|
||||
|
||||
// UpdateResource returns a function that will handle a resource update
|
||||
func UpdateResource(r RESTUpdater, nameFn ResourceNameFunc, objNameFunc ObjectNameFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction {
|
||||
func UpdateResource(r RESTUpdater, ctxFn ContextFunc, nameFn ResourceNameFunc, objNameFunc ObjectNameFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource string, admit admission.Interface) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
w := res.ResponseWriter
|
||||
|
||||
@ -174,7 +177,7 @@ func UpdateResource(r RESTUpdater, nameFn ResourceNameFunc, objNameFunc ObjectNa
|
||||
notFound(w, req.Request)
|
||||
return
|
||||
}
|
||||
ctx := api.NewContext()
|
||||
ctx := ctxFn(req)
|
||||
if len(namespace) > 0 {
|
||||
ctx = api.WithNamespace(ctx, namespace)
|
||||
}
|
||||
@ -238,7 +241,7 @@ func UpdateResource(r RESTUpdater, nameFn ResourceNameFunc, objNameFunc ObjectNa
|
||||
}
|
||||
|
||||
// DeleteResource returns a function that will handle a resource deletion
|
||||
func DeleteResource(r RESTDeleter, nameFn ResourceNameFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource, kind string, admit admission.Interface) restful.RouteFunction {
|
||||
func DeleteResource(r RESTDeleter, ctxFn ContextFunc, nameFn ResourceNameFunc, linkFn LinkResourceFunc, codec runtime.Codec, resource, kind string, admit admission.Interface) restful.RouteFunction {
|
||||
return func(req *restful.Request, res *restful.Response) {
|
||||
w := res.ResponseWriter
|
||||
|
||||
@ -250,7 +253,7 @@ func DeleteResource(r RESTDeleter, nameFn ResourceNameFunc, linkFn LinkResourceF
|
||||
notFound(w, req.Request)
|
||||
return
|
||||
}
|
||||
ctx := api.NewContext()
|
||||
ctx := ctxFn(req)
|
||||
if len(namespace) > 0 {
|
||||
ctx = api.WithNamespace(ctx, namespace)
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ func TestWatchWebsocket(t *testing.T) {
|
||||
_ = ResourceWatcher(simpleStorage) // Give compile error if this doesn't work.
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": simpleStorage,
|
||||
}, codec, "/api", "version", selfLinker, admissionControl, mapper)
|
||||
}, codec, "/api", "version", selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -104,7 +104,7 @@ func TestWatchHTTP(t *testing.T) {
|
||||
simpleStorage := &SimpleRESTStorage{}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": simpleStorage,
|
||||
}, codec, "/api", "version", selfLinker, admissionControl, mapper)
|
||||
}, codec, "/api", "version", selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
client := http.Client{}
|
||||
@ -167,7 +167,7 @@ func TestWatchParamParsing(t *testing.T) {
|
||||
simpleStorage := &SimpleRESTStorage{}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": simpleStorage,
|
||||
}, codec, "/api", "version", selfLinker, admissionControl, mapper)
|
||||
}, codec, "/api", "version", selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -239,7 +239,7 @@ func TestWatchProtocolSelection(t *testing.T) {
|
||||
simpleStorage := &SimpleRESTStorage{}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": simpleStorage,
|
||||
}, codec, "/api", "version", selfLinker, admissionControl, mapper)
|
||||
}, codec, "/api", "version", selfLinker, admissionControl, requestContextMapper, mapper)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
defer server.CloseClientConnections()
|
||||
|
@ -18,39 +18,35 @@ package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// RequestContext is the interface used to associate a user with an http Request.
|
||||
type RequestContext interface {
|
||||
Set(*http.Request, user.Info)
|
||||
Get(req *http.Request) (user.Info, bool)
|
||||
Remove(*http.Request)
|
||||
}
|
||||
|
||||
// NewRequestAuthenticator creates an http handler that tries to authenticate the given request as a user, and then
|
||||
// stores any such user found onto the provided context for the request. If authentication fails or returns an error
|
||||
// the failed handler is used. On success, handler is invoked to serve the request.
|
||||
func NewRequestAuthenticator(context RequestContext, auth authenticator.Request, failed http.Handler, handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
user, ok, err := auth.AuthenticateRequest(req)
|
||||
if err != nil || !ok {
|
||||
if err != nil {
|
||||
glog.Errorf("Unable to authenticate the request due to an error: %v", err)
|
||||
func NewRequestAuthenticator(mapper api.RequestContextMapper, auth authenticator.Request, failed http.Handler, handler http.Handler) (http.Handler, error) {
|
||||
return api.NewRequestContextFilter(
|
||||
mapper,
|
||||
http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
user, ok, err := auth.AuthenticateRequest(req)
|
||||
if err != nil || !ok {
|
||||
if err != nil {
|
||||
glog.Errorf("Unable to authenticate the request due to an error: %v", err)
|
||||
}
|
||||
failed.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
failed.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
context.Set(req, user)
|
||||
defer context.Remove(req)
|
||||
if ctx, ok := mapper.Get(req); ok {
|
||||
mapper.Update(req, api.WithUser(ctx, user))
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
})
|
||||
handler.ServeHTTP(w, req)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
var Unauthorized http.HandlerFunc = unauthorized
|
||||
@ -59,38 +55,3 @@ var Unauthorized http.HandlerFunc = unauthorized
|
||||
func unauthorized(w http.ResponseWriter, req *http.Request) {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
// UserRequestContext allows different levels of a call stack to store/retrieve info about the
|
||||
// current user associated with an http.Request.
|
||||
type UserRequestContext struct {
|
||||
requests map[*http.Request]user.Info
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
// NewUserRequestContext provides a map for storing and retrieving users associated with requests.
|
||||
// Be sure to pair each `context.Set(req, user)` call with a `defer context.Remove(req)` call or
|
||||
// you will leak requests. It implements the RequestContext interface.
|
||||
func NewUserRequestContext() *UserRequestContext {
|
||||
return &UserRequestContext{
|
||||
requests: make(map[*http.Request]user.Info),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UserRequestContext) Get(req *http.Request) (user.Info, bool) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
user, ok := c.requests[req]
|
||||
return user, ok
|
||||
}
|
||||
|
||||
func (c *UserRequestContext) Set(req *http.Request, user user.Info) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.requests[req] = user
|
||||
}
|
||||
|
||||
func (c *UserRequestContext) Remove(req *http.Request) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
delete(c.requests, req)
|
||||
}
|
||||
|
@ -22,15 +22,16 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authenticator"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
|
||||
)
|
||||
|
||||
func TestAuthenticateRequest(t *testing.T) {
|
||||
success := make(chan struct{})
|
||||
context := NewUserRequestContext()
|
||||
auth := NewRequestAuthenticator(
|
||||
context,
|
||||
contextMapper := api.NewRequestContextMapper()
|
||||
auth, err := NewRequestAuthenticator(
|
||||
contextMapper,
|
||||
authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) {
|
||||
return &user.DefaultInfo{Name: "user"}, true, nil
|
||||
}),
|
||||
@ -38,8 +39,13 @@ func TestAuthenticateRequest(t *testing.T) {
|
||||
t.Errorf("unexpected call to failed")
|
||||
}),
|
||||
http.HandlerFunc(func(_ http.ResponseWriter, req *http.Request) {
|
||||
if user, ok := context.Get(req); user == nil || !ok {
|
||||
t.Errorf("no user stored on context: %#v", context)
|
||||
ctx, ok := contextMapper.Get(req)
|
||||
if ctx == nil || !ok {
|
||||
t.Errorf("no context stored on contextMapper: %#v", contextMapper)
|
||||
}
|
||||
user, ok := api.UserFrom(ctx)
|
||||
if user == nil || !ok {
|
||||
t.Errorf("no user stored in context: %#v", ctx)
|
||||
}
|
||||
close(success)
|
||||
}),
|
||||
@ -48,16 +54,20 @@ func TestAuthenticateRequest(t *testing.T) {
|
||||
auth.ServeHTTP(httptest.NewRecorder(), &http.Request{})
|
||||
|
||||
<-success
|
||||
if len(context.requests) > 0 {
|
||||
t.Errorf("context should have no stored requests: %v", context)
|
||||
empty, err := api.IsEmpty(contextMapper)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !empty {
|
||||
t.Fatalf("contextMapper should have no stored requests: %v", contextMapper)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthenticateRequestFailed(t *testing.T) {
|
||||
failed := make(chan struct{})
|
||||
context := NewUserRequestContext()
|
||||
auth := NewRequestAuthenticator(
|
||||
context,
|
||||
contextMapper := api.NewRequestContextMapper()
|
||||
auth, err := NewRequestAuthenticator(
|
||||
contextMapper,
|
||||
authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) {
|
||||
return nil, false, nil
|
||||
}),
|
||||
@ -72,16 +82,20 @@ func TestAuthenticateRequestFailed(t *testing.T) {
|
||||
auth.ServeHTTP(httptest.NewRecorder(), &http.Request{})
|
||||
|
||||
<-failed
|
||||
if len(context.requests) > 0 {
|
||||
t.Errorf("context should have no stored requests: %v", context)
|
||||
empty, err := api.IsEmpty(contextMapper)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !empty {
|
||||
t.Fatalf("contextMapper should have no stored requests: %v", contextMapper)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthenticateRequestError(t *testing.T) {
|
||||
failed := make(chan struct{})
|
||||
context := NewUserRequestContext()
|
||||
auth := NewRequestAuthenticator(
|
||||
context,
|
||||
contextMapper := api.NewRequestContextMapper()
|
||||
auth, err := NewRequestAuthenticator(
|
||||
contextMapper,
|
||||
authenticator.RequestFunc(func(req *http.Request) (user.Info, bool, error) {
|
||||
return nil, false, errors.New("failure")
|
||||
}),
|
||||
@ -96,7 +110,11 @@ func TestAuthenticateRequestError(t *testing.T) {
|
||||
auth.ServeHTTP(httptest.NewRecorder(), &http.Request{})
|
||||
|
||||
<-failed
|
||||
if len(context.requests) > 0 {
|
||||
t.Errorf("context should have no stored requests: %v", context)
|
||||
empty, err := api.IsEmpty(contextMapper)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !empty {
|
||||
t.Fatalf("contextMapper should have no stored requests: %v", contextMapper)
|
||||
}
|
||||
}
|
||||
|
@ -89,6 +89,9 @@ type Config struct {
|
||||
AdmissionControl admission.Interface
|
||||
MasterServiceNamespace string
|
||||
|
||||
// Map requests to contexts. Exported so downstream consumers can provider their own mappers
|
||||
RequestContextMapper api.RequestContextMapper
|
||||
|
||||
// If specified, all web services will be registered into this container
|
||||
RestfulContainer *restful.Container
|
||||
|
||||
@ -143,6 +146,7 @@ type Master struct {
|
||||
admissionControl admission.Interface
|
||||
masterCount int
|
||||
v1beta3 bool
|
||||
requestContextMapper api.RequestContextMapper
|
||||
|
||||
publicIP net.IP
|
||||
publicReadOnlyPort int
|
||||
@ -225,6 +229,9 @@ func setDefaults(c *Config) {
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
}
|
||||
if c.RequestContextMapper == nil {
|
||||
c.RequestContextMapper = api.NewRequestContextMapper()
|
||||
}
|
||||
}
|
||||
|
||||
// New returns a new instance of Master from the given config.
|
||||
@ -292,6 +299,7 @@ func New(c *Config) *Master {
|
||||
authorizer: c.Authorizer,
|
||||
admissionControl: c.AdmissionControl,
|
||||
v1beta3: c.EnableV1Beta3,
|
||||
requestContextMapper: c.RequestContextMapper,
|
||||
|
||||
cacheTimeout: c.CacheTimeout,
|
||||
|
||||
@ -367,8 +375,6 @@ func logStackOnRecover(panicReason interface{}, httpWriter http.ResponseWriter)
|
||||
|
||||
// init initializes master.
|
||||
func (m *Master) init(c *Config) {
|
||||
var userContexts = handlers.NewUserRequestContext()
|
||||
var authenticator = c.Authenticator
|
||||
|
||||
nodeRESTStorage := minion.NewREST(m.minionRegistry)
|
||||
podCache := NewPodCache(
|
||||
@ -453,12 +459,16 @@ func (m *Master) init(c *Config) {
|
||||
|
||||
m.InsecureHandler = handler
|
||||
|
||||
attributeGetter := apiserver.NewRequestAttributeGetter(userContexts, latest.RESTMapper, "api")
|
||||
attributeGetter := apiserver.NewRequestAttributeGetter(m.requestContextMapper, latest.RESTMapper, "api")
|
||||
handler = apiserver.WithAuthorizationCheck(handler, attributeGetter, m.authorizer)
|
||||
|
||||
// Install Authenticator
|
||||
if authenticator != nil {
|
||||
handler = handlers.NewRequestAuthenticator(userContexts, authenticator, handlers.Unauthorized, handler)
|
||||
if c.Authenticator != nil {
|
||||
authenticatedHandler, err := handlers.NewRequestAuthenticator(m.requestContextMapper, c.Authenticator, handlers.Unauthorized, handler)
|
||||
if err != nil {
|
||||
glog.Fatalf("Could not initialize authenticator: %v", err)
|
||||
}
|
||||
handler = authenticatedHandler
|
||||
}
|
||||
|
||||
// Install root web services
|
||||
@ -471,6 +481,19 @@ func (m *Master) init(c *Config) {
|
||||
m.InstallSwaggerAPI()
|
||||
}
|
||||
|
||||
// After all wrapping is done, put a context filter around both handlers
|
||||
if handler, err := api.NewRequestContextFilter(m.requestContextMapper, m.Handler); err != nil {
|
||||
glog.Fatalf("Could not initialize request context filter: %v", err)
|
||||
} else {
|
||||
m.Handler = handler
|
||||
}
|
||||
|
||||
if handler, err := api.NewRequestContextFilter(m.requestContextMapper, m.InsecureHandler); err != nil {
|
||||
glog.Fatalf("Could not initialize request context filter: %v", err)
|
||||
} else {
|
||||
m.InsecureHandler = handler
|
||||
}
|
||||
|
||||
// TODO: Attempt clean shutdown?
|
||||
m.masterServices.Start()
|
||||
}
|
||||
@ -530,25 +553,25 @@ func (m *Master) getServersToValidate(c *Config) map[string]apiserver.Server {
|
||||
}
|
||||
|
||||
// api_v1beta1 returns the resources and codec for API version v1beta1.
|
||||
func (m *Master) api_v1beta1() (map[string]apiserver.RESTStorage, runtime.Codec, string, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) {
|
||||
func (m *Master) api_v1beta1() (map[string]apiserver.RESTStorage, runtime.Codec, string, string, runtime.SelfLinker, admission.Interface, api.RequestContextMapper, meta.RESTMapper) {
|
||||
storage := make(map[string]apiserver.RESTStorage)
|
||||
for k, v := range m.storage {
|
||||
storage[k] = v
|
||||
}
|
||||
return storage, v1beta1.Codec, "api", "/api/v1beta1", latest.SelfLinker, m.admissionControl, latest.RESTMapper
|
||||
return storage, v1beta1.Codec, "api", "/api/v1beta1", latest.SelfLinker, m.admissionControl, m.requestContextMapper, latest.RESTMapper
|
||||
}
|
||||
|
||||
// api_v1beta2 returns the resources and codec for API version v1beta2.
|
||||
func (m *Master) api_v1beta2() (map[string]apiserver.RESTStorage, runtime.Codec, string, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) {
|
||||
func (m *Master) api_v1beta2() (map[string]apiserver.RESTStorage, runtime.Codec, string, string, runtime.SelfLinker, admission.Interface, api.RequestContextMapper, meta.RESTMapper) {
|
||||
storage := make(map[string]apiserver.RESTStorage)
|
||||
for k, v := range m.storage {
|
||||
storage[k] = v
|
||||
}
|
||||
return storage, v1beta2.Codec, "api", "/api/v1beta2", latest.SelfLinker, m.admissionControl, latest.RESTMapper
|
||||
return storage, v1beta2.Codec, "api", "/api/v1beta2", latest.SelfLinker, m.admissionControl, m.requestContextMapper, latest.RESTMapper
|
||||
}
|
||||
|
||||
// api_v1beta3 returns the resources and codec for API version v1beta3.
|
||||
func (m *Master) api_v1beta3() (map[string]apiserver.RESTStorage, runtime.Codec, string, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) {
|
||||
func (m *Master) api_v1beta3() (map[string]apiserver.RESTStorage, runtime.Codec, string, string, runtime.SelfLinker, admission.Interface, api.RequestContextMapper, meta.RESTMapper) {
|
||||
storage := make(map[string]apiserver.RESTStorage)
|
||||
for k, v := range m.storage {
|
||||
if k == "minions" {
|
||||
@ -556,5 +579,5 @@ func (m *Master) api_v1beta3() (map[string]apiserver.RESTStorage, runtime.Codec,
|
||||
}
|
||||
storage[strings.ToLower(k)] = v
|
||||
}
|
||||
return storage, v1beta3.Codec, "api", "/api/v1beta3", latest.SelfLinker, m.admissionControl, latest.RESTMapper
|
||||
return storage, v1beta3.Codec, "api", "/api/v1beta3", latest.SelfLinker, m.admissionControl, m.requestContextMapper, latest.RESTMapper
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user