mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 12:43:23 +00:00
Separate apiserver handler filters
This commit is contained in:
parent
a2f943e7c1
commit
6dc2f6337b
@ -27,7 +27,6 @@ import (
|
||||
"path"
|
||||
rt "runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/admission"
|
||||
@ -210,22 +209,6 @@ func logStackOnRecover(s runtime.NegotiatedSerializer, panicReason interface{},
|
||||
errorNegotiated(apierrors.NewGenericServerResponse(http.StatusInternalServerError, "", api.Resource(""), "", "", 0, false), s, unversioned.GroupVersion{}, w, &http.Request{Header: headers})
|
||||
}
|
||||
|
||||
func InstallServiceErrorHandler(s runtime.NegotiatedSerializer, container *restful.Container) {
|
||||
container.ServiceErrorHandler(func(serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) {
|
||||
serviceErrorHandler(s, serviceErr, request, response)
|
||||
})
|
||||
}
|
||||
|
||||
func serviceErrorHandler(s runtime.NegotiatedSerializer, serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) {
|
||||
errorNegotiated(
|
||||
apierrors.NewGenericServerResponse(serviceErr.Code, "", api.Resource(""), "", serviceErr.Message, 0, false),
|
||||
s,
|
||||
unversioned.GroupVersion{},
|
||||
response.ResponseWriter,
|
||||
request.Request,
|
||||
)
|
||||
}
|
||||
|
||||
// Adds a service to return the supported api versions at the legacy /api.
|
||||
func AddApiWebService(s runtime.NegotiatedSerializer, container *restful.Container, apiPrefix string, getAPIVersionsFunc func(req *restful.Request) *unversioned.APIVersions) {
|
||||
// TODO: InstallREST should register each version automatically
|
||||
@ -501,12 +484,3 @@ func readBody(req *http.Request) ([]byte, error) {
|
||||
defer req.Body.Close()
|
||||
return ioutil.ReadAll(req.Body)
|
||||
}
|
||||
|
||||
// splitPath returns the segments for a URL path.
|
||||
func splitPath(path string) []string {
|
||||
path = strings.Trim(path, "/")
|
||||
if path == "" {
|
||||
return []string{}
|
||||
}
|
||||
return strings.Split(path, "/")
|
||||
}
|
||||
|
@ -44,7 +44,6 @@ import (
|
||||
"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"
|
||||
@ -252,10 +251,6 @@ func handleLinker(storage map[string]rest.Storage, selfLinker runtime.SelfLinker
|
||||
return handleInternal(storage, admissionControl, selfLinker)
|
||||
}
|
||||
|
||||
func newTestRequestInfoResolver() *RequestInfoResolver {
|
||||
return &RequestInfoResolver{sets.NewString("api", "apis"), sets.NewString("api")}
|
||||
}
|
||||
|
||||
func handleInternal(storage map[string]rest.Storage, admissionControl admission.Interface, selfLinker runtime.SelfLinker) http.Handler {
|
||||
container := restful.NewContainer()
|
||||
container.Router(restful.CurlyRouter{})
|
||||
|
@ -76,25 +76,6 @@ func notFound(w http.ResponseWriter, req *http.Request) {
|
||||
fmt.Fprintf(w, "Not Found: %#v", req.RequestURI)
|
||||
}
|
||||
|
||||
// badGatewayError renders a simple bad gateway error.
|
||||
func badGatewayError(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
fmt.Fprintf(w, "Bad Gateway: %#v", req.RequestURI)
|
||||
}
|
||||
|
||||
// forbidden renders a simple forbidden error
|
||||
func forbidden(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
fmt.Fprintf(w, "Forbidden: %#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 {
|
||||
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package audit
|
||||
package filters
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@ -27,7 +27,6 @@ import (
|
||||
"github.com/golang/glog"
|
||||
"github.com/pborman/uuid"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apiserver"
|
||||
utilnet "k8s.io/kubernetes/pkg/util/net"
|
||||
)
|
||||
|
||||
@ -85,7 +84,7 @@ var _ http.Hijacker = &fancyResponseWriterDelegator{}
|
||||
// 2. the response line containing:
|
||||
// - the unique id from 1
|
||||
// - response code
|
||||
func WithAudit(handler http.Handler, attributeGetter apiserver.RequestAttributeGetter, out io.Writer) http.Handler {
|
||||
func WithAudit(handler http.Handler, attributeGetter RequestAttributeGetter, out io.Writer) http.Handler {
|
||||
if out == nil {
|
||||
return handler
|
||||
}
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package audit
|
||||
package filters
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@ -85,7 +85,7 @@ func (*fakeRequestContextMapper) Update(req *http.Request, context api.Context)
|
||||
|
||||
func TestAudit(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
attributeGetter := apiserver.NewRequestAttributeGetter(&fakeRequestContextMapper{},
|
||||
attributeGetter := NewRequestAttributeGetter(&fakeRequestContextMapper{},
|
||||
&apiserver.RequestInfoResolver{APIPrefixes: sets.NewString("api", "apis"), GrouplessAPIPrefixes: sets.NewString("api")})
|
||||
handler := WithAudit(&fakeHTTPHandler{}, attributeGetter, &buf)
|
||||
req, _ := http.NewRequest("GET", "/api/v1/namespaces/default/pods", nil)
|
91
pkg/apiserver/filters/authorization.go
Normal file
91
pkg/apiserver/filters/authorization.go
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
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 (
|
||||
"net/http"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apiserver"
|
||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||
)
|
||||
|
||||
// WithAuthorizationCheck passes all authorized requests on to handler, and returns a forbidden error otherwise.
|
||||
func WithAuthorization(handler http.Handler, getAttribs RequestAttributeGetter, a authorizer.Authorizer) http.Handler {
|
||||
if a == nil {
|
||||
glog.Warningf("Authorization is disabled")
|
||||
return handler
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
authorized, reason, err := a.Authorize(getAttribs.GetAttribs(req))
|
||||
if err != nil {
|
||||
internalError(w, req, err)
|
||||
return
|
||||
}
|
||||
if !authorized {
|
||||
glog.V(4).Infof("Forbidden: %#v, Reason: %s", req.RequestURI, reason)
|
||||
forbidden(w, req)
|
||||
return
|
||||
}
|
||||
handler.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
|
||||
// RequestAttributeGetter is a function that extracts authorizer.Attributes from an http.Request
|
||||
type RequestAttributeGetter interface {
|
||||
GetAttribs(req *http.Request) (attribs authorizer.Attributes)
|
||||
}
|
||||
|
||||
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 (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attributes {
|
||||
attribs := authorizer.AttributesRecord{}
|
||||
|
||||
ctx, ok := r.requestContextMapper.Get(req)
|
||||
if ok {
|
||||
user, ok := api.UserFrom(ctx)
|
||||
if ok {
|
||||
attribs.User = user
|
||||
}
|
||||
}
|
||||
|
||||
requestInfo, _ := r.requestInfoResolver.GetRequestInfo(req)
|
||||
|
||||
// Start with common attributes that apply to resource and non-resource requests
|
||||
attribs.ResourceRequest = requestInfo.IsResourceRequest
|
||||
attribs.Path = requestInfo.Path
|
||||
attribs.Verb = requestInfo.Verb
|
||||
|
||||
attribs.APIGroup = requestInfo.APIGroup
|
||||
attribs.APIVersion = requestInfo.APIVersion
|
||||
attribs.Resource = requestInfo.Resource
|
||||
attribs.Subresource = requestInfo.Subresource
|
||||
attribs.Namespace = requestInfo.Namespace
|
||||
attribs.Name = requestInfo.Name
|
||||
|
||||
return &attribs
|
||||
}
|
111
pkg/apiserver/filters/authorization_test.go
Normal file
111
pkg/apiserver/filters/authorization_test.go
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
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 (
|
||||
"net/http"
|
||||
"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")}}
|
||||
|
||||
testcases := map[string]struct {
|
||||
Verb string
|
||||
Path string
|
||||
ExpectedAttributes *authorizer.AttributesRecord
|
||||
}{
|
||||
"non-resource root": {
|
||||
Verb: "POST",
|
||||
Path: "/",
|
||||
ExpectedAttributes: &authorizer.AttributesRecord{
|
||||
Verb: "post",
|
||||
Path: "/",
|
||||
},
|
||||
},
|
||||
"non-resource api prefix": {
|
||||
Verb: "GET",
|
||||
Path: "/api/",
|
||||
ExpectedAttributes: &authorizer.AttributesRecord{
|
||||
Verb: "get",
|
||||
Path: "/api/",
|
||||
},
|
||||
},
|
||||
"non-resource group api prefix": {
|
||||
Verb: "GET",
|
||||
Path: "/apis/extensions/",
|
||||
ExpectedAttributes: &authorizer.AttributesRecord{
|
||||
Verb: "get",
|
||||
Path: "/apis/extensions/",
|
||||
},
|
||||
},
|
||||
|
||||
"resource": {
|
||||
Verb: "POST",
|
||||
Path: "/api/v1/nodes/mynode",
|
||||
ExpectedAttributes: &authorizer.AttributesRecord{
|
||||
Verb: "create",
|
||||
Path: "/api/v1/nodes/mynode",
|
||||
ResourceRequest: true,
|
||||
Resource: "nodes",
|
||||
APIVersion: "v1",
|
||||
Name: "mynode",
|
||||
},
|
||||
},
|
||||
"namespaced resource": {
|
||||
Verb: "PUT",
|
||||
Path: "/api/v1/namespaces/myns/pods/mypod",
|
||||
ExpectedAttributes: &authorizer.AttributesRecord{
|
||||
Verb: "update",
|
||||
Path: "/api/v1/namespaces/myns/pods/mypod",
|
||||
ResourceRequest: true,
|
||||
Namespace: "myns",
|
||||
Resource: "pods",
|
||||
APIVersion: "v1",
|
||||
Name: "mypod",
|
||||
},
|
||||
},
|
||||
"API group resource": {
|
||||
Verb: "GET",
|
||||
Path: "/apis/extensions/v1beta1/namespaces/myns/jobs",
|
||||
ExpectedAttributes: &authorizer.AttributesRecord{
|
||||
Verb: "list",
|
||||
Path: "/apis/extensions/v1beta1/namespaces/myns/jobs",
|
||||
ResourceRequest: true,
|
||||
APIGroup: extensions.GroupName,
|
||||
APIVersion: "v1beta1",
|
||||
Namespace: "myns",
|
||||
Resource: "jobs",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range testcases {
|
||||
req, _ := http.NewRequest(tc.Verb, tc.Path, nil)
|
||||
attribs := r.GetAttribs(req)
|
||||
if !reflect.DeepEqual(attribs, tc.ExpectedAttributes) {
|
||||
t.Errorf("%s: expected\n\t%#v\ngot\n\t%#v", k, tc.ExpectedAttributes, attribs)
|
||||
}
|
||||
}
|
||||
}
|
19
pkg/apiserver/filters/doc.go
Normal file
19
pkg/apiserver/filters/doc.go
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
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 contains all the http handler chain filters which
|
||||
// _are_ api related.
|
||||
package filters // import "k8s.io/kubernetes/pkg/apiserver/filters"
|
43
pkg/apiserver/filters/errors.go
Executable file
43
pkg/apiserver/filters/errors.go
Executable file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright 2014 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 (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"k8s.io/kubernetes/pkg/util/runtime"
|
||||
)
|
||||
|
||||
// badGatewayError renders a simple bad gateway error.
|
||||
func badGatewayError(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusBadGateway)
|
||||
fmt.Fprintf(w, "Bad Gateway: %#v", req.RequestURI)
|
||||
}
|
||||
|
||||
// forbidden renders a simple forbidden error
|
||||
func forbidden(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
fmt.Fprintf(w, "Forbidden: %#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)
|
||||
}
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package apiserver
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package apiserver
|
||||
package filters
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -264,7 +264,7 @@ func TestImpersonationFilter(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
requestContextMapper = api.NewRequestContextMapper()
|
||||
requestContextMapper := api.NewRequestContextMapper()
|
||||
var ctx api.Context
|
||||
var actualUser user.Info
|
||||
var lock sync.Mutex
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
Copyright 2015 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.
|
||||
@ -21,115 +21,10 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
)
|
||||
|
||||
// specialVerbs contains just strings which are used in REST paths for special actions that don't fall under the normal
|
||||
// CRUDdy GET/POST/PUT/DELETE actions on REST objects.
|
||||
// TODO: find a way to keep this up to date automatically. Maybe dynamically populate list as handlers added to
|
||||
// master's Mux.
|
||||
var specialVerbs = sets.NewString("proxy", "redirect", "watch")
|
||||
|
||||
// specialVerbsNoSubresources contains root verbs which do not allow subresources
|
||||
var specialVerbsNoSubresources = sets.NewString("proxy", "redirect")
|
||||
|
||||
// namespaceSubresources contains subresources of namespace
|
||||
// this list allows the parser to distinguish between a namespace subresource, and a namespaced resource
|
||||
var namespaceSubresources = sets.NewString("status", "finalize")
|
||||
|
||||
// NamespaceSubResourcesForTest exports namespaceSubresources for testing in pkg/master/master_test.go, so we never drift
|
||||
var NamespaceSubResourcesForTest = sets.NewString(namespaceSubresources.List()...)
|
||||
|
||||
// IsReadOnlyReq() is true for any (or at least many) request which has no observable
|
||||
// side effects on state of apiserver (though there may be internal side effects like
|
||||
// caching and logging).
|
||||
func IsReadOnlyReq(req http.Request) bool {
|
||||
if req.Method == "GET" {
|
||||
// TODO: add OPTIONS and HEAD if we ever support those.
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ReadOnly passes all GET requests on to handler, and returns an error on all other requests.
|
||||
func ReadOnly(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if IsReadOnlyReq(*req) {
|
||||
handler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
fmt.Fprintf(w, "This is a read-only endpoint.")
|
||||
})
|
||||
}
|
||||
|
||||
// RequestAttributeGetter is a function that extracts authorizer.Attributes from an http.Request
|
||||
type RequestAttributeGetter interface {
|
||||
GetAttribs(req *http.Request) (attribs authorizer.Attributes)
|
||||
}
|
||||
|
||||
type requestAttributeGetter struct {
|
||||
requestContextMapper api.RequestContextMapper
|
||||
requestInfoResolver *RequestInfoResolver
|
||||
}
|
||||
|
||||
// NewAttributeGetter returns an object which implements the RequestAttributeGetter interface.
|
||||
func NewRequestAttributeGetter(requestContextMapper api.RequestContextMapper, requestInfoResolver *RequestInfoResolver) RequestAttributeGetter {
|
||||
return &requestAttributeGetter{requestContextMapper, requestInfoResolver}
|
||||
}
|
||||
|
||||
func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attributes {
|
||||
attribs := authorizer.AttributesRecord{}
|
||||
|
||||
ctx, ok := r.requestContextMapper.Get(req)
|
||||
if ok {
|
||||
user, ok := api.UserFrom(ctx)
|
||||
if ok {
|
||||
attribs.User = user
|
||||
}
|
||||
}
|
||||
|
||||
requestInfo, _ := r.requestInfoResolver.GetRequestInfo(req)
|
||||
|
||||
// Start with common attributes that apply to resource and non-resource requests
|
||||
attribs.ResourceRequest = requestInfo.IsResourceRequest
|
||||
attribs.Path = requestInfo.Path
|
||||
attribs.Verb = requestInfo.Verb
|
||||
|
||||
attribs.APIGroup = requestInfo.APIGroup
|
||||
attribs.APIVersion = requestInfo.APIVersion
|
||||
attribs.Resource = requestInfo.Resource
|
||||
attribs.Subresource = requestInfo.Subresource
|
||||
attribs.Namespace = requestInfo.Namespace
|
||||
attribs.Name = requestInfo.Name
|
||||
|
||||
return &attribs
|
||||
}
|
||||
|
||||
// WithAuthorizationCheck passes all authorized requests on to handler, and returns a forbidden error otherwise.
|
||||
func WithAuthorization(handler http.Handler, getAttribs RequestAttributeGetter, a authorizer.Authorizer) http.Handler {
|
||||
if a == nil {
|
||||
glog.Warningf("Authorization is disabled")
|
||||
return handler
|
||||
}
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
authorized, reason, err := a.Authorize(getAttribs.GetAttribs(req))
|
||||
if err != nil {
|
||||
internalError(w, req, err)
|
||||
return
|
||||
}
|
||||
if !authorized {
|
||||
glog.V(4).Infof("Forbidden: %#v, Reason: %s", req.RequestURI, reason)
|
||||
forbidden(w, req)
|
||||
return
|
||||
}
|
||||
handler.ServeHTTP(w, req)
|
||||
})
|
||||
}
|
||||
|
||||
// RequestInfo holds information parsed from the http.Request
|
||||
type RequestInfo struct {
|
||||
// IsResourceRequest indicates whether or not the request is for an API resource or subresource
|
||||
@ -156,6 +51,22 @@ type RequestInfo struct {
|
||||
Parts []string
|
||||
}
|
||||
|
||||
// specialVerbs contains just strings which are used in REST paths for special actions that don't fall under the normal
|
||||
// CRUDdy GET/POST/PUT/DELETE actions on REST objects.
|
||||
// TODO: find a way to keep this up to date automatically. Maybe dynamically populate list as handlers added to
|
||||
// master's Mux.
|
||||
var specialVerbs = sets.NewString("proxy", "redirect", "watch")
|
||||
|
||||
// specialVerbsNoSubresources contains root verbs which do not allow subresources
|
||||
var specialVerbsNoSubresources = sets.NewString("proxy", "redirect")
|
||||
|
||||
// namespaceSubresources contains subresources of namespace
|
||||
// this list allows the parser to distinguish between a namespace subresource, and a namespaced resource
|
||||
var namespaceSubresources = sets.NewString("status", "finalize")
|
||||
|
||||
// NamespaceSubResourcesForTest exports namespaceSubresources for testing in pkg/master/master_test.go, so we never drift
|
||||
var NamespaceSubResourcesForTest = sets.NewString(namespaceSubresources.List()...)
|
||||
|
||||
type RequestInfoResolver struct {
|
||||
APIPrefixes sets.String
|
||||
GrouplessAPIPrefixes sets.String
|
||||
@ -295,3 +206,12 @@ func (r *RequestInfoResolver) GetRequestInfo(req *http.Request) (RequestInfo, er
|
||||
|
||||
return requestInfo, nil
|
||||
}
|
||||
|
||||
// splitPath returns the segments for a URL path.
|
||||
func splitPath(path string) []string {
|
||||
path = strings.Trim(path, "/")
|
||||
if path == "" {
|
||||
return []string{}
|
||||
}
|
||||
return strings.Split(path, "/")
|
||||
}
|
@ -18,14 +18,11 @@ package apiserver
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
)
|
||||
|
||||
@ -43,106 +40,6 @@ func pathWithPrefix(prefix, resource, namespace, name string) string {
|
||||
return testapi.Default.ResourcePathWithPrefix(prefix, resource, namespace, name)
|
||||
}
|
||||
|
||||
func TestReadOnly(t *testing.T) {
|
||||
server := httptest.NewServer(ReadOnly(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, req *http.Request) {
|
||||
if req.Method != "GET" {
|
||||
t.Errorf("Unexpected call: %v", req.Method)
|
||||
}
|
||||
},
|
||||
)))
|
||||
defer server.Close()
|
||||
for _, verb := range []string{"GET", "POST", "PUT", "DELETE", "CREATE"} {
|
||||
req, err := http.NewRequest(verb, server.URL, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't make request: %v", err)
|
||||
}
|
||||
http.DefaultClient.Do(req)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAttribs(t *testing.T) {
|
||||
r := &requestAttributeGetter{api.NewRequestContextMapper(), &RequestInfoResolver{sets.NewString("api", "apis"), sets.NewString("api")}}
|
||||
|
||||
testcases := map[string]struct {
|
||||
Verb string
|
||||
Path string
|
||||
ExpectedAttributes *authorizer.AttributesRecord
|
||||
}{
|
||||
"non-resource root": {
|
||||
Verb: "POST",
|
||||
Path: "/",
|
||||
ExpectedAttributes: &authorizer.AttributesRecord{
|
||||
Verb: "post",
|
||||
Path: "/",
|
||||
},
|
||||
},
|
||||
"non-resource api prefix": {
|
||||
Verb: "GET",
|
||||
Path: "/api/",
|
||||
ExpectedAttributes: &authorizer.AttributesRecord{
|
||||
Verb: "get",
|
||||
Path: "/api/",
|
||||
},
|
||||
},
|
||||
"non-resource group api prefix": {
|
||||
Verb: "GET",
|
||||
Path: "/apis/extensions/",
|
||||
ExpectedAttributes: &authorizer.AttributesRecord{
|
||||
Verb: "get",
|
||||
Path: "/apis/extensions/",
|
||||
},
|
||||
},
|
||||
|
||||
"resource": {
|
||||
Verb: "POST",
|
||||
Path: "/api/v1/nodes/mynode",
|
||||
ExpectedAttributes: &authorizer.AttributesRecord{
|
||||
Verb: "create",
|
||||
Path: "/api/v1/nodes/mynode",
|
||||
ResourceRequest: true,
|
||||
Resource: "nodes",
|
||||
APIVersion: "v1",
|
||||
Name: "mynode",
|
||||
},
|
||||
},
|
||||
"namespaced resource": {
|
||||
Verb: "PUT",
|
||||
Path: "/api/v1/namespaces/myns/pods/mypod",
|
||||
ExpectedAttributes: &authorizer.AttributesRecord{
|
||||
Verb: "update",
|
||||
Path: "/api/v1/namespaces/myns/pods/mypod",
|
||||
ResourceRequest: true,
|
||||
Namespace: "myns",
|
||||
Resource: "pods",
|
||||
APIVersion: "v1",
|
||||
Name: "mypod",
|
||||
},
|
||||
},
|
||||
"API group resource": {
|
||||
Verb: "GET",
|
||||
Path: "/apis/extensions/v1beta1/namespaces/myns/jobs",
|
||||
ExpectedAttributes: &authorizer.AttributesRecord{
|
||||
Verb: "list",
|
||||
Path: "/apis/extensions/v1beta1/namespaces/myns/jobs",
|
||||
ResourceRequest: true,
|
||||
APIGroup: extensions.GroupName,
|
||||
APIVersion: "v1beta1",
|
||||
Namespace: "myns",
|
||||
Resource: "jobs",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range testcases {
|
||||
req, _ := http.NewRequest(tc.Verb, tc.Path, nil)
|
||||
attribs := r.GetAttribs(req)
|
||||
if !reflect.DeepEqual(attribs, tc.ExpectedAttributes) {
|
||||
t.Errorf("%s: expected\n\t%#v\ngot\n\t%#v", k, tc.ExpectedAttributes, attribs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAPIRequestInfo(t *testing.T) {
|
||||
successCases := []struct {
|
||||
method string
|
||||
@ -202,12 +99,12 @@ func TestGetAPIRequestInfo(t *testing.T) {
|
||||
{"POST", "/apis/extensions/v1beta3/namespaces/other/pods", "create", "api", "extensions", "v1beta3", "other", "pods", "", "", []string{"pods"}},
|
||||
}
|
||||
|
||||
requestInfoResolver := newTestRequestInfoResolver()
|
||||
resolver := newTestRequestInfoResolver()
|
||||
|
||||
for _, successCase := range successCases {
|
||||
req, _ := http.NewRequest(successCase.method, successCase.url, nil)
|
||||
|
||||
apiRequestInfo, err := requestInfoResolver.GetRequestInfo(req)
|
||||
apiRequestInfo, err := resolver.GetRequestInfo(req)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for url: %s %v", successCase.url, err)
|
||||
}
|
||||
@ -250,7 +147,7 @@ func TestGetAPIRequestInfo(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error %v", err)
|
||||
}
|
||||
apiRequestInfo, err := requestInfoResolver.GetRequestInfo(req)
|
||||
apiRequestInfo, err := resolver.GetRequestInfo(req)
|
||||
if err != nil {
|
||||
t.Errorf("%s: Unexpected error %v", k, err)
|
||||
}
|
||||
@ -281,12 +178,12 @@ func TestGetNonAPIRequestInfo(t *testing.T) {
|
||||
"empty": {"", false},
|
||||
}
|
||||
|
||||
requestInfoResolver := newTestRequestInfoResolver()
|
||||
resolver := newTestRequestInfoResolver()
|
||||
|
||||
for testName, tc := range tests {
|
||||
req, _ := http.NewRequest("GET", tc.url, nil)
|
||||
|
||||
apiRequestInfo, err := requestInfoResolver.GetRequestInfo(req)
|
||||
apiRequestInfo, err := resolver.GetRequestInfo(req)
|
||||
if err != nil {
|
||||
t.Errorf("%s: Unexpected error %v", testName, err)
|
||||
}
|
||||
@ -295,3 +192,7 @@ func TestGetNonAPIRequestInfo(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newTestRequestInfoResolver() *RequestInfoResolver {
|
||||
return &RequestInfoResolver{sets.NewString("api", "apis"), sets.NewString("api")}
|
||||
}
|
42
pkg/apiserver/serviceerror.go
Normal file
42
pkg/apiserver/serviceerror.go
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
Copyright 2014 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 apiserver
|
||||
|
||||
import (
|
||||
"github.com/emicklei/go-restful"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
func InstallServiceErrorHandler(s runtime.NegotiatedSerializer, container *restful.Container) {
|
||||
container.ServiceErrorHandler(func(serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) {
|
||||
serviceErrorHandler(s, serviceErr, request, response)
|
||||
})
|
||||
}
|
||||
|
||||
func serviceErrorHandler(s runtime.NegotiatedSerializer, serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) {
|
||||
errorNegotiated(
|
||||
apierrors.NewGenericServerResponse(serviceErr.Code, "", api.Resource(""), "", serviceErr.Message, 0, false),
|
||||
s,
|
||||
unversioned.GroupVersion{},
|
||||
response.ResponseWriter,
|
||||
request.Request,
|
||||
)
|
||||
}
|
@ -36,7 +36,7 @@ import (
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/apiserver"
|
||||
"k8s.io/kubernetes/pkg/apiserver/audit"
|
||||
apiserverfilters "k8s.io/kubernetes/pkg/apiserver/filters"
|
||||
"k8s.io/kubernetes/pkg/auth/authenticator"
|
||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||
authhandlers "k8s.io/kubernetes/pkg/auth/handlers"
|
||||
@ -363,18 +363,16 @@ func (s *GenericAPIServer) buildHandlerChains(c *Config, handler http.Handler) (
|
||||
|
||||
// insecure filters
|
||||
insecure = handler
|
||||
insecure = api.WithRequestContext(insecure, c.RequestContextMapper)
|
||||
insecure = genericfilters.WithPanicRecovery(insecure, s.NewRequestInfoResolver())
|
||||
insecure = genericfilters.WithTimeoutForNonLongRunningRequests(insecure, longRunningFunc)
|
||||
|
||||
// secure filters
|
||||
attributeGetter := apiserver.NewRequestAttributeGetter(c.RequestContextMapper, s.NewRequestInfoResolver())
|
||||
attributeGetter := apiserverfilters.NewRequestAttributeGetter(c.RequestContextMapper, s.NewRequestInfoResolver())
|
||||
secure = handler
|
||||
secure = apiserver.WithAuthorization(secure, attributeGetter, c.Authorizer)
|
||||
secure = apiserver.WithImpersonation(secure, c.RequestContextMapper, c.Authorizer)
|
||||
secure = audit.WithAudit(secure, attributeGetter, s.auditWriter) // before impersonation to read original user
|
||||
secure = apiserverfilters.WithAuthorization(secure, attributeGetter, c.Authorizer)
|
||||
secure = apiserverfilters.WithImpersonation(secure, c.RequestContextMapper, c.Authorizer)
|
||||
secure = apiserverfilters.WithAudit(secure, attributeGetter, s.auditWriter) // before impersonation to read original user
|
||||
secure = authhandlers.WithAuthentication(secure, c.RequestContextMapper, c.Authenticator, authhandlers.Unauthorized(c.SupportsBasicAuth))
|
||||
secure = api.WithRequestContext(secure, c.RequestContextMapper)
|
||||
secure = genericfilters.WithPanicRecovery(secure, s.NewRequestInfoResolver())
|
||||
secure = genericfilters.WithTimeoutForNonLongRunningRequests(secure, longRunningFunc)
|
||||
secure = genericfilters.WithMaxInFlightLimit(secure, c.MaxRequestsInFlight, longRunningFunc)
|
||||
|
Loading…
Reference in New Issue
Block a user