mirror of https://github.com/k3s-io/k3s
Merge pull request #3931 from deads2k/deads-pull-more-info-from-request
pull more complete information from requestpull/6/head
commit
550b98ebf4
|
@ -10,7 +10,7 @@ readonly port is not currently subject to authorization, but is planned to be
|
|||
removed soon.)
|
||||
|
||||
The authorization check for any request compares attributes of the context of
|
||||
the request, (such as user, resource kind, and namespace) with access
|
||||
the request, (such as user, resource, and namespace) with access
|
||||
policies. An API call must be allowed by some policy in order to proceed.
|
||||
|
||||
The following implementations are available, and are selected by flag:
|
||||
|
@ -28,10 +28,10 @@ The following implementations are available, and are selected by flag:
|
|||
A request has 4 attributes that can be considered for authorization:
|
||||
- user (the user-string which a user was authenticated as).
|
||||
- whether the request is readonly (GETs are readonly)
|
||||
- what kind of object is being accessed
|
||||
- what resource is being accessed
|
||||
- applies only to the API endpoints, such as
|
||||
`/api/v1beta1/pods`. For miscelaneous endpoints, like `/version`, the
|
||||
kind is the empty string.
|
||||
resource is the empty string.
|
||||
- the namespace of the object being access, or the empty string if the
|
||||
endpoint does not support namespaced objects.
|
||||
|
||||
|
@ -49,7 +49,7 @@ Each line is a "policy object". A policy object is a map with the following pro
|
|||
- `user`, type string; the user-string from `--token_auth_file`
|
||||
- `readonly`, type boolean, when true, means that the policy only applies to GET
|
||||
operations.
|
||||
- `kind`, type string; a kind of object, from an URL, such as `pods`.
|
||||
- `resource`, type string; a resource from an URL, such as `pods`.
|
||||
- `namespace`, type string; a namespace string.
|
||||
|
||||
An unset property is the same as a property set to the zero value for its type (e.g. empty string, 0, false).
|
||||
|
@ -76,9 +76,9 @@ To permit an action Policy with an unset namespace applies regardless of namespa
|
|||
|
||||
### Examples
|
||||
1. Alice can do anything: `{"user":"alice"}`
|
||||
2. Kubelet can read any pods: `{"user":"kubelet", "kind": "pods", "readonly": true}`
|
||||
3. Kubelet can read and write events: `{"user":"kubelet", "kind": "events"}`
|
||||
4. Bob can just read pods in namespace "projectCaribou": `{"user":"bob", "kind": "pods", "readonly": true, "ns": "projectCaribou"}`
|
||||
2. Kubelet can read any pods: `{"user":"kubelet", "resource": "pods", "readonly": true}`
|
||||
3. Kubelet can read and write events: `{"user":"kubelet", "resource": "events"}`
|
||||
4. Bob can just read pods in namespace "projectCaribou": `{"user":"bob", "resource": "pods", "readonly": true, "ns": "projectCaribou"}`
|
||||
|
||||
[Complete file example](../pkg/auth/authorizer/abac/example_policy_file.jsonl)
|
||||
|
||||
|
|
|
@ -22,15 +22,15 @@ import (
|
|||
|
||||
type attributesRecord struct {
|
||||
namespace string
|
||||
kind string
|
||||
resource string
|
||||
operation string
|
||||
object runtime.Object
|
||||
}
|
||||
|
||||
func NewAttributesRecord(object runtime.Object, namespace, kind, operation string) Attributes {
|
||||
func NewAttributesRecord(object runtime.Object, namespace, resource, operation string) Attributes {
|
||||
return &attributesRecord{
|
||||
namespace: namespace,
|
||||
kind: kind,
|
||||
resource: resource,
|
||||
operation: operation,
|
||||
object: object,
|
||||
}
|
||||
|
@ -40,8 +40,8 @@ func (record *attributesRecord) GetNamespace() string {
|
|||
return record.namespace
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetKind() string {
|
||||
return record.kind
|
||||
func (record *attributesRecord) GetResource() string {
|
||||
return record.resource
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetOperation() string {
|
||||
|
|
|
@ -24,7 +24,7 @@ import (
|
|||
// that is used to make an admission decision.
|
||||
type Attributes interface {
|
||||
GetNamespace() string
|
||||
GetKind() string
|
||||
GetResource() string
|
||||
GetOperation() string
|
||||
GetObject() runtime.Object
|
||||
}
|
||||
|
|
|
@ -53,9 +53,10 @@ func (a *APIInstaller) Install() (ws *restful.WebService, errors []error) {
|
|||
codec: a.restHandler.codec,
|
||||
canonicalPrefix: a.restHandler.canonicalPrefix,
|
||||
selfLinker: a.restHandler.selfLinker,
|
||||
apiRequestInfoResolver: a.restHandler.apiRequestInfoResolver,
|
||||
})
|
||||
redirectHandler := (&RedirectHandler{a.restHandler.storage, a.restHandler.codec})
|
||||
proxyHandler := (&ProxyHandler{a.prefix + "/proxy/", a.restHandler.storage, a.restHandler.codec})
|
||||
redirectHandler := (&RedirectHandler{a.restHandler.storage, a.restHandler.codec, a.restHandler.apiRequestInfoResolver})
|
||||
proxyHandler := (&ProxyHandler{a.prefix + "/proxy/", a.restHandler.storage, a.restHandler.codec, a.restHandler.apiRequestInfoResolver})
|
||||
|
||||
for path, storage := range a.restHandler.storage {
|
||||
if err := a.registerResourceHandlers(path, storage, ws, watchHandler, redirectHandler, proxyHandler); err != nil {
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
|
@ -57,7 +58,7 @@ type defaultAPIServer struct {
|
|||
// Note: This method is used only in tests.
|
||||
func Handle(storage map[string]RESTStorage, codec runtime.Codec, root string, version string, selfLinker runtime.SelfLinker, admissionControl admission.Interface, mapper meta.RESTMapper) http.Handler {
|
||||
prefix := root + "/" + version
|
||||
group := NewAPIGroupVersion(storage, codec, prefix, selfLinker, admissionControl, mapper)
|
||||
group := NewAPIGroupVersion(storage, codec, root, prefix, selfLinker, admissionControl, mapper)
|
||||
container := restful.NewContainer()
|
||||
container.Router(restful.CurlyRouter{})
|
||||
mux := container.ServeMux
|
||||
|
@ -85,7 +86,7 @@ type APIGroupVersion struct {
|
|||
// This is a helper method for registering multiple sets of REST handlers under different
|
||||
// prefixes onto a server.
|
||||
// TODO: add multitype codec serialization
|
||||
func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, canonicalPrefix string, selfLinker runtime.SelfLinker, admissionControl admission.Interface, mapper meta.RESTMapper) *APIGroupVersion {
|
||||
func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, apiRoot, canonicalPrefix string, selfLinker runtime.SelfLinker, admissionControl admission.Interface, mapper meta.RESTMapper) *APIGroupVersion {
|
||||
return &APIGroupVersion{
|
||||
handler: RESTHandler{
|
||||
storage: storage,
|
||||
|
@ -94,6 +95,7 @@ func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, can
|
|||
selfLinker: selfLinker,
|
||||
ops: NewOperations(),
|
||||
admissionControl: admissionControl,
|
||||
apiRequestInfoResolver: &APIRequestInfoResolver{util.NewStringSet(apiRoot), latest.RESTMapper},
|
||||
},
|
||||
mapper: mapper,
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer"
|
||||
authhandlers "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/handlers"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
|
||||
|
@ -154,11 +155,12 @@ type RequestAttributeGetter interface {
|
|||
|
||||
type requestAttributeGetter struct {
|
||||
userContexts authhandlers.RequestContext
|
||||
apiRequestInfoResolver *APIRequestInfoResolver
|
||||
}
|
||||
|
||||
// NewAttributeGetter returns an object which implements the RequestAttributeGetter interface.
|
||||
func NewRequestAttributeGetter(userContexts authhandlers.RequestContext) RequestAttributeGetter {
|
||||
return &requestAttributeGetter{userContexts}
|
||||
func NewRequestAttributeGetter(userContexts authhandlers.RequestContext, restMapper meta.RESTMapper, apiRoots ...string) RequestAttributeGetter {
|
||||
return &requestAttributeGetter{userContexts, &APIRequestInfoResolver{util.NewStringSet(apiRoots...), restMapper}}
|
||||
}
|
||||
|
||||
func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attributes {
|
||||
|
@ -171,16 +173,16 @@ func (r *requestAttributeGetter) GetAttribs(req *http.Request) authorizer.Attrib
|
|||
|
||||
attribs.ReadOnly = IsReadOnlyReq(*req)
|
||||
|
||||
namespace, kind, _, _ := KindAndNamespace(req)
|
||||
apiRequestInfo, _ := r.apiRequestInfoResolver.GetAPIRequestInfo(req)
|
||||
|
||||
// If a path follows the conventions of the REST object store, then
|
||||
// we can extract the object Kind. Otherwise, not.
|
||||
attribs.Kind = kind
|
||||
// we can extract the resource. Otherwise, not.
|
||||
attribs.Resource = apiRequestInfo.Resource
|
||||
|
||||
// If the request specifies a namespace, then the namespace is filled in.
|
||||
// Assumes there is no empty string namespace. Unspecified results
|
||||
// in empty (does not understand defaulting rules.)
|
||||
attribs.Namespace = namespace
|
||||
attribs.Namespace = apiRequestInfo.Namespace
|
||||
|
||||
return &attribs
|
||||
}
|
||||
|
@ -197,78 +199,136 @@ func WithAuthorizationCheck(handler http.Handler, getAttribs RequestAttributeGet
|
|||
})
|
||||
}
|
||||
|
||||
// KindAndNamespace returns the kind, namespace, and path parts for the request relative to /{kind}/{name}
|
||||
// APIRequestInfo holds information parsed from the http.Request
|
||||
type APIRequestInfo struct {
|
||||
// Verb is the kube verb associated with the request, not the http verb. This includes things like list and watch.
|
||||
Verb string
|
||||
APIVersion string
|
||||
Namespace string
|
||||
// Resource is the name of the resource being requested. This is not the kind. For example: pods
|
||||
Resource string
|
||||
// Kind is the type of object being manipulated. For example: Pod
|
||||
Kind string
|
||||
// Name is empty for some verbs, but if the request directly indicates a name (not in body content) then this field is filled in.
|
||||
Name string
|
||||
// Parts are the path parts for the request relative to /{resource}/{name}
|
||||
Parts []string
|
||||
}
|
||||
|
||||
type APIRequestInfoResolver struct {
|
||||
apiPrefixes util.StringSet
|
||||
restMapper meta.RESTMapper
|
||||
}
|
||||
|
||||
// GetAPIRequestInfo returns the information from the http request. If error is not nil, APIRequestInfo holds the information as best it is known before the failure
|
||||
// Valid Inputs:
|
||||
// Storage paths
|
||||
// /ns/{namespace}/{kind}
|
||||
// /ns/{namespace}/{kind}/{resourceName}
|
||||
// /{kind}
|
||||
// /{kind}/{resourceName}
|
||||
// /{kind}/{resourceName}?namespace={namespace}
|
||||
// /{kind}?namespace={namespace}
|
||||
// /ns/{namespace}/{resource}
|
||||
// /ns/{namespace}/{resource}/{resourceName}
|
||||
// /{resource}
|
||||
// /{resource}/{resourceName}
|
||||
// /{resource}/{resourceName}?namespace={namespace}
|
||||
// /{resource}?namespace={namespace}
|
||||
//
|
||||
// Special verbs:
|
||||
// /proxy/{kind}/{resourceName}
|
||||
// /proxy/ns/{namespace}/{kind}/{resourceName}
|
||||
// /redirect/ns/{namespace}/{kind}/{resourceName}
|
||||
// /redirect/{kind}/{resourceName}
|
||||
// /watch/{kind}
|
||||
// /watch/ns/{namespace}/{kind}
|
||||
// /proxy/{resource}/{resourceName}
|
||||
// /proxy/ns/{namespace}/{resource}/{resourceName}
|
||||
// /redirect/ns/{namespace}/{resource}/{resourceName}
|
||||
// /redirect/{resource}/{resourceName}
|
||||
// /watch/{resource}
|
||||
// /watch/ns/{namespace}/{resource}
|
||||
//
|
||||
// Fully qualified paths for above:
|
||||
// /api/{version}/*
|
||||
// /api/{version}/*
|
||||
func KindAndNamespace(req *http.Request) (namespace, kind string, parts []string, err error) {
|
||||
parts = splitPath(req.URL.Path)
|
||||
if len(parts) < 1 {
|
||||
err = fmt.Errorf("Unable to determine kind and namespace from an empty URL path")
|
||||
return
|
||||
func (r *APIRequestInfoResolver) GetAPIRequestInfo(req *http.Request) (APIRequestInfo, error) {
|
||||
requestInfo := APIRequestInfo{}
|
||||
|
||||
currentParts := splitPath(req.URL.Path)
|
||||
if len(currentParts) < 1 {
|
||||
return requestInfo, fmt.Errorf("Unable to determine kind and namespace from an empty URL path")
|
||||
}
|
||||
|
||||
for _, currPrefix := range r.apiPrefixes.List() {
|
||||
// handle input of form /api/{version}/* by adjusting special paths
|
||||
if parts[0] == "api" {
|
||||
if len(parts) > 2 {
|
||||
parts = parts[2:]
|
||||
if currentParts[0] == currPrefix {
|
||||
if len(currentParts) > 1 {
|
||||
requestInfo.APIVersion = currentParts[1]
|
||||
}
|
||||
|
||||
if len(currentParts) > 2 {
|
||||
currentParts = currentParts[2:]
|
||||
} else {
|
||||
err = fmt.Errorf("Unable to determine kind and namespace from url, %v", req.URL)
|
||||
return
|
||||
return requestInfo, fmt.Errorf("Unable to determine kind and namespace from url, %v", req.URL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle input of form /{specialVerb}/*
|
||||
if _, ok := specialVerbs[parts[0]]; ok {
|
||||
if len(parts) > 1 {
|
||||
parts = parts[1:]
|
||||
if _, ok := specialVerbs[currentParts[0]]; ok {
|
||||
requestInfo.Verb = currentParts[0]
|
||||
|
||||
if len(currentParts) > 1 {
|
||||
currentParts = currentParts[1:]
|
||||
} else {
|
||||
err = fmt.Errorf("Unable to determine kind and namespace from url, %v", req.URL)
|
||||
return
|
||||
return requestInfo, fmt.Errorf("Unable to determine kind and namespace from url, %v", req.URL)
|
||||
}
|
||||
} else {
|
||||
switch req.Method {
|
||||
case "POST":
|
||||
requestInfo.Verb = "create"
|
||||
case "GET":
|
||||
requestInfo.Verb = "get"
|
||||
case "PUT":
|
||||
requestInfo.Verb = "update"
|
||||
case "DELETE":
|
||||
requestInfo.Verb = "delete"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// URL forms: /ns/{namespace}/{resource}/*, where parts are adjusted to be relative to kind
|
||||
if currentParts[0] == "ns" {
|
||||
if len(currentParts) < 3 {
|
||||
return requestInfo, fmt.Errorf("ResourceTypeAndNamespace expects a path of form /ns/{namespace}/*")
|
||||
}
|
||||
requestInfo.Resource = currentParts[2]
|
||||
requestInfo.Namespace = currentParts[1]
|
||||
currentParts = currentParts[2:]
|
||||
|
||||
} else {
|
||||
// URL forms: /{resource}/*
|
||||
// URL forms: POST /{resource} is a legacy API convention to create in "default" namespace
|
||||
// URL forms: /{resource}/{resourceName} use the "default" namespace if omitted from query param
|
||||
// URL forms: /{resource} assume cross-namespace operation if omitted from query param
|
||||
requestInfo.Resource = currentParts[0]
|
||||
requestInfo.Namespace = req.URL.Query().Get("namespace")
|
||||
if len(requestInfo.Namespace) == 0 {
|
||||
if len(currentParts) > 1 || req.Method == "POST" {
|
||||
requestInfo.Namespace = api.NamespaceDefault
|
||||
} else {
|
||||
requestInfo.Namespace = api.NamespaceAll
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// URL forms: /ns/{namespace}/{kind}/*, where parts are adjusted to be relative to kind
|
||||
if parts[0] == "ns" {
|
||||
if len(parts) < 3 {
|
||||
err = fmt.Errorf("ResourceTypeAndNamespace expects a path of form /ns/{namespace}/*")
|
||||
return
|
||||
}
|
||||
namespace = parts[1]
|
||||
kind = parts[2]
|
||||
parts = parts[2:]
|
||||
return
|
||||
// parsing successful, so we now know the proper value for .Parts
|
||||
requestInfo.Parts = currentParts
|
||||
|
||||
// if there's another part remaining after the kind, then that's the resource name
|
||||
if len(requestInfo.Parts) >= 2 {
|
||||
requestInfo.Name = requestInfo.Parts[1]
|
||||
}
|
||||
|
||||
// URL forms: /{kind}/*
|
||||
// URL forms: POST /{kind} is a legacy API convention to create in "default" namespace
|
||||
// URL forms: /{kind}/{resourceName} use the "default" namespace if omitted from query param
|
||||
// URL forms: /{kind} assume cross-namespace operation if omitted from query param
|
||||
kind = parts[0]
|
||||
namespace = req.URL.Query().Get("namespace")
|
||||
if len(namespace) == 0 {
|
||||
if len(parts) > 1 || req.Method == "POST" {
|
||||
namespace = api.NamespaceDefault
|
||||
} else {
|
||||
namespace = api.NamespaceAll
|
||||
// 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 we have a resource, we have a good shot at being able to determine kind
|
||||
if len(requestInfo.Resource) > 0 {
|
||||
_, requestInfo.Kind, _ = r.restMapper.VersionAndKindForResource(requestInfo.Resource)
|
||||
}
|
||||
return
|
||||
|
||||
return requestInfo, nil
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
)
|
||||
|
||||
type fakeRL bool
|
||||
|
@ -63,59 +65,78 @@ func TestReadOnly(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestKindAndNamespace(t *testing.T) {
|
||||
func TestGetAPIRequestInfo(t *testing.T) {
|
||||
successCases := []struct {
|
||||
method string
|
||||
url string
|
||||
expectedVerb string
|
||||
expectedAPIVersion string
|
||||
expectedNamespace string
|
||||
expectedResource string
|
||||
expectedKind string
|
||||
expectedName string
|
||||
expectedParts []string
|
||||
}{
|
||||
// resource paths
|
||||
{"GET", "/ns/other/pods", "other", "pods", []string{"pods"}},
|
||||
{"GET", "/ns/other/pods/foo", "other", "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/pods", api.NamespaceAll, "pods", []string{"pods"}},
|
||||
{"POST", "/pods", api.NamespaceDefault, "pods", []string{"pods"}},
|
||||
{"GET", "/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/pods/foo?namespace=other", "other", "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/pods?namespace=other", "other", "pods", []string{"pods"}},
|
||||
{"GET", "/ns/other/pods", "list", "", "other", "pods", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/ns/other/pods/foo", "get", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/pods", "list", "", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
|
||||
{"POST", "/pods", "create", "", api.NamespaceDefault, "pods", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/pods/foo", "get", "", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/pods/foo?namespace=other", "get", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/pods?namespace=other", "list", "", "other", "pods", "Pod", "", []string{"pods"}},
|
||||
|
||||
// special verbs
|
||||
{"GET", "/proxy/ns/other/pods/foo", "other", "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/proxy/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/redirect/ns/other/pods/foo", "other", "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/redirect/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/watch/pods", api.NamespaceAll, "pods", []string{"pods"}},
|
||||
{"GET", "/watch/ns/other/pods", "other", "pods", []string{"pods"}},
|
||||
{"GET", "/proxy/ns/other/pods/foo", "proxy", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/proxy/pods/foo", "proxy", "", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/redirect/ns/other/pods/foo", "redirect", "", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/redirect/pods/foo", "redirect", "", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/watch/pods", "watch", "", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/watch/ns/other/pods", "watch", "", "other", "pods", "Pod", "", []string{"pods"}},
|
||||
|
||||
// fully-qualified paths
|
||||
{"GET", "/api/v1beta1/ns/other/pods", "other", "pods", []string{"pods"}},
|
||||
{"GET", "/api/v1beta1/ns/other/pods/foo", "other", "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1beta1/pods", api.NamespaceAll, "pods", []string{"pods"}},
|
||||
{"POST", "/api/v1beta1/pods", api.NamespaceDefault, "pods", []string{"pods"}},
|
||||
{"GET", "/api/v1beta1/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1beta1/pods/foo?namespace=other", "other", "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1beta1/pods?namespace=other", "other", "pods", []string{"pods"}},
|
||||
{"GET", "/api/v1beta1/proxy/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1beta1/redirect/pods/foo", api.NamespaceDefault, "pods", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1beta1/watch/pods", api.NamespaceAll, "pods", []string{"pods"}},
|
||||
{"GET", "/api/v1beta1/watch/ns/other/pods", "other", "pods", []string{"pods"}},
|
||||
{"GET", "/api/v1beta1/ns/other/pods", "list", "v1beta1", "other", "pods", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/api/v1beta1/ns/other/pods/foo", "get", "v1beta1", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1beta1/pods", "list", "v1beta1", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
|
||||
{"POST", "/api/v1beta1/pods", "create", "v1beta1", api.NamespaceDefault, "pods", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/api/v1beta1/pods/foo", "get", "v1beta1", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1beta1/pods/foo?namespace=other", "get", "v1beta1", "other", "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1beta1/pods?namespace=other", "list", "v1beta1", "other", "pods", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/api/v1beta1/proxy/pods/foo", "proxy", "v1beta1", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1beta1/redirect/pods/foo", "redirect", "v1beta1", api.NamespaceDefault, "pods", "Pod", "foo", []string{"pods", "foo"}},
|
||||
{"GET", "/api/v1beta1/watch/pods", "watch", "v1beta1", api.NamespaceAll, "pods", "Pod", "", []string{"pods"}},
|
||||
{"GET", "/api/v1beta1/watch/ns/other/pods", "watch", "v1beta1", "other", "pods", "Pod", "", []string{"pods"}},
|
||||
}
|
||||
|
||||
apiRequestInfoResolver := &APIRequestInfoResolver{util.NewStringSet("api"), latest.RESTMapper}
|
||||
|
||||
for _, successCase := range successCases {
|
||||
req, _ := http.NewRequest(successCase.method, successCase.url, nil)
|
||||
namespace, kind, parts, err := KindAndNamespace(req)
|
||||
|
||||
apiRequestInfo, err := apiRequestInfoResolver.GetAPIRequestInfo(req)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error for url: %s", successCase.url)
|
||||
t.Errorf("Unexpected error for url: %s %v", successCase.url, err)
|
||||
}
|
||||
if successCase.expectedNamespace != namespace {
|
||||
t.Errorf("Unexpected namespace for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedNamespace, namespace)
|
||||
if successCase.expectedVerb != apiRequestInfo.Verb {
|
||||
t.Errorf("Unexpected verb for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedVerb, apiRequestInfo.Verb)
|
||||
}
|
||||
if successCase.expectedKind != kind {
|
||||
t.Errorf("Unexpected resourceType for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedKind, kind)
|
||||
if successCase.expectedAPIVersion != apiRequestInfo.APIVersion {
|
||||
t.Errorf("Unexpected apiVersion for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedAPIVersion, apiRequestInfo.APIVersion)
|
||||
}
|
||||
if !reflect.DeepEqual(successCase.expectedParts, parts) {
|
||||
t.Errorf("Unexpected parts for url: %s, expected: %v, actual: %v", successCase.url, successCase.expectedParts, parts)
|
||||
if successCase.expectedNamespace != apiRequestInfo.Namespace {
|
||||
t.Errorf("Unexpected namespace for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedNamespace, apiRequestInfo.Namespace)
|
||||
}
|
||||
if successCase.expectedKind != apiRequestInfo.Kind {
|
||||
t.Errorf("Unexpected kind for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedKind, apiRequestInfo.Kind)
|
||||
}
|
||||
if successCase.expectedResource != apiRequestInfo.Resource {
|
||||
t.Errorf("Unexpected resource for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedResource, apiRequestInfo.Resource)
|
||||
}
|
||||
if successCase.expectedName != apiRequestInfo.Name {
|
||||
t.Errorf("Unexpected name for url: %s, expected: %s, actual: %s", successCase.url, successCase.expectedName, apiRequestInfo.Name)
|
||||
}
|
||||
if !reflect.DeepEqual(successCase.expectedParts, apiRequestInfo.Parts) {
|
||||
t.Errorf("Unexpected parts for url: %s, expected: %v, actual: %v", successCase.url, successCase.expectedParts, apiRequestInfo.Parts)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,7 +152,7 @@ func TestKindAndNamespace(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Errorf("Unexpected error %v", err)
|
||||
}
|
||||
_, _, _, err = KindAndNamespace(req)
|
||||
_, err = apiRequestInfoResolver.GetAPIRequestInfo(req)
|
||||
if err == nil {
|
||||
t.Errorf("Expected error for key: %s", k)
|
||||
}
|
||||
|
|
|
@ -78,14 +78,17 @@ type ProxyHandler struct {
|
|||
prefix string
|
||||
storage map[string]RESTStorage
|
||||
codec runtime.Codec
|
||||
apiRequestInfoResolver *APIRequestInfoResolver
|
||||
}
|
||||
|
||||
func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
namespace, kind, parts, err := KindAndNamespace(req)
|
||||
requestInfo, err := r.apiRequestInfoResolver.GetAPIRequestInfo(req)
|
||||
if err != nil {
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
namespace, resource, parts := requestInfo.Namespace, requestInfo.Resource, requestInfo.Parts
|
||||
|
||||
ctx := api.WithNamespace(api.NewContext(), namespace)
|
||||
if len(parts) < 2 {
|
||||
notFound(w, req)
|
||||
|
@ -103,17 +106,17 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
rest = rest + "/"
|
||||
}
|
||||
}
|
||||
storage, ok := r.storage[kind]
|
||||
storage, ok := r.storage[resource]
|
||||
if !ok {
|
||||
httplog.LogOf(req, w).Addf("'%v' has no storage object", kind)
|
||||
httplog.LogOf(req, w).Addf("'%v' has no storage object", resource)
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
redirector, ok := storage.(Redirector)
|
||||
if !ok {
|
||||
httplog.LogOf(req, w).Addf("'%v' is not a redirector", kind)
|
||||
errorJSON(errors.NewMethodNotSupported(kind, "proxy"), r.codec, w)
|
||||
httplog.LogOf(req, w).Addf("'%v' is not a redirector", resource)
|
||||
errorJSON(errors.NewMethodNotSupported(resource, "proxy"), r.codec, w)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -156,7 +159,7 @@ func (r *ProxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
proxy.Transport = &proxyTransport{
|
||||
proxyScheme: req.URL.Scheme,
|
||||
proxyHost: req.URL.Host,
|
||||
proxyPathPrepend: path.Join(r.prefix, "ns", namespace, kind, id),
|
||||
proxyPathPrepend: path.Join(r.prefix, "ns", namespace, resource, id),
|
||||
}
|
||||
proxy.FlushInterval = 200 * time.Millisecond
|
||||
proxy.ServeHTTP(w, newReq)
|
||||
|
|
|
@ -28,33 +28,35 @@ import (
|
|||
type RedirectHandler struct {
|
||||
storage map[string]RESTStorage
|
||||
codec runtime.Codec
|
||||
apiRequestInfoResolver *APIRequestInfoResolver
|
||||
}
|
||||
|
||||
func (r *RedirectHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
namespace, kind, parts, err := KindAndNamespace(req)
|
||||
requestInfo, err := r.apiRequestInfoResolver.GetAPIRequestInfo(req)
|
||||
if err != nil {
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
ctx := api.WithNamespace(api.NewContext(), namespace)
|
||||
resource, parts := requestInfo.Resource, requestInfo.Parts
|
||||
ctx := api.WithNamespace(api.NewContext(), requestInfo.Namespace)
|
||||
|
||||
// redirection requires /kind/resourceName path parts
|
||||
// redirection requires /resource/resourceName path parts
|
||||
if len(parts) != 2 || req.Method != "GET" {
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
id := parts[1]
|
||||
storage, ok := r.storage[kind]
|
||||
storage, ok := r.storage[resource]
|
||||
if !ok {
|
||||
httplog.LogOf(req, w).Addf("'%v' has no storage object", kind)
|
||||
httplog.LogOf(req, w).Addf("'%v' has no storage object", resource)
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
redirector, ok := storage.(Redirector)
|
||||
if !ok {
|
||||
httplog.LogOf(req, w).Addf("'%v' is not a redirector", kind)
|
||||
errorJSON(errors.NewMethodNotSupported(kind, "redirect"), r.codec, w)
|
||||
httplog.LogOf(req, w).Addf("'%v' is not a redirector", resource)
|
||||
errorJSON(errors.NewMethodNotSupported(resource, "redirect"), r.codec, w)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -38,22 +38,23 @@ type RESTHandler struct {
|
|||
selfLinker runtime.SelfLinker
|
||||
ops *Operations
|
||||
admissionControl admission.Interface
|
||||
apiRequestInfoResolver *APIRequestInfoResolver
|
||||
}
|
||||
|
||||
// ServeHTTP handles requests to all RESTStorage objects.
|
||||
func (h *RESTHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
namespace, kind, parts, err := KindAndNamespace(req)
|
||||
requestInfo, err := h.apiRequestInfoResolver.GetAPIRequestInfo(req)
|
||||
if err != nil {
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
storage, ok := h.storage[kind]
|
||||
storage, ok := h.storage[requestInfo.Resource]
|
||||
if !ok {
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
h.handleRESTStorage(parts, req, w, storage, namespace, kind)
|
||||
h.handleRESTStorage(requestInfo.Parts, req, w, storage, requestInfo.Namespace, requestInfo.Resource)
|
||||
}
|
||||
|
||||
// Sets the SelfLink field of the object.
|
||||
|
|
|
@ -40,6 +40,7 @@ type WatchHandler struct {
|
|||
codec runtime.Codec
|
||||
canonicalPrefix string
|
||||
selfLinker runtime.SelfLinker
|
||||
apiRequestInfoResolver *APIRequestInfoResolver
|
||||
}
|
||||
|
||||
// setSelfLinkAddName sets the self link, appending the object's name to the canonical path & type.
|
||||
|
@ -87,21 +88,21 @@ func (h *WatchHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
namespace, kind, _, err := KindAndNamespace(req)
|
||||
requestInfo, err := h.apiRequestInfoResolver.GetAPIRequestInfo(req)
|
||||
if err != nil {
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
ctx := api.WithNamespace(api.NewContext(), namespace)
|
||||
ctx := api.WithNamespace(api.NewContext(), requestInfo.Namespace)
|
||||
|
||||
storage := h.storage[kind]
|
||||
storage := h.storage[requestInfo.Resource]
|
||||
if storage == nil {
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
watcher, ok := storage.(ResourceWatcher)
|
||||
if !ok {
|
||||
errorJSON(errors.NewMethodNotSupported(kind, "watch"), h.codec, w)
|
||||
errorJSON(errors.NewMethodNotSupported(requestInfo.Resource, "watch"), h.codec, w)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ type policy struct {
|
|||
|
||||
// TODO: make this a proper REST object with its own registry.
|
||||
Readonly bool `json:"readonly,omitempty"`
|
||||
Kind string `json:"kind,omitempty"`
|
||||
Resource string `json:"resource,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
|
||||
// TODO: "expires" string in RFC3339 format.
|
||||
|
@ -100,7 +100,7 @@ func NewFromFile(path string) (policyList, error) {
|
|||
func (p policy) matches(a authorizer.Attributes) bool {
|
||||
if p.subjectMatches(a) {
|
||||
if p.Readonly == false || (p.Readonly == a.IsReadOnly()) {
|
||||
if p.Kind == "" || (p.Kind == a.GetKind()) {
|
||||
if p.Resource == "" || (p.Resource == a.GetResource()) {
|
||||
if p.Namespace == "" || (p.Namespace == a.GetNamespace()) {
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -76,49 +76,49 @@ func NotTestAuthorize(t *testing.T) {
|
|||
testCases := []struct {
|
||||
User user.DefaultInfo
|
||||
RO bool
|
||||
Kind string
|
||||
Resource string
|
||||
NS string
|
||||
ExpectAllow bool
|
||||
}{
|
||||
// Scheduler can read pods
|
||||
{User: uScheduler, RO: true, Kind: "pods", NS: "ns1", ExpectAllow: true},
|
||||
{User: uScheduler, RO: true, Kind: "pods", NS: "", ExpectAllow: true},
|
||||
{User: uScheduler, RO: true, Resource: "pods", NS: "ns1", ExpectAllow: true},
|
||||
{User: uScheduler, RO: true, Resource: "pods", NS: "", ExpectAllow: true},
|
||||
// Scheduler cannot write pods
|
||||
{User: uScheduler, RO: false, Kind: "pods", NS: "ns1", ExpectAllow: false},
|
||||
{User: uScheduler, RO: false, Kind: "pods", NS: "", ExpectAllow: false},
|
||||
{User: uScheduler, RO: false, Resource: "pods", NS: "ns1", ExpectAllow: false},
|
||||
{User: uScheduler, RO: false, Resource: "pods", NS: "", ExpectAllow: false},
|
||||
// Scheduler can write bindings
|
||||
{User: uScheduler, RO: true, Kind: "bindings", NS: "ns1", ExpectAllow: true},
|
||||
{User: uScheduler, RO: true, Kind: "bindings", NS: "", ExpectAllow: true},
|
||||
{User: uScheduler, RO: true, Resource: "bindings", NS: "ns1", ExpectAllow: true},
|
||||
{User: uScheduler, RO: true, Resource: "bindings", NS: "", ExpectAllow: true},
|
||||
|
||||
// Alice can read and write anything in the right namespace.
|
||||
{User: uAlice, RO: true, Kind: "pods", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: true, Kind: "widgets", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: true, Kind: "", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: false, Kind: "pods", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: false, Kind: "widgets", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: false, Kind: "", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: true, Resource: "pods", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: true, Resource: "widgets", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: true, Resource: "", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: false, Resource: "pods", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: false, Resource: "widgets", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, RO: false, Resource: "", NS: "projectCaribou", ExpectAllow: true},
|
||||
// .. but not the wrong namespace.
|
||||
{User: uAlice, RO: true, Kind: "pods", NS: "ns1", ExpectAllow: false},
|
||||
{User: uAlice, RO: true, Kind: "widgets", NS: "ns1", ExpectAllow: false},
|
||||
{User: uAlice, RO: true, Kind: "", NS: "ns1", ExpectAllow: false},
|
||||
{User: uAlice, RO: true, Resource: "pods", NS: "ns1", ExpectAllow: false},
|
||||
{User: uAlice, RO: true, Resource: "widgets", NS: "ns1", ExpectAllow: false},
|
||||
{User: uAlice, RO: true, Resource: "", NS: "ns1", ExpectAllow: false},
|
||||
|
||||
// Chuck can read events, since anyone can.
|
||||
{User: uChuck, RO: true, Kind: "events", NS: "ns1", ExpectAllow: true},
|
||||
{User: uChuck, RO: true, Kind: "events", NS: "", ExpectAllow: true},
|
||||
{User: uChuck, RO: true, Resource: "events", NS: "ns1", ExpectAllow: true},
|
||||
{User: uChuck, RO: true, Resource: "events", NS: "", ExpectAllow: true},
|
||||
// Chuck can't do other things.
|
||||
{User: uChuck, RO: false, Kind: "events", NS: "ns1", ExpectAllow: false},
|
||||
{User: uChuck, RO: true, Kind: "pods", NS: "ns1", ExpectAllow: false},
|
||||
{User: uChuck, RO: true, Kind: "floop", NS: "ns1", ExpectAllow: false},
|
||||
{User: uChuck, RO: false, Resource: "events", NS: "ns1", ExpectAllow: false},
|
||||
{User: uChuck, RO: true, Resource: "pods", NS: "ns1", ExpectAllow: false},
|
||||
{User: uChuck, RO: true, Resource: "floop", NS: "ns1", ExpectAllow: false},
|
||||
// Chunk can't access things with no kind or namespace
|
||||
// TODO: find a way to give someone access to miscelaneous endpoints, such as
|
||||
// /healthz, /version, etc.
|
||||
{User: uChuck, RO: true, Kind: "", NS: "", ExpectAllow: false},
|
||||
{User: uChuck, RO: true, Resource: "", NS: "", ExpectAllow: false},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
attr := authorizer.AttributesRecord{
|
||||
User: &tc.User,
|
||||
ReadOnly: tc.RO,
|
||||
Kind: tc.Kind,
|
||||
Resource: tc.Resource,
|
||||
Namespace: tc.NS,
|
||||
}
|
||||
t.Logf("tc: %v -> attr %v", tc, attr)
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{"user":"admin"}
|
||||
{"user":"scheduler", "readonly": true, "kind": "pods"}
|
||||
{"user":"scheduler", "kind": "bindings"}
|
||||
{"user":"kubelet", "readonly": true, "kind": "pods"}
|
||||
{"user":"kubelet", "readonly": true, "kind": "services"}
|
||||
{"user":"kubelet", "readonly": true, "kind": "endpoints"}
|
||||
{"user":"kubelet", "kind": "events"}
|
||||
{"user":"scheduler", "readonly": true, "resource": "pods"}
|
||||
{"user":"scheduler", "resource": "bindings"}
|
||||
{"user":"kubelet", "readonly": true, "resource": "pods"}
|
||||
{"user":"kubelet", "readonly": true, "resource": "services"}
|
||||
{"user":"kubelet", "readonly": true, "resource": "endpoints"}
|
||||
{"user":"kubelet", "resource": "events"}
|
||||
{"user":"alice", "ns": "projectCaribou"}
|
||||
{"user":"bob", "readonly": true, "ns": "projectCaribou"}
|
||||
|
|
|
@ -40,7 +40,7 @@ type Attributes interface {
|
|||
GetNamespace() string
|
||||
|
||||
// The kind of object, if a request is for a REST object.
|
||||
GetKind() string
|
||||
GetResource() string
|
||||
}
|
||||
|
||||
// Authorizer makes an authorization decision based on information gained by making
|
||||
|
@ -55,7 +55,7 @@ type AttributesRecord struct {
|
|||
User user.Info
|
||||
ReadOnly bool
|
||||
Namespace string
|
||||
Kind string
|
||||
Resource string
|
||||
}
|
||||
|
||||
func (a AttributesRecord) GetUserName() string {
|
||||
|
@ -74,6 +74,6 @@ func (a AttributesRecord) GetNamespace() string {
|
|||
return a.Namespace
|
||||
}
|
||||
|
||||
func (a AttributesRecord) GetKind() string {
|
||||
return a.Kind
|
||||
func (a AttributesRecord) GetResource() string {
|
||||
return a.Resource
|
||||
}
|
||||
|
|
|
@ -454,7 +454,7 @@ func (m *Master) init(c *Config) {
|
|||
|
||||
m.InsecureHandler = handler
|
||||
|
||||
attributeGetter := apiserver.NewRequestAttributeGetter(userContexts)
|
||||
attributeGetter := apiserver.NewRequestAttributeGetter(userContexts, latest.RESTMapper, "api")
|
||||
handler = apiserver.WithAuthorizationCheck(handler, attributeGetter, m.authorizer)
|
||||
|
||||
// Install Authenticator
|
||||
|
@ -531,25 +531,25 @@ func (m *Master) getServersToValidate(c *Config) map[string]apiserver.Server {
|
|||
}
|
||||
|
||||
// api_v1beta1 returns the resources and codec for API version v1beta1.
|
||||
func (m *Master) api_v1beta1() (map[string]apiserver.RESTStorage, runtime.Codec, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) {
|
||||
func (m *Master) api_v1beta1() (map[string]apiserver.RESTStorage, runtime.Codec, string, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) {
|
||||
storage := make(map[string]apiserver.RESTStorage)
|
||||
for k, v := range m.storage {
|
||||
storage[k] = v
|
||||
}
|
||||
return storage, v1beta1.Codec, "/api/v1beta1", latest.SelfLinker, m.admissionControl, latest.RESTMapper
|
||||
return storage, v1beta1.Codec, "api", "/api/v1beta1", latest.SelfLinker, m.admissionControl, latest.RESTMapper
|
||||
}
|
||||
|
||||
// api_v1beta2 returns the resources and codec for API version v1beta2.
|
||||
func (m *Master) api_v1beta2() (map[string]apiserver.RESTStorage, runtime.Codec, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) {
|
||||
func (m *Master) api_v1beta2() (map[string]apiserver.RESTStorage, runtime.Codec, string, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) {
|
||||
storage := make(map[string]apiserver.RESTStorage)
|
||||
for k, v := range m.storage {
|
||||
storage[k] = v
|
||||
}
|
||||
return storage, v1beta2.Codec, "/api/v1beta2", latest.SelfLinker, m.admissionControl, latest.RESTMapper
|
||||
return storage, v1beta2.Codec, "api", "/api/v1beta2", latest.SelfLinker, m.admissionControl, latest.RESTMapper
|
||||
}
|
||||
|
||||
// api_v1beta3 returns the resources and codec for API version v1beta3.
|
||||
func (m *Master) api_v1beta3() (map[string]apiserver.RESTStorage, runtime.Codec, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) {
|
||||
func (m *Master) api_v1beta3() (map[string]apiserver.RESTStorage, runtime.Codec, string, string, runtime.SelfLinker, admission.Interface, meta.RESTMapper) {
|
||||
storage := make(map[string]apiserver.RESTStorage)
|
||||
for k, v := range m.storage {
|
||||
if k == "minions" {
|
||||
|
@ -557,5 +557,5 @@ func (m *Master) api_v1beta3() (map[string]apiserver.RESTStorage, runtime.Codec,
|
|||
}
|
||||
storage[strings.ToLower(k)] = v
|
||||
}
|
||||
return storage, v1beta3.Codec, "/api/v1beta3", latest.SelfLinker, m.admissionControl, latest.RESTMapper
|
||||
return storage, v1beta3.Codec, "api", "/api/v1beta3", latest.SelfLinker, m.admissionControl, latest.RESTMapper
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ func init() {
|
|||
type alwaysDeny struct{}
|
||||
|
||||
func (alwaysDeny) Admit(a admission.Attributes) (err error) {
|
||||
return apierrors.NewForbidden(a.GetKind(), "", errors.New("Admission control is denying all modifications"))
|
||||
return apierrors.NewForbidden(a.GetResource(), "", errors.New("Admission control is denying all modifications"))
|
||||
}
|
||||
|
||||
func NewAlwaysDeny() admission.Interface {
|
||||
|
|
|
@ -58,7 +58,7 @@ func (l *limitRanger) Admit(a admission.Attributes) (err error) {
|
|||
// ensure it meets each prescribed min/max
|
||||
for i := range items.Items {
|
||||
limitRange := &items.Items[i]
|
||||
err = l.limitFunc(limitRange, a.GetKind(), a.GetObject())
|
||||
err = l.limitFunc(limitRange, a.GetResource(), a.GetObject())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -86,8 +86,8 @@ func Max(a int64, b int64) int64 {
|
|||
}
|
||||
|
||||
// PodLimitFunc enforces that a pod spec does not exceed any limits specified on the supplied limit range
|
||||
func PodLimitFunc(limitRange *api.LimitRange, kind string, obj runtime.Object) error {
|
||||
if kind != "pods" {
|
||||
func PodLimitFunc(limitRange *api.LimitRange, resourceName string, obj runtime.Object) error {
|
||||
if resourceName != "pods" {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -161,11 +161,11 @@ func PodLimitFunc(limitRange *api.LimitRange, kind string, obj runtime.Object) e
|
|||
switch minOrMax {
|
||||
case "Min":
|
||||
if observed < enforced {
|
||||
return apierrors.NewForbidden(kind, pod.Name, err)
|
||||
return apierrors.NewForbidden(resourceName, pod.Name, err)
|
||||
}
|
||||
case "Max":
|
||||
if observed > enforced {
|
||||
return apierrors.NewForbidden(kind, pod.Name, err)
|
||||
return apierrors.NewForbidden(resourceName, pod.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ func (resourceDefaults) Admit(a admission.Attributes) (err error) {
|
|||
}
|
||||
|
||||
// we only care about pods
|
||||
if a.GetKind() != "pods" {
|
||||
if a.GetResource() != "pods" {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ func NewResourceQuota(client client.Interface) admission.Interface {
|
|||
return "a{client: client}
|
||||
}
|
||||
|
||||
var kindToResourceName = map[string]api.ResourceName{
|
||||
var resourceToResourceName = map[string]api.ResourceName{
|
||||
"pods": api.ResourcePods,
|
||||
"services": api.ResourceServices,
|
||||
"replicationControllers": api.ResourceReplicationControllers,
|
||||
|
@ -57,7 +57,7 @@ func (q *quota) Admit(a admission.Attributes) (err error) {
|
|||
}
|
||||
|
||||
obj := a.GetObject()
|
||||
kind := a.GetKind()
|
||||
resource := a.GetResource()
|
||||
name := "Unknown"
|
||||
if obj != nil {
|
||||
name, _ = meta.NewAccessor().Name(obj)
|
||||
|
@ -65,7 +65,7 @@ func (q *quota) Admit(a admission.Attributes) (err error) {
|
|||
|
||||
list, err := q.client.ResourceQuotas(a.GetNamespace()).List(labels.Everything())
|
||||
if err != nil {
|
||||
return apierrors.NewForbidden(a.GetKind(), name, fmt.Errorf("Unable to %s %s at this time because there was an error enforcing quota", a.GetOperation(), kind))
|
||||
return apierrors.NewForbidden(a.GetResource(), name, fmt.Errorf("Unable to %s %s at this time because there was an error enforcing quota", a.GetOperation(), resource))
|
||||
}
|
||||
|
||||
if len(list.Items) == 0 {
|
||||
|
@ -90,7 +90,7 @@ func (q *quota) Admit(a admission.Attributes) (err error) {
|
|||
usage.Status = quota.Status
|
||||
err = q.client.ResourceQuotaUsages(usage.Namespace).Create(&usage)
|
||||
if err != nil {
|
||||
return apierrors.NewForbidden(a.GetKind(), name, fmt.Errorf("Unable to %s %s at this time because there was an error enforcing quota", a.GetOperation(), a.GetKind()))
|
||||
return apierrors.NewForbidden(a.GetResource(), name, fmt.Errorf("Unable to %s %s at this time because there was an error enforcing quota", a.GetOperation(), a.GetResource()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,7 +102,7 @@ func (q *quota) Admit(a admission.Attributes) (err error) {
|
|||
// Return an error if the operation should not pass admission control
|
||||
func IncrementUsage(a admission.Attributes, status *api.ResourceQuotaStatus, client client.Interface) (bool, error) {
|
||||
obj := a.GetObject()
|
||||
kind := a.GetKind()
|
||||
resourceName := a.GetResource()
|
||||
name := "Unknown"
|
||||
if obj != nil {
|
||||
name, _ = meta.NewAccessor().Name(obj)
|
||||
|
@ -114,15 +114,15 @@ func IncrementUsage(a admission.Attributes, status *api.ResourceQuotaStatus, cli
|
|||
}
|
||||
// handle max counts for each kind of resource (pods, services, replicationControllers, etc.)
|
||||
if a.GetOperation() == "CREATE" {
|
||||
resourceName := kindToResourceName[a.GetKind()]
|
||||
resourceName := resourceToResourceName[a.GetResource()]
|
||||
hard, hardFound := status.Hard[resourceName]
|
||||
if hardFound {
|
||||
used, usedFound := status.Used[resourceName]
|
||||
if !usedFound {
|
||||
return false, apierrors.NewForbidden(kind, name, fmt.Errorf("Quota usage stats are not yet known, unable to admit resource until an accurate count is completed."))
|
||||
return false, apierrors.NewForbidden(a.GetResource(), name, fmt.Errorf("Quota usage stats are not yet known, unable to admit resource until an accurate count is completed."))
|
||||
}
|
||||
if used.Value() >= hard.Value() {
|
||||
return false, apierrors.NewForbidden(kind, name, fmt.Errorf("Limited to %s %s", hard.String(), kind))
|
||||
return false, apierrors.NewForbidden(a.GetResource(), name, fmt.Errorf("Limited to %s %s", hard.String(), a.GetResource()))
|
||||
} else {
|
||||
status.Used[resourceName] = *resource.NewQuantity(used.Value()+int64(1), resource.DecimalSI)
|
||||
dirty = true
|
||||
|
@ -130,7 +130,7 @@ func IncrementUsage(a admission.Attributes, status *api.ResourceQuotaStatus, cli
|
|||
}
|
||||
}
|
||||
// handle memory/cpu constraints, and any diff of usage based on memory/cpu on updates
|
||||
if a.GetKind() == "pods" && (set[api.ResourceMemory] || set[api.ResourceCPU]) {
|
||||
if a.GetResource() == "pods" && (set[api.ResourceMemory] || set[api.ResourceCPU]) {
|
||||
pod := obj.(*api.Pod)
|
||||
deltaCPU := resourcequota.PodCPU(pod)
|
||||
deltaMemory := resourcequota.PodMemory(pod)
|
||||
|
@ -138,7 +138,7 @@ func IncrementUsage(a admission.Attributes, status *api.ResourceQuotaStatus, cli
|
|||
if a.GetOperation() == "UPDATE" {
|
||||
oldPod, err := client.Pods(a.GetNamespace()).Get(pod.Name)
|
||||
if err != nil {
|
||||
return false, apierrors.NewForbidden(kind, name, err)
|
||||
return false, apierrors.NewForbidden(resourceName, name, err)
|
||||
}
|
||||
oldCPU := resourcequota.PodCPU(oldPod)
|
||||
oldMemory := resourcequota.PodMemory(oldPod)
|
||||
|
@ -150,10 +150,10 @@ func IncrementUsage(a admission.Attributes, status *api.ResourceQuotaStatus, cli
|
|||
if hardMemFound {
|
||||
used, usedFound := status.Used[api.ResourceMemory]
|
||||
if !usedFound {
|
||||
return false, apierrors.NewForbidden(kind, name, fmt.Errorf("Quota usage stats are not yet known, unable to admit resource until an accurate count is completed."))
|
||||
return false, apierrors.NewForbidden(resourceName, name, fmt.Errorf("Quota usage stats are not yet known, unable to admit resource until an accurate count is completed."))
|
||||
}
|
||||
if used.Value()+deltaMemory.Value() > hardMem.Value() {
|
||||
return false, apierrors.NewForbidden(kind, name, fmt.Errorf("Limited to %s memory", hardMem.String()))
|
||||
return false, apierrors.NewForbidden(resourceName, name, fmt.Errorf("Limited to %s memory", hardMem.String()))
|
||||
} else {
|
||||
status.Used[api.ResourceMemory] = *resource.NewQuantity(used.Value()+deltaMemory.Value(), resource.DecimalSI)
|
||||
dirty = true
|
||||
|
@ -163,10 +163,10 @@ func IncrementUsage(a admission.Attributes, status *api.ResourceQuotaStatus, cli
|
|||
if hardCPUFound {
|
||||
used, usedFound := status.Used[api.ResourceCPU]
|
||||
if !usedFound {
|
||||
return false, apierrors.NewForbidden(kind, name, fmt.Errorf("Quota usage stats are not yet known, unable to admit resource until an accurate count is completed."))
|
||||
return false, apierrors.NewForbidden(resourceName, name, fmt.Errorf("Quota usage stats are not yet known, unable to admit resource until an accurate count is completed."))
|
||||
}
|
||||
if used.MilliValue()+deltaCPU.MilliValue() > hardCPU.MilliValue() {
|
||||
return false, apierrors.NewForbidden(kind, name, fmt.Errorf("Limited to %s CPU", hardCPU.String()))
|
||||
return false, apierrors.NewForbidden(resourceName, name, fmt.Errorf("Limited to %s CPU", hardCPU.String()))
|
||||
} else {
|
||||
status.Used[api.ResourceCPU] = *resource.NewMilliQuantity(used.MilliValue()+deltaCPU.MilliValue(), resource.DecimalSI)
|
||||
dirty = true
|
||||
|
|
|
@ -691,7 +691,7 @@ func TestKindAuthorization(t *testing.T) {
|
|||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
a := newAuthorizerWithContents(t, `{"kind": "services"}
|
||||
a := newAuthorizerWithContents(t, `{"resource": "services"}
|
||||
`)
|
||||
|
||||
var m *master.Master
|
||||
|
|
Loading…
Reference in New Issue