Commit d40613a8 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #33478 from sttts/sttts-separate-apiserver-filters

Automatic merge from submit-queue Separate handler filters in pkg/apiserver into pkg/apiserver/filters ... in order to make dependencies explicit towards better composability.
parents 294c9aa6 6dc2f633
......@@ -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)
......
/*
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
}
/*
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)
}
}
}
/*
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"
/*
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
......
/*
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.
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 (
"fmt"
"net/http"
"strings"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/util/sets"
)
// 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
IsResourceRequest bool
// Path is the URL path of the request
Path string
// Verb is the kube verb associated with the request for API requests, not the http verb. This includes things like list and watch.
// for non-resource requests, this is the lowercase http verb
Verb string
APIPrefix string
APIGroup string
APIVersion string
Namespace string
// Resource is the name of the resource being requested. This is not the kind. For example: pods
Resource string
// Subresource is the name of the subresource being requested. This is a different resource, scoped to the parent resource, but it may have a different kind.
// For instance, /pods has the resource "pods" and the kind "Pod", while /pods/foo/status has the resource "pods", the sub resource "status", and the kind "Pod"
// (because status operates on pods). The binding resource for a pod though may be /pods/foo/binding, which has resource "pods", subresource "binding", and kind "Binding".
Subresource string
// Name is empty for some verbs, but if the request directly indicates a name (not in body content) then this field is filled in.
Name string
// Parts are the path parts for the request, always starting with /{resource}/{name}
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
}
// TODO write an integration test against the swagger doc to test the RequestInfo and match up behavior to responses
// GetRequestInfo returns the information from the http request. If error is not nil, RequestInfo holds the information as best it is known before the failure
// It handles both resource and non-resource requests and fills in all the pertinent information for each.
// Valid Inputs:
// Resource paths
// /apis/{api-group}/{version}/namespaces
// /api/{version}/namespaces
// /api/{version}/namespaces/{namespace}
// /api/{version}/namespaces/{namespace}/{resource}
// /api/{version}/namespaces/{namespace}/{resource}/{resourceName}
// /api/{version}/{resource}
// /api/{version}/{resource}/{resourceName}
//
// Special verbs without subresources:
// /api/{version}/proxy/{resource}/{resourceName}
// /api/{version}/proxy/namespaces/{namespace}/{resource}/{resourceName}
// /api/{version}/redirect/namespaces/{namespace}/{resource}/{resourceName}
// /api/{version}/redirect/{resource}/{resourceName}
//
// Special verbs with subresources:
// /api/{version}/watch/{resource}
// /api/{version}/watch/namespaces/{namespace}/{resource}
//
// NonResource paths
// /apis/{api-group}/{version}
// /apis/{api-group}
// /apis
// /api/{version}
// /api
// /healthz
// /
func (r *RequestInfoResolver) GetRequestInfo(req *http.Request) (RequestInfo, error) {
// start with a non-resource request until proven otherwise
requestInfo := RequestInfo{
IsResourceRequest: false,
Path: req.URL.Path,
Verb: strings.ToLower(req.Method),
}
currentParts := splitPath(req.URL.Path)
if len(currentParts) < 3 {
// return a non-resource request
return requestInfo, nil
}
if !r.APIPrefixes.Has(currentParts[0]) {
// return a non-resource request
return requestInfo, nil
}
requestInfo.APIPrefix = currentParts[0]
currentParts = currentParts[1:]
if !r.GrouplessAPIPrefixes.Has(requestInfo.APIPrefix) {
// one part (APIPrefix) has already been consumed, so this is actually "do we have four parts?"
if len(currentParts) < 3 {
// return a non-resource request
return requestInfo, nil
}
requestInfo.APIGroup = currentParts[0]
currentParts = currentParts[1:]
}
requestInfo.IsResourceRequest = true
requestInfo.APIVersion = currentParts[0]
currentParts = currentParts[1:]
// 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)
}
requestInfo.Verb = currentParts[0]
currentParts = currentParts[1:]
} else {
switch req.Method {
case "POST":
requestInfo.Verb = "create"
case "GET", "HEAD":
requestInfo.Verb = "get"
case "PUT":
requestInfo.Verb = "update"
case "PATCH":
requestInfo.Verb = "patch"
case "DELETE":
requestInfo.Verb = "delete"
default:
requestInfo.Verb = ""
}
}
// URL forms: /namespaces/{namespace}/{kind}/*, where parts are adjusted to be relative to kind
if currentParts[0] == "namespaces" {
if len(currentParts) > 1 {
requestInfo.Namespace = currentParts[1]
// if there is another step after the namespace name and it is not a known namespace subresource
// move currentParts to include it as a resource in its own right
if len(currentParts) > 2 && !namespaceSubresources.Has(currentParts[2]) {
currentParts = currentParts[2:]
}
}
} else {
requestInfo.Namespace = api.NamespaceNone
}
// parsing successful, so we now know the proper value for .Parts
requestInfo.Parts = currentParts
// parts look like: resource/resourceName/subresource/other/stuff/we/don't/interpret
switch {
case len(requestInfo.Parts) >= 3 && !specialVerbsNoSubresources.Has(requestInfo.Verb):
requestInfo.Subresource = requestInfo.Parts[2]
fallthrough
case len(requestInfo.Parts) >= 2:
requestInfo.Name = requestInfo.Parts[1]
fallthrough
case len(requestInfo.Parts) >= 1:
requestInfo.Resource = requestInfo.Parts[0]
}
// if there's no name on the request and we thought it was a get before, then the actual verb is a list
if len(requestInfo.Name) == 0 && requestInfo.Verb == "get" {
requestInfo.Verb = "list"
}
// if there's no name on the request and we thought it was a delete before, then the actual verb is deletecollection
if len(requestInfo.Name) == 0 && requestInfo.Verb == "delete" {
requestInfo.Verb = "deletecollection"
}
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")}
}
/*
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,19 +363,17 @@ func (s *GenericAPIServer) buildHandlerChains(c *Config, handler http.Handler) (
// insecure filters
insecure = handler
insecure = api.WithRequestContext(insecure, c.RequestContextMapper)
insecure = apiserver.RecoverPanics(insecure, s.NewRequestInfoResolver())
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 = apiserver.RecoverPanics(secure, s.NewRequestInfoResolver())
secure = genericfilters.WithPanicRecovery(secure, s.NewRequestInfoResolver())
secure = genericfilters.WithTimeoutForNonLongRunningRequests(secure, longRunningFunc)
secure = genericfilters.WithMaxInFlightLimit(secure, c.MaxRequestsInFlight, longRunningFunc)
......
/*
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"
"runtime/debug"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/apiserver"
"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 {
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)
glog.Errorf("APIServer panic'd on %v %v: %v\n%s\n", req.Method, req.RequestURI, err, debug.Stack())
})
logger := httplog.NewLogged(req, &w)
requestInfo, err := resolver.GetRequestInfo(req)
if err != nil || requestInfo.Verb != "proxy" {
logger.StacktraceWhen(
httplog.StatusIsNot(
http.StatusOK,
http.StatusCreated,
http.StatusAccepted,
http.StatusBadRequest,
http.StatusMovedPermanently,
http.StatusTemporaryRedirect,
http.StatusConflict,
http.StatusNotFound,
http.StatusUnauthorized,
http.StatusForbidden,
http.StatusNotModified,
errors.StatusUnprocessableEntity,
http.StatusSwitchingProtocols,
),
)
}
defer logger.Log()
// Dispatch to the internal handler
handler.ServeHTTP(w, req)
})
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment