mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-30 15:05:27 +00:00
Merge pull request #33490 from sttts/sttts-requestinfo-via-context
Automatic merge from submit-queue Store RequestInfo in Context instead of RequestInfoResolver dependency injection **Depends on https://github.com/kubernetes/kubernetes/pull/33478** The `RequestInfoResolver` is used in the proxy handler and in the authorization code. It is passed through half of the apiserver code base to be available at those locations. This PR uses the context instead, which is our natural dependency injection mechanism in the handlers. `RequestInfo` and all tooling around is moved to `pkg/apiserver/request` temporarily to avoid dependency cycles. This is necessary as long as `pkg/apiserver` implements the proxy and other handlers. Those might move to `pkg/apiserver/handlers` probably later.
This commit is contained in:
commit
9b1e2069a6
@ -40,7 +40,6 @@ import (
|
||||
|
||||
type APIInstaller struct {
|
||||
group *APIGroupVersion
|
||||
info *RequestInfoResolver
|
||||
prefix string // Path prefix where API resources are to be registered.
|
||||
minRequestTimeout time.Duration
|
||||
}
|
||||
@ -67,11 +66,10 @@ func (a *APIInstaller) Install(ws *restful.WebService) (apiResources []unversion
|
||||
errors = make([]error, 0)
|
||||
|
||||
proxyHandler := (&ProxyHandler{
|
||||
prefix: a.prefix + "/proxy/",
|
||||
storage: a.group.Storage,
|
||||
serializer: a.group.Serializer,
|
||||
context: a.group.Context,
|
||||
requestInfoResolver: a.info,
|
||||
prefix: a.prefix + "/proxy/",
|
||||
storage: a.group.Storage,
|
||||
serializer: a.group.Serializer,
|
||||
mapper: a.group.Context,
|
||||
})
|
||||
|
||||
// Register the paths in a deterministic (sorted) order to get a deterministic swagger spec.
|
||||
|
@ -67,10 +67,6 @@ type APIGroupVersion struct {
|
||||
// GroupVersion is the external group version
|
||||
GroupVersion unversioned.GroupVersion
|
||||
|
||||
// RequestInfoResolver 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
|
||||
RequestInfoResolver *RequestInfoResolver
|
||||
|
||||
// OptionsExternalVersion controls the Kubernetes APIVersion used for common objects in the apiserver
|
||||
// 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
|
||||
@ -175,7 +171,6 @@ func (g *APIGroupVersion) newInstaller() *APIInstaller {
|
||||
prefix := path.Join(g.Root, g.GroupVersion.Group, g.GroupVersion.Version)
|
||||
installer := &APIInstaller{
|
||||
group: g,
|
||||
info: g.RequestInfoResolver,
|
||||
prefix: prefix,
|
||||
minRequestTimeout: g.MinRequestTimeout,
|
||||
}
|
||||
|
@ -39,11 +39,14 @@ import (
|
||||
"k8s.io/kubernetes/pkg/api/rest"
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/apiserver/filters"
|
||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
||||
apiservertesting "k8s.io/kubernetes/pkg/apiserver/testing"
|
||||
"k8s.io/kubernetes/pkg/fields"
|
||||
"k8s.io/kubernetes/pkg/labels"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/util/diff"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
"k8s.io/kubernetes/pkg/watch"
|
||||
"k8s.io/kubernetes/pkg/watch/versioned"
|
||||
"k8s.io/kubernetes/plugin/pkg/admission/admit"
|
||||
@ -259,8 +262,6 @@ func handleInternal(storage map[string]rest.Storage, admissionControl admission.
|
||||
template := APIGroupVersion{
|
||||
Storage: storage,
|
||||
|
||||
RequestInfoResolver: newTestRequestInfoResolver(),
|
||||
|
||||
Creater: api.Scheme,
|
||||
Convertor: api.Scheme,
|
||||
Copier: api.Scheme,
|
||||
@ -2386,14 +2387,13 @@ func TestCreateChecksDecode(t *testing.T) {
|
||||
func TestUpdateREST(t *testing.T) {
|
||||
makeGroup := func(storage map[string]rest.Storage) *APIGroupVersion {
|
||||
return &APIGroupVersion{
|
||||
Storage: storage,
|
||||
Root: "/" + prefix,
|
||||
RequestInfoResolver: newTestRequestInfoResolver(),
|
||||
Creater: api.Scheme,
|
||||
Convertor: api.Scheme,
|
||||
Copier: api.Scheme,
|
||||
Typer: api.Scheme,
|
||||
Linker: selfLinker,
|
||||
Storage: storage,
|
||||
Root: "/" + prefix,
|
||||
Creater: api.Scheme,
|
||||
Convertor: api.Scheme,
|
||||
Copier: api.Scheme,
|
||||
Typer: api.Scheme,
|
||||
Linker: selfLinker,
|
||||
|
||||
Admit: admissionControl,
|
||||
Context: requestContextMapper,
|
||||
@ -2472,13 +2472,12 @@ func TestParentResourceIsRequired(t *testing.T) {
|
||||
Storage: map[string]rest.Storage{
|
||||
"simple/sub": storage,
|
||||
},
|
||||
Root: "/" + prefix,
|
||||
RequestInfoResolver: newTestRequestInfoResolver(),
|
||||
Creater: api.Scheme,
|
||||
Convertor: api.Scheme,
|
||||
Copier: api.Scheme,
|
||||
Typer: api.Scheme,
|
||||
Linker: selfLinker,
|
||||
Root: "/" + prefix,
|
||||
Creater: api.Scheme,
|
||||
Convertor: api.Scheme,
|
||||
Copier: api.Scheme,
|
||||
Typer: api.Scheme,
|
||||
Linker: selfLinker,
|
||||
|
||||
Admit: admissionControl,
|
||||
Context: requestContextMapper,
|
||||
@ -2504,13 +2503,12 @@ func TestParentResourceIsRequired(t *testing.T) {
|
||||
"simple": &SimpleRESTStorage{},
|
||||
"simple/sub": storage,
|
||||
},
|
||||
Root: "/" + prefix,
|
||||
RequestInfoResolver: newTestRequestInfoResolver(),
|
||||
Creater: api.Scheme,
|
||||
Convertor: api.Scheme,
|
||||
Copier: api.Scheme,
|
||||
Typer: api.Scheme,
|
||||
Linker: selfLinker,
|
||||
Root: "/" + prefix,
|
||||
Creater: api.Scheme,
|
||||
Convertor: api.Scheme,
|
||||
Copier: api.Scheme,
|
||||
Typer: api.Scheme,
|
||||
Linker: selfLinker,
|
||||
|
||||
Admit: admissionControl,
|
||||
Context: requestContextMapper,
|
||||
@ -3131,8 +3129,6 @@ func TestXGSubresource(t *testing.T) {
|
||||
group := APIGroupVersion{
|
||||
Storage: storage,
|
||||
|
||||
RequestInfoResolver: newTestRequestInfoResolver(),
|
||||
|
||||
Creater: api.Scheme,
|
||||
Convertor: api.Scheme,
|
||||
Copier: api.Scheme,
|
||||
@ -3159,8 +3155,7 @@ func TestXGSubresource(t *testing.T) {
|
||||
panic(fmt.Sprintf("unable to install container %s: %v", group.GroupVersion, err))
|
||||
}
|
||||
|
||||
handler := defaultAPIServer{mux, container}
|
||||
server := httptest.NewServer(handler)
|
||||
server := newTestServer(defaultAPIServer{mux, container})
|
||||
defer server.Close()
|
||||
|
||||
resp, err := http.Get(server.URL + "/" + prefix + "/" + testGroupVersion.Group + "/" + testGroupVersion.Version + "/namespaces/default/simple/" + itemID + "/subsimple")
|
||||
@ -3249,3 +3244,16 @@ func BenchmarkUpdateProtobuf(b *testing.B) {
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
func newTestServer(handler http.Handler) *httptest.Server {
|
||||
handler = filters.WithRequestInfo(handler, newTestRequestInfoResolver(), requestContextMapper)
|
||||
handler = api.WithRequestContext(handler, requestContextMapper)
|
||||
return httptest.NewServer(handler)
|
||||
}
|
||||
|
||||
func newTestRequestInfoResolver() *request.RequestInfoResolver {
|
||||
return &request.RequestInfoResolver{
|
||||
APIPrefixes: sets.NewString("api", "apis"),
|
||||
GrouplessAPIPrefixes: sets.NewString("api"),
|
||||
}
|
||||
}
|
||||
|
@ -76,6 +76,13 @@ func notFound(w http.ResponseWriter, req *http.Request) {
|
||||
fmt.Fprintf(w, "Not Found: %#v", req.RequestURI)
|
||||
}
|
||||
|
||||
// internalError renders a simple internal error
|
||||
func internalError(w http.ResponseWriter, req *http.Request, err error) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprintf(w, "Internal Server Error: %#v", req.RequestURI)
|
||||
runtime.HandleError(err)
|
||||
}
|
||||
|
||||
// errAPIPrefixNotFound indicates that a RequestInfo resolution failed because the request isn't under
|
||||
// any known API prefixes
|
||||
type errAPIPrefixNotFound struct {
|
||||
|
@ -89,7 +89,11 @@ func WithAudit(handler http.Handler, attributeGetter RequestAttributeGetter, out
|
||||
return handler
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
attribs := attributeGetter.GetAttribs(req)
|
||||
attribs, err := attributeGetter.GetAttribs(req)
|
||||
if err != nil {
|
||||
internalError(w, req, err)
|
||||
return
|
||||
}
|
||||
asuser := req.Header.Get("Impersonate-User")
|
||||
if len(asuser) == 0 {
|
||||
asuser = "<self>"
|
||||
|
@ -29,9 +29,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apiserver"
|
||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
)
|
||||
|
||||
type simpleResponseWriter struct {
|
||||
@ -72,22 +71,14 @@ func (*fakeHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(200)
|
||||
}
|
||||
|
||||
type fakeRequestContextMapper struct{}
|
||||
|
||||
func (*fakeRequestContextMapper) Get(req *http.Request) (api.Context, bool) {
|
||||
return api.WithUser(api.NewContext(), &user.DefaultInfo{Name: "admin"}), true
|
||||
|
||||
}
|
||||
|
||||
func (*fakeRequestContextMapper) Update(req *http.Request, context api.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestAudit(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
attributeGetter := NewRequestAttributeGetter(&fakeRequestContextMapper{},
|
||||
&apiserver.RequestInfoResolver{APIPrefixes: sets.NewString("api", "apis"), GrouplessAPIPrefixes: sets.NewString("api")})
|
||||
|
||||
attributeGetter := NewRequestAttributeGetter(&fakeRequestContextMapper{
|
||||
user: &user.DefaultInfo{Name: "admin"},
|
||||
})
|
||||
handler := WithAudit(&fakeHTTPHandler{}, attributeGetter, &buf)
|
||||
|
||||
req, _ := http.NewRequest("GET", "/api/v1/namespaces/default/pods", nil)
|
||||
req.RemoteAddr = "127.0.0.1"
|
||||
handler.ServeHTTP(httptest.NewRecorder(), req)
|
||||
@ -110,3 +101,26 @@ func TestAudit(t *testing.T) {
|
||||
t.Errorf("Unexpected second line of audit: %s", line[1])
|
||||
}
|
||||
}
|
||||
|
||||
type fakeRequestContextMapper struct {
|
||||
user *user.DefaultInfo
|
||||
}
|
||||
|
||||
func (m *fakeRequestContextMapper) Get(req *http.Request) (api.Context, bool) {
|
||||
ctx := api.NewContext()
|
||||
if m.user != nil {
|
||||
ctx = api.WithUser(ctx, m.user)
|
||||
}
|
||||
|
||||
resolver := newTestRequestInfoResolver()
|
||||
info, err := resolver.GetRequestInfo(req)
|
||||
if err == nil {
|
||||
ctx = request.WithRequestInfo(ctx, info)
|
||||
}
|
||||
|
||||
return ctx, true
|
||||
}
|
||||
|
||||
func (*fakeRequestContextMapper) Update(req *http.Request, context api.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
@ -17,12 +17,13 @@ limitations under the License.
|
||||
package filters
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apiserver"
|
||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||
)
|
||||
|
||||
@ -33,7 +34,12 @@ func WithAuthorization(handler http.Handler, getAttribs RequestAttributeGetter,
|
||||
return handler
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
authorized, reason, err := a.Authorize(getAttribs.GetAttribs(req))
|
||||
attrs, err := getAttribs.GetAttribs(req)
|
||||
if err != nil {
|
||||
internalError(w, req, err)
|
||||
return
|
||||
}
|
||||
authorized, reason, err := a.Authorize(attrs)
|
||||
if err != nil {
|
||||
internalError(w, req, err)
|
||||
return
|
||||
@ -49,31 +55,35 @@ func WithAuthorization(handler http.Handler, getAttribs RequestAttributeGetter,
|
||||
|
||||
// RequestAttributeGetter is a function that extracts authorizer.Attributes from an http.Request
|
||||
type RequestAttributeGetter interface {
|
||||
GetAttribs(req *http.Request) (attribs authorizer.Attributes)
|
||||
GetAttribs(req *http.Request) (authorizer.Attributes, error)
|
||||
}
|
||||
|
||||
type requestAttributeGetter struct {
|
||||
requestContextMapper api.RequestContextMapper
|
||||
requestInfoResolver *apiserver.RequestInfoResolver
|
||||
}
|
||||
|
||||
// NewAttributeGetter returns an object which implements the RequestAttributeGetter interface.
|
||||
func NewRequestAttributeGetter(requestContextMapper api.RequestContextMapper, requestInfoResolver *apiserver.RequestInfoResolver) RequestAttributeGetter {
|
||||
return &requestAttributeGetter{requestContextMapper, requestInfoResolver}
|
||||
func NewRequestAttributeGetter(requestContextMapper api.RequestContextMapper) RequestAttributeGetter {
|
||||
return &requestAttributeGetter{requestContextMapper}
|
||||
}
|
||||
|
||||
func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attributes {
|
||||
func (r *requestAttributeGetter) GetAttribs(req *http.Request) (authorizer.Attributes, error) {
|
||||
attribs := authorizer.AttributesRecord{}
|
||||
|
||||
ctx, ok := r.requestContextMapper.Get(req)
|
||||
if ok {
|
||||
user, ok := api.UserFrom(ctx)
|
||||
if ok {
|
||||
attribs.User = user
|
||||
}
|
||||
if !ok {
|
||||
return nil, errors.New("no context found for request")
|
||||
}
|
||||
|
||||
requestInfo, _ := r.requestInfoResolver.GetRequestInfo(req)
|
||||
user, ok := api.UserFrom(ctx)
|
||||
if ok {
|
||||
attribs.User = user
|
||||
}
|
||||
|
||||
requestInfo, found := request.RequestInfoFrom(ctx)
|
||||
if !found {
|
||||
return nil, errors.New("no RequestInfo found in the context")
|
||||
}
|
||||
|
||||
// Start with common attributes that apply to resource and non-resource requests
|
||||
attribs.ResourceRequest = requestInfo.IsResourceRequest
|
||||
@ -87,5 +97,5 @@ func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attrib
|
||||
attribs.Namespace = requestInfo.Namespace
|
||||
attribs.Name = requestInfo.Name
|
||||
|
||||
return &attribs
|
||||
return &attribs, nil
|
||||
}
|
||||
|
@ -18,18 +18,18 @@ package filters
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/apiserver"
|
||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
)
|
||||
|
||||
func TestGetAttribs(t *testing.T) {
|
||||
r := &requestAttributeGetter{api.NewRequestContextMapper(), &apiserver.RequestInfoResolver{APIPrefixes: sets.NewString("api", "apis"), GrouplessAPIPrefixes: sets.NewString("api")}}
|
||||
mapper := api.NewRequestContextMapper()
|
||||
attributeGetter := NewRequestAttributeGetter(mapper)
|
||||
|
||||
testcases := map[string]struct {
|
||||
Verb string
|
||||
@ -103,8 +103,20 @@ func TestGetAttribs(t *testing.T) {
|
||||
|
||||
for k, tc := range testcases {
|
||||
req, _ := http.NewRequest(tc.Verb, tc.Path, nil)
|
||||
attribs := r.GetAttribs(req)
|
||||
if !reflect.DeepEqual(attribs, tc.ExpectedAttributes) {
|
||||
req.RemoteAddr = "127.0.0.1"
|
||||
|
||||
var attribs authorizer.Attributes
|
||||
var err error
|
||||
var handler http.Handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
attribs, err = attributeGetter.GetAttribs(req)
|
||||
})
|
||||
handler = WithRequestInfo(handler, newTestRequestInfoResolver(), mapper)
|
||||
handler = api.WithRequestContext(handler, mapper)
|
||||
handler.ServeHTTP(httptest.NewRecorder(), req)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("%s: unexpected error: %v", k, err)
|
||||
} else if !reflect.DeepEqual(attribs, tc.ExpectedAttributes) {
|
||||
t.Errorf("%s: expected\n\t%#v\ngot\n\t%#v", k, tc.ExpectedAttributes, attribs)
|
||||
}
|
||||
}
|
||||
|
47
pkg/apiserver/filters/requestinfo.go
Normal file
47
pkg/apiserver/filters/requestinfo.go
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
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 filters
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
||||
)
|
||||
|
||||
// WithRequestInfo attaches a RequestInfo to the context.
|
||||
func WithRequestInfo(handler http.Handler, resolver *request.RequestInfoResolver, requestContextMapper api.RequestContextMapper) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
ctx, ok := requestContextMapper.Get(req)
|
||||
if !ok {
|
||||
internalError(w, req, errors.New("no context found for request"))
|
||||
return
|
||||
}
|
||||
|
||||
info, err := resolver.GetRequestInfo(req)
|
||||
if err != nil {
|
||||
internalError(w, req, fmt.Errorf("failed to create RequestInfo: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
requestContextMapper.Update(req, request.WithRequestInfo(ctx, info))
|
||||
|
||||
handler.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
29
pkg/apiserver/filters/requestinfo_test.go
Normal file
29
pkg/apiserver/filters/requestinfo_test.go
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
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 filters
|
||||
|
||||
import (
|
||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
)
|
||||
|
||||
func newTestRequestInfoResolver() *request.RequestInfoResolver {
|
||||
return &request.RequestInfoResolver{
|
||||
APIPrefixes: sets.NewString("api", "apis"),
|
||||
GrouplessAPIPrefixes: sets.NewString("api"),
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||
package apiserver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
@ -27,7 +28,7 @@ import (
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/errors"
|
||||
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/api/rest"
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/apiserver/metrics"
|
||||
@ -38,16 +39,16 @@ import (
|
||||
proxyutil "k8s.io/kubernetes/pkg/util/proxy"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
||||
)
|
||||
|
||||
// ProxyHandler provides a http.Handler which will proxy traffic to locations
|
||||
// specified by items implementing Redirector.
|
||||
type ProxyHandler struct {
|
||||
prefix string
|
||||
storage map[string]rest.Storage
|
||||
serializer runtime.NegotiatedSerializer
|
||||
context api.RequestContextMapper
|
||||
requestInfoResolver *RequestInfoResolver
|
||||
prefix string
|
||||
storage map[string]rest.Storage
|
||||
serializer runtime.NegotiatedSerializer
|
||||
mapper api.RequestContextMapper
|
||||
}
|
||||
|
||||
func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
@ -59,8 +60,20 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
reqStart := time.Now()
|
||||
defer metrics.Monitor(&verb, &apiResource, net.GetHTTPClient(req), w.Header().Get("Content-Type"), httpCode, reqStart)
|
||||
|
||||
requestInfo, err := r.requestInfoResolver.GetRequestInfo(req)
|
||||
if err != nil || !requestInfo.IsResourceRequest {
|
||||
ctx, ok := r.mapper.Get(req)
|
||||
if !ok {
|
||||
internalError(w, req, errors.New("Error getting request context"))
|
||||
httpCode = http.StatusInternalServerError
|
||||
return
|
||||
}
|
||||
|
||||
requestInfo, ok := request.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
internalError(w, req, errors.New("Error getting RequestInfo from context"))
|
||||
httpCode = http.StatusInternalServerError
|
||||
return
|
||||
}
|
||||
if !requestInfo.IsResourceRequest {
|
||||
notFound(w, req)
|
||||
httpCode = http.StatusNotFound
|
||||
return
|
||||
@ -68,10 +81,6 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
verb = requestInfo.Verb
|
||||
namespace, resource, parts := requestInfo.Namespace, requestInfo.Resource, requestInfo.Parts
|
||||
|
||||
ctx, ok := r.context.Get(req)
|
||||
if !ok {
|
||||
ctx = api.NewContext()
|
||||
}
|
||||
ctx = api.WithNamespace(ctx, namespace)
|
||||
if len(parts) < 2 {
|
||||
notFound(w, req)
|
||||
@ -104,7 +113,7 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
redirector, ok := storage.(rest.Redirector)
|
||||
if !ok {
|
||||
httplog.LogOf(req, w).Addf("'%v' is not a redirector", resource)
|
||||
httpCode = errorNegotiated(errors.NewMethodNotSupported(api.Resource(resource), "proxy"), r.serializer, gv, w, req)
|
||||
httpCode = errorNegotiated(apierrors.NewMethodNotSupported(api.Resource(resource), "proxy"), r.serializer, gv, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -204,7 +204,7 @@ func TestProxyRequestContentLengthAndTransferEncoding(t *testing.T) {
|
||||
expectedResourceNamespace: "default",
|
||||
}
|
||||
namespaceHandler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage})
|
||||
server := httptest.NewServer(namespaceHandler)
|
||||
server := newTestServer(namespaceHandler)
|
||||
defer server.Close()
|
||||
|
||||
// Dial the proxy server
|
||||
@ -310,7 +310,7 @@ func TestProxy(t *testing.T) {
|
||||
}
|
||||
|
||||
namespaceHandler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage})
|
||||
namespaceServer := httptest.NewServer(namespaceHandler)
|
||||
namespaceServer := newTestServer(namespaceHandler)
|
||||
defer namespaceServer.Close()
|
||||
|
||||
// test each supported URL pattern for finding the redirection resource in the proxy in a particular namespace
|
||||
@ -432,7 +432,7 @@ func TestProxyUpgrade(t *testing.T) {
|
||||
|
||||
namespaceHandler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage})
|
||||
|
||||
server := httptest.NewServer(namespaceHandler)
|
||||
server := newTestServer(namespaceHandler)
|
||||
defer server.Close()
|
||||
|
||||
ws, err := websocket.Dial("ws://"+server.Listener.Addr().String()+"/"+prefix+"/"+newGroupVersion.Group+"/"+newGroupVersion.Version+"/proxy/namespaces/myns/foo/123", "", "http://127.0.0.1/")
|
||||
@ -496,7 +496,7 @@ func TestRedirectOnMissingTrailingSlash(t *testing.T) {
|
||||
}
|
||||
|
||||
handler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage})
|
||||
server := httptest.NewServer(handler)
|
||||
server := newTestServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
proxyTestPattern := "/" + prefix + "/" + newGroupVersion.Group + "/" + newGroupVersion.Version + "/proxy/namespaces/ns/foo/id" + item.path
|
||||
|
20
pkg/apiserver/request/doc.go
Normal file
20
pkg/apiserver/request/doc.go
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
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 request contains everything around extracting info from
|
||||
// a http request object.
|
||||
// TODO: this package is temporary. Handlers must move into pkg/apiserver/handlers to avoid dependency cycle
|
||||
package request // import "k8s.io/kubernetes/pkg/apiserver/request"
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package apiserver
|
||||
package request
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -68,8 +68,8 @@ var namespaceSubresources = sets.NewString("status", "finalize")
|
||||
var NamespaceSubResourcesForTest = sets.NewString(namespaceSubresources.List()...)
|
||||
|
||||
type RequestInfoResolver struct {
|
||||
APIPrefixes sets.String
|
||||
GrouplessAPIPrefixes sets.String
|
||||
APIPrefixes sets.String // without leading and trailing slashes
|
||||
GrouplessAPIPrefixes sets.String // without leading and trailing slashes
|
||||
}
|
||||
|
||||
// TODO write an integration test against the swagger doc to test the RequestInfo and match up behavior to responses
|
||||
@ -103,7 +103,7 @@ type RequestInfoResolver struct {
|
||||
// /api
|
||||
// /healthz
|
||||
// /
|
||||
func (r *RequestInfoResolver) GetRequestInfo(req *http.Request) (RequestInfo, error) {
|
||||
func (r *RequestInfoResolver) GetRequestInfo(req *http.Request) (*RequestInfo, error) {
|
||||
// start with a non-resource request until proven otherwise
|
||||
requestInfo := RequestInfo{
|
||||
IsResourceRequest: false,
|
||||
@ -114,12 +114,12 @@ func (r *RequestInfoResolver) GetRequestInfo(req *http.Request) (RequestInfo, er
|
||||
currentParts := splitPath(req.URL.Path)
|
||||
if len(currentParts) < 3 {
|
||||
// return a non-resource request
|
||||
return requestInfo, nil
|
||||
return &requestInfo, nil
|
||||
}
|
||||
|
||||
if !r.APIPrefixes.Has(currentParts[0]) {
|
||||
// return a non-resource request
|
||||
return requestInfo, nil
|
||||
return &requestInfo, nil
|
||||
}
|
||||
requestInfo.APIPrefix = currentParts[0]
|
||||
currentParts = currentParts[1:]
|
||||
@ -128,7 +128,7 @@ func (r *RequestInfoResolver) GetRequestInfo(req *http.Request) (RequestInfo, er
|
||||
// one part (APIPrefix) has already been consumed, so this is actually "do we have four parts?"
|
||||
if len(currentParts) < 3 {
|
||||
// return a non-resource request
|
||||
return requestInfo, nil
|
||||
return &requestInfo, nil
|
||||
}
|
||||
|
||||
requestInfo.APIGroup = currentParts[0]
|
||||
@ -142,7 +142,7 @@ func (r *RequestInfoResolver) GetRequestInfo(req *http.Request) (RequestInfo, er
|
||||
// handle input of form /{specialVerb}/*
|
||||
if specialVerbs.Has(currentParts[0]) {
|
||||
if len(currentParts) < 2 {
|
||||
return requestInfo, fmt.Errorf("unable to determine kind and namespace from url, %v", req.URL)
|
||||
return &requestInfo, fmt.Errorf("unable to determine kind and namespace from url, %v", req.URL)
|
||||
}
|
||||
|
||||
requestInfo.Verb = currentParts[0]
|
||||
@ -204,7 +204,25 @@ func (r *RequestInfoResolver) GetRequestInfo(req *http.Request) (RequestInfo, er
|
||||
requestInfo.Verb = "deletecollection"
|
||||
}
|
||||
|
||||
return requestInfo, nil
|
||||
return &requestInfo, nil
|
||||
}
|
||||
|
||||
type requestInfoKeyType int
|
||||
|
||||
// requestInfoKey is the RequestInfo key for the context. It's of private type here. Because
|
||||
// keys are interfaces and interfaces are equal when the type and the value is equal, this
|
||||
// does not conflict with the keys defined in pkg/api.
|
||||
const requestInfoKey requestInfoKeyType = iota
|
||||
|
||||
// WithRequestInfo returns a copy of parent in which the request info value is set
|
||||
func WithRequestInfo(parent api.Context, info *RequestInfo) api.Context {
|
||||
return api.WithValue(parent, requestInfoKey, info)
|
||||
}
|
||||
|
||||
// RequestInfoFrom returns the value of the RequestInfo key on the ctx
|
||||
func RequestInfoFrom(ctx api.Context) (*RequestInfo, bool) {
|
||||
info, ok := ctx.Value(requestInfoKey).(*RequestInfo)
|
||||
return info, ok
|
||||
}
|
||||
|
||||
// splitPath returns the segments for a URL path.
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package apiserver
|
||||
package request
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
@ -194,5 +194,8 @@ func TestGetNonAPIRequestInfo(t *testing.T) {
|
||||
}
|
||||
|
||||
func newTestRequestInfoResolver() *RequestInfoResolver {
|
||||
return &RequestInfoResolver{sets.NewString("api", "apis"), sets.NewString("api")}
|
||||
return &RequestInfoResolver{
|
||||
APIPrefixes: sets.NewString("api", "apis"),
|
||||
GrouplessAPIPrefixes: sets.NewString("api"),
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ import (
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/emicklei/go-restful"
|
||||
@ -37,6 +38,7 @@ import (
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/apiserver"
|
||||
apiserverfilters "k8s.io/kubernetes/pkg/apiserver/filters"
|
||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
||||
"k8s.io/kubernetes/pkg/auth/authenticator"
|
||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||
authhandlers "k8s.io/kubernetes/pkg/auth/handlers"
|
||||
@ -49,6 +51,7 @@ import (
|
||||
ipallocator "k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
utilnet "k8s.io/kubernetes/pkg/util/net"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
)
|
||||
|
||||
// Config is a structure used to configure a GenericAPIServer.
|
||||
@ -354,17 +357,21 @@ func (s *GenericAPIServer) buildHandlerChains(c *Config, handler http.Handler) (
|
||||
|
||||
// insecure filters
|
||||
insecure = handler
|
||||
insecure = genericfilters.WithPanicRecovery(insecure, s.NewRequestInfoResolver())
|
||||
insecure = genericfilters.WithPanicRecovery(insecure, c.RequestContextMapper)
|
||||
insecure = apiserverfilters.WithRequestInfo(insecure, NewRequestInfoResolver(c), c.RequestContextMapper)
|
||||
insecure = api.WithRequestContext(insecure, c.RequestContextMapper)
|
||||
insecure = genericfilters.WithTimeoutForNonLongRunningRequests(insecure, c.LongRunningFunc)
|
||||
|
||||
// secure filters
|
||||
attributeGetter := apiserverfilters.NewRequestAttributeGetter(c.RequestContextMapper, s.NewRequestInfoResolver())
|
||||
attributeGetter := apiserverfilters.NewRequestAttributeGetter(c.RequestContextMapper)
|
||||
secure = handler
|
||||
secure = apiserverfilters.WithAuthorization(secure, attributeGetter, c.Authorizer)
|
||||
secure = apiserverfilters.WithImpersonation(secure, c.RequestContextMapper, c.Authorizer)
|
||||
secure = apiserverfilters.WithAudit(secure, attributeGetter, c.AuditWriter) // before impersonation to read original user
|
||||
secure = authhandlers.WithAuthentication(secure, c.RequestContextMapper, c.Authenticator, authhandlers.Unauthorized(c.SupportsBasicAuth))
|
||||
secure = genericfilters.WithPanicRecovery(secure, s.NewRequestInfoResolver())
|
||||
secure = genericfilters.WithPanicRecovery(secure, c.RequestContextMapper)
|
||||
secure = apiserverfilters.WithRequestInfo(secure, NewRequestInfoResolver(c), c.RequestContextMapper)
|
||||
secure = api.WithRequestContext(secure, c.RequestContextMapper)
|
||||
secure = genericfilters.WithTimeoutForNonLongRunningRequests(secure, c.LongRunningFunc)
|
||||
secure = genericfilters.WithMaxInFlightLimit(secure, c.MaxRequestsInFlight, c.LongRunningFunc)
|
||||
|
||||
@ -436,3 +443,10 @@ func DefaultAndValidateRunOptions(options *options.ServerRunOptions) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func NewRequestInfoResolver(c *Config) *request.RequestInfoResolver {
|
||||
return &request.RequestInfoResolver{
|
||||
APIPrefixes: sets.NewString(strings.Trim(c.APIPrefix, "/"), strings.Trim(c.APIGroupPrefix, "/")), // all possible API prefixes
|
||||
GrouplessAPIPrefixes: sets.NewString(strings.Trim(c.APIPrefix, "/")), // APIPrefixes that won't have groups (legacy)
|
||||
}
|
||||
}
|
||||
|
@ -22,14 +22,15 @@ import (
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/apiserver"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
||||
"k8s.io/kubernetes/pkg/httplog"
|
||||
"k8s.io/kubernetes/pkg/util/runtime"
|
||||
)
|
||||
|
||||
// WithPanicRecovery wraps an http Handler to recover and log panics.
|
||||
func WithPanicRecovery(handler http.Handler, resolver *apiserver.RequestInfoResolver) http.Handler {
|
||||
func WithPanicRecovery(handler http.Handler, requestContextMapper api.RequestContextMapper) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
defer runtime.HandleCrash(func(err interface{}) {
|
||||
http.Error(w, "This request caused apisever to panic. Look in log for details.", http.StatusInternalServerError)
|
||||
@ -37,8 +38,19 @@ func WithPanicRecovery(handler http.Handler, resolver *apiserver.RequestInfoReso
|
||||
})
|
||||
|
||||
logger := httplog.NewLogged(req, &w)
|
||||
requestInfo, err := resolver.GetRequestInfo(req)
|
||||
if err != nil || requestInfo.Verb != "proxy" {
|
||||
|
||||
var requestInfo *request.RequestInfo
|
||||
ctx, ok := requestContextMapper.Get(req)
|
||||
if !ok {
|
||||
glog.Errorf("no context found for request, handler chain must be wrong")
|
||||
} else {
|
||||
requestInfo, ok = request.RequestInfoFrom(ctx)
|
||||
if !ok {
|
||||
glog.Errorf("no RequestInfo found in context, handler chain must be wrong")
|
||||
}
|
||||
}
|
||||
|
||||
if !ok || requestInfo.Verb != "proxy" {
|
||||
logger.StacktraceWhen(
|
||||
httplog.StatusIsNot(
|
||||
http.StatusOK,
|
||||
@ -52,12 +64,13 @@ func WithPanicRecovery(handler http.Handler, resolver *apiserver.RequestInfoReso
|
||||
http.StatusUnauthorized,
|
||||
http.StatusForbidden,
|
||||
http.StatusNotModified,
|
||||
errors.StatusUnprocessableEntity,
|
||||
apierrors.StatusUnprocessableEntity,
|
||||
http.StatusSwitchingProtocols,
|
||||
),
|
||||
)
|
||||
}
|
||||
defer logger.Log()
|
||||
|
||||
// Dispatch to the internal handler
|
||||
handler.ServeHTTP(w, req)
|
||||
})
|
||||
|
@ -49,7 +49,6 @@ import (
|
||||
certutil "k8s.io/kubernetes/pkg/util/cert"
|
||||
utilnet "k8s.io/kubernetes/pkg/util/net"
|
||||
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
)
|
||||
|
||||
// Info about an API group.
|
||||
@ -187,13 +186,6 @@ func (s *GenericAPIServer) MinRequestTimeout() time.Duration {
|
||||
return s.minRequestTimeout
|
||||
}
|
||||
|
||||
func (s *GenericAPIServer) NewRequestInfoResolver() *apiserver.RequestInfoResolver {
|
||||
return &apiserver.RequestInfoResolver{
|
||||
APIPrefixes: sets.NewString(strings.Trim(s.legacyAPIPrefix, "/"), strings.Trim(s.apiPrefix, "/")), // all possible API prefixes
|
||||
GrouplessAPIPrefixes: sets.NewString(strings.Trim(s.legacyAPIPrefix, "/")), // APIPrefixes that won't have groups (legacy)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleWithAuth adds an http.Handler for pattern to an http.ServeMux
|
||||
// Applies the same authentication and authorization (if any is configured)
|
||||
// to the request is used for the GenericAPIServer's built-in endpoints.
|
||||
@ -452,8 +444,6 @@ func (s *GenericAPIServer) getAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupV
|
||||
|
||||
func (s *GenericAPIServer) newAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupVersion unversioned.GroupVersion) (*apiserver.APIGroupVersion, error) {
|
||||
return &apiserver.APIGroupVersion{
|
||||
RequestInfoResolver: s.NewRequestInfoResolver(),
|
||||
|
||||
GroupVersion: groupVersion,
|
||||
|
||||
ParameterCodec: apiGroupInfo.ParameterCodec,
|
||||
|
@ -785,9 +785,8 @@ func (m *Master) thirdpartyapi(group, kind, version, pluralResource string) *api
|
||||
|
||||
apiRoot := extensionsrest.MakeThirdPartyPath("")
|
||||
return &apiserver.APIGroupVersion{
|
||||
Root: apiRoot,
|
||||
GroupVersion: externalVersion,
|
||||
RequestInfoResolver: m.NewRequestInfoResolver(),
|
||||
Root: apiRoot,
|
||||
GroupVersion: externalVersion,
|
||||
|
||||
Creater: thirdpartyresourcedata.NewObjectCreator(group, version, api.Scheme),
|
||||
Convertor: api.Scheme,
|
||||
|
@ -47,7 +47,8 @@ import (
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
extensionsapiv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
|
||||
"k8s.io/kubernetes/pkg/apis/rbac"
|
||||
"k8s.io/kubernetes/pkg/apiserver"
|
||||
"k8s.io/kubernetes/pkg/apiserver/request"
|
||||
"k8s.io/kubernetes/pkg/generated/openapi"
|
||||
"k8s.io/kubernetes/pkg/genericapiserver"
|
||||
"k8s.io/kubernetes/pkg/kubelet/client"
|
||||
"k8s.io/kubernetes/pkg/registry/core/endpoint"
|
||||
@ -72,7 +73,6 @@ import (
|
||||
"github.com/go-openapi/validate"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/net/context"
|
||||
"k8s.io/kubernetes/pkg/generated/openapi"
|
||||
)
|
||||
|
||||
// setUp is a convience function for setting up for (most) tests.
|
||||
@ -192,7 +192,7 @@ func TestNamespaceSubresources(t *testing.T) {
|
||||
master, etcdserver, _, _ := newMaster(t)
|
||||
defer etcdserver.Terminate(t)
|
||||
|
||||
expectedSubresources := apiserver.NamespaceSubResourcesForTest
|
||||
expectedSubresources := request.NamespaceSubResourcesForTest
|
||||
foundSubresources := sets.NewString()
|
||||
|
||||
for k := range master.v1ResourcesStorage {
|
||||
|
Loading…
Reference in New Issue
Block a user