add selfsubjectrulesreview api

pull/6/head
xilabao 2017-07-14 11:24:27 +08:00
parent 6a845c67f0
commit f14c138438
28 changed files with 951 additions and 33 deletions

View File

@ -441,7 +441,7 @@ func BuildGenericConfig(s *options.ServerRunOptions) (*genericapiserver.Config,
return nil, nil, nil, nil, nil, fmt.Errorf("invalid authentication config: %v", err)
}
genericConfig.Authorizer, err = BuildAuthorizer(s, sharedInformers)
genericConfig.Authorizer, genericConfig.RuleResolver, err = BuildAuthorizer(s, sharedInformers)
if err != nil {
return nil, nil, nil, nil, nil, fmt.Errorf("invalid authorization config: %v", err)
}
@ -542,7 +542,7 @@ func BuildAuthenticator(s *options.ServerRunOptions, storageFactory serverstorag
}
// BuildAuthorizer constructs the authorizer
func BuildAuthorizer(s *options.ServerRunOptions, sharedInformers informers.SharedInformerFactory) (authorizer.Authorizer, error) {
func BuildAuthorizer(s *options.ServerRunOptions, sharedInformers informers.SharedInformerFactory) (authorizer.Authorizer, authorizer.RuleResolver, error) {
authorizationConfig := s.Authorization.ToAuthorizationConfig(sharedInformers)
return authorizationConfig.New()
}

View File

@ -190,7 +190,7 @@ func NonBlockingRun(s *options.ServerRunOptions, stopCh <-chan struct{}) error {
sharedInformers := informers.NewSharedInformerFactory(client, 10*time.Minute)
authorizationConfig := s.Authorization.ToAuthorizationConfig(sharedInformers)
apiAuthorizer, err := authorizationConfig.New()
apiAuthorizer, _, err := authorizationConfig.New()
if err != nil {
return fmt.Errorf("invalid Authorization Config: %v", err)
}

View File

@ -39,7 +39,7 @@ func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *r
&announced.GroupMetaFactoryArgs{
GroupName: authorization.GroupName,
VersionPreferenceOrder: []string{v1.SchemeGroupVersion.Version, v1beta1.SchemeGroupVersion.Version},
RootScopedKinds: sets.NewString("SubjectAccessReview", "SelfSubjectAccessReview"),
RootScopedKinds: sets.NewString("SubjectAccessReview", "SelfSubjectAccessReview", "SelfSubjectRulesReview"),
AddInternalObjectsToScheme: authorization.AddToScheme,
},
announced.VersionToSchemeFunc{

View File

@ -44,6 +44,7 @@ var (
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&SelfSubjectRulesReview{},
&SelfSubjectAccessReview{},
&SubjectAccessReview{},
&LocalSubjectAccessReview{},

View File

@ -149,3 +149,74 @@ type SubjectAccessReviewStatus struct {
// For instance, RBAC can be missing a role, but enough roles are still present and bound to reason about the request.
EvaluationError string
}
// +genclient
// +genclient:nonNamespaced
// +genclient:noVerbs
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// SelfSubjectRulesReview enumerates the set of actions the current user can perform within a namespace.
// The returned list of actions may be incomplete depending on the server's authorization mode,
// and any errors experienced during the evaluation. SelfSubjectRulesReview should be used by UIs to show/hide actions,
// or to quickly let an end user reason about their permissions. It should NOT Be used by external systems to
// drive authorization decisions as this raises confused deputy, cache lifetime/revocation, and correctness concerns.
// SubjectAccessReview, and LocalAccessReview are the correct way to defer authorization decisions to the API server.
type SelfSubjectRulesReview struct {
metav1.TypeMeta
metav1.ObjectMeta
// Spec holds information about the request being evaluated.
Spec SelfSubjectRulesReviewSpec
// Status is filled in by the server and indicates the set of actions a user can perform.
Status SubjectRulesReviewStatus
}
type SelfSubjectRulesReviewSpec struct {
// Namespace to evaluate rules for. Required.
Namespace string
}
// SubjectRulesReviewStatus contains the result of a rules check. This check can be incomplete depending on
// the set of authorizers the server is configured with and any errors experienced during evaluation.
// Because authorization rules are additive, if a rule appears in a list it's safe to assume the subject has that permission,
// even if that list is incomplete.
type SubjectRulesReviewStatus struct {
// ResourceRules is the list of actions the subject is allowed to perform on resources.
// The list ordering isn't significant, may contain duplicates, and possibly be incomplete.
ResourceRules []ResourceRule
// NonResourceRules is the list of actions the subject is allowed to perform on non-resources.
// The list ordering isn't significant, may contain duplicates, and possibly be incomplete.
NonResourceRules []NonResourceRule
// Incomplete is true when the rules returned by this call are incomplete. This is most commonly
// encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation.
Incomplete bool
// EvaluationError can appear in combination with Rules. It indicates an error occurred during
// rule evaluation, such as an authorizer that doesn't support rule evaluation, and that
// ResourceRules and/or NonResourceRules may be incomplete.
EvaluationError string
}
// ResourceRule is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant,
// may contain duplicates, and possibly be incomplete.
type ResourceRule struct {
// Verb is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. "*" means all.
Verbs []string
// APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of
// the enumerated resources in any API group will be allowed. "*" means all.
APIGroups []string
// Resources is a list of resources this rule applies to. ResourceAll represents all resources. "*" means all.
Resources []string
// ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. "*" means all.
ResourceNames []string
}
// NonResourceRule holds information that describes a rule for the non-resource
type NonResourceRule struct {
// Verb is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. "*" means all.
Verbs []string
// NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full,
// final step in the path. "*" means all.
NonResourceURLs []string
}

View File

@ -50,6 +50,10 @@ func ValidateSelfSubjectAccessReviewSpec(spec authorizationapi.SelfSubjectAccess
return allErrs
}
func ValidateSelfSubjectRulesReview(review *authorizationapi.SelfSubjectRulesReview) field.ErrorList {
return field.ErrorList{}
}
func ValidateSubjectAccessReview(sar *authorizationapi.SubjectAccessReview) field.ErrorList {
allErrs := ValidateSubjectAccessReviewSpec(sar.Spec, field.NewPath("spec"))
if !apiequality.Semantic.DeepEqual(metav1.ObjectMeta{}, sar.ObjectMeta) {

View File

@ -28,6 +28,7 @@ import (
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
api "k8s.io/kubernetes/pkg/apis/abac"
_ "k8s.io/kubernetes/pkg/apis/abac/latest"
@ -114,7 +115,7 @@ func NewFromFile(path string) (policyList, error) {
}
func matches(p api.Policy, a authorizer.Attributes) bool {
if subjectMatches(p, a) {
if subjectMatches(p, a.GetUser()) {
if verbMatches(p, a) {
// Resource and non-resource requests are mutually exclusive, at most one will match a policy
if resourceMatches(p, a) {
@ -129,15 +130,14 @@ func matches(p api.Policy, a authorizer.Attributes) bool {
}
// subjectMatches returns true if specified user and group properties in the policy match the attributes
func subjectMatches(p api.Policy, a authorizer.Attributes) bool {
func subjectMatches(p api.Policy, user user.Info) bool {
matched := false
username := ""
groups := []string{}
if user := a.GetUser(); user != nil {
username = user.GetName()
groups = user.GetGroups()
if user == nil {
return false
}
username := user.GetName()
groups := user.GetGroups()
// If the policy specified a user, ensure it matches
if len(p.Spec.User) > 0 {
@ -232,3 +232,42 @@ func (pl policyList) Authorize(a authorizer.Attributes) (bool, string, error) {
// policy file, compared to other steps such as encoding/decoding.
// Then, add Caching only if needed.
}
func (pl policyList) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
var (
resourceRules []authorizer.ResourceRuleInfo
nonResourceRules []authorizer.NonResourceRuleInfo
)
for _, p := range pl {
if subjectMatches(*p, user) {
if p.Spec.Namespace == "*" || p.Spec.Namespace == namespace {
if len(p.Spec.Resource) > 0 {
r := authorizer.DefaultResourceRuleInfo{
Verbs: getVerbs(p.Spec.Readonly),
APIGroups: []string{p.Spec.APIGroup},
Resources: []string{p.Spec.Resource},
}
var resourceRule authorizer.ResourceRuleInfo = &r
resourceRules = append(resourceRules, resourceRule)
}
if len(p.Spec.NonResourcePath) > 0 {
r := authorizer.DefaultNonResourceRuleInfo{
Verbs: getVerbs(p.Spec.Readonly),
NonResourceURLs: []string{p.Spec.NonResourcePath},
}
var nonResourceRule authorizer.NonResourceRuleInfo = &r
nonResourceRules = append(nonResourceRules, nonResourceRule)
}
}
}
}
return resourceRules, nonResourceRules, false, nil
}
func getVerbs(isReadOnly bool) []string {
if isReadOnly {
return []string{"get", "list", "watch"}
}
return []string{"*"}
}

View File

@ -19,6 +19,7 @@ package abac
import (
"io/ioutil"
"os"
"reflect"
"testing"
"k8s.io/apimachinery/pkg/runtime"
@ -141,6 +142,203 @@ func TestAuthorizeV0(t *testing.T) {
}
}
func getResourceRules(infos []authorizer.ResourceRuleInfo) []authorizer.DefaultResourceRuleInfo {
rules := make([]authorizer.DefaultResourceRuleInfo, len(infos))
for i, info := range infos {
rules[i] = authorizer.DefaultResourceRuleInfo{
Verbs: info.GetVerbs(),
APIGroups: info.GetAPIGroups(),
Resources: info.GetResources(),
ResourceNames: info.GetResourceNames(),
}
}
return rules
}
func getNonResourceRules(infos []authorizer.NonResourceRuleInfo) []authorizer.DefaultNonResourceRuleInfo {
rules := make([]authorizer.DefaultNonResourceRuleInfo, len(infos))
for i, info := range infos {
rules[i] = authorizer.DefaultNonResourceRuleInfo{
Verbs: info.GetVerbs(),
NonResourceURLs: info.GetNonResourceURLs(),
}
}
return rules
}
func TestRulesFor(t *testing.T) {
a, err := newWithContents(t, `
{ "readonly": true, "resource": "events" }
{"user":"scheduler", "readonly": true, "resource": "pods" }
{"user":"scheduler", "resource": "bindings" }
{"user":"kubelet", "readonly": true, "resource": "pods" }
{"user":"kubelet", "resource": "events" }
{"user":"alice", "namespace": "projectCaribou"}
{"user":"bob", "readonly": true, "namespace": "projectCaribou"}
{"user":"bob", "readonly": true, "nonResourcePath": "*"}
{"group":"a", "resource": "bindings" }
{"group":"b", "readonly": true, "nonResourcePath": "*"}
`)
if err != nil {
t.Fatalf("unable to read policy file: %v", err)
}
authenticatedGroup := []string{user.AllAuthenticated}
uScheduler := user.DefaultInfo{Name: "scheduler", UID: "uid1", Groups: authenticatedGroup}
uKubelet := user.DefaultInfo{Name: "kubelet", UID: "uid2", Groups: []string{"a", "b"}}
uAlice := user.DefaultInfo{Name: "alice", UID: "uid3", Groups: authenticatedGroup}
uBob := user.DefaultInfo{Name: "bob", UID: "uid4", Groups: authenticatedGroup}
uChuck := user.DefaultInfo{Name: "chuck", UID: "uid5", Groups: []string{"a", "b"}}
testCases := []struct {
User user.DefaultInfo
Namespace string
ExpectResourceRules []authorizer.DefaultResourceRuleInfo
ExpectNonResourceRules []authorizer.DefaultNonResourceRuleInfo
}{
{
User: uScheduler,
Namespace: "ns1",
ExpectResourceRules: []authorizer.DefaultResourceRuleInfo{
{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"events"},
},
{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"pods"},
},
{
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"bindings"},
},
},
ExpectNonResourceRules: []authorizer.DefaultNonResourceRuleInfo{},
},
{
User: uKubelet,
Namespace: "ns1",
ExpectResourceRules: []authorizer.DefaultResourceRuleInfo{
{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"pods"},
},
{
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"events"},
},
{
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"bindings"},
},
{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"*"},
},
},
ExpectNonResourceRules: []authorizer.DefaultNonResourceRuleInfo{
{
Verbs: []string{"get", "list", "watch"},
NonResourceURLs: []string{"*"},
},
},
},
{
User: uAlice,
Namespace: "projectCaribou",
ExpectResourceRules: []authorizer.DefaultResourceRuleInfo{
{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"events"},
},
{
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"*"},
},
},
ExpectNonResourceRules: []authorizer.DefaultNonResourceRuleInfo{},
},
{
User: uBob,
Namespace: "projectCaribou",
ExpectResourceRules: []authorizer.DefaultResourceRuleInfo{
{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"events"},
},
{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"*"},
},
{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"*"},
},
},
ExpectNonResourceRules: []authorizer.DefaultNonResourceRuleInfo{
{
Verbs: []string{"get", "list", "watch"},
NonResourceURLs: []string{"*"},
},
},
},
{
User: uChuck,
Namespace: "ns1",
ExpectResourceRules: []authorizer.DefaultResourceRuleInfo{
{
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"bindings"},
},
{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"*"},
},
},
ExpectNonResourceRules: []authorizer.DefaultNonResourceRuleInfo{
{
Verbs: []string{"get", "list", "watch"},
NonResourceURLs: []string{"*"},
},
},
},
}
for i, tc := range testCases {
attr := authorizer.AttributesRecord{
User: &tc.User,
Namespace: tc.Namespace,
}
resourceRules, nonResourceRules, _, _ := a.RulesFor(attr.GetUser(), attr.GetNamespace())
actualResourceRules := getResourceRules(resourceRules)
if !reflect.DeepEqual(tc.ExpectResourceRules, actualResourceRules) {
t.Logf("tc: %v -> attr %v", tc, attr)
t.Errorf("%d: Expected: \n%#v\n but actual: \n%#v\n",
i, tc.ExpectResourceRules, actualResourceRules)
}
actualNonResourceRules := getNonResourceRules(nonResourceRules)
if !reflect.DeepEqual(tc.ExpectNonResourceRules, actualNonResourceRules) {
t.Logf("tc: %v -> attr %v", tc, attr)
t.Errorf("%d: Expected: \n%#v\n but actual: \n%#v\n",
i, tc.ExpectNonResourceRules, actualNonResourceRules)
}
}
}
func TestAuthorizeV1beta1(t *testing.T) {
a, err := newWithContents(t,
`
@ -609,7 +807,7 @@ func TestSubjectMatches(t *testing.T) {
attr := authorizer.AttributesRecord{
User: &tc.User,
}
actualMatch := subjectMatches(*policy, attr)
actualMatch := subjectMatches(*policy, attr.GetUser())
if tc.ExpectMatch != actualMatch {
t.Errorf("%v: Expected actorMatches=%v but actually got=%v",
k, tc.ExpectMatch, actualMatch)
@ -617,7 +815,7 @@ func TestSubjectMatches(t *testing.T) {
}
}
func newWithContents(t *testing.T, contents string) (authorizer.Authorizer, error) {
func newWithContents(t *testing.T, contents string) (policyList, error) {
f, err := ioutil.TempFile("", "abac_test")
if err != nil {
t.Fatalf("unexpected error creating policyfile: %v", err)

View File

@ -359,6 +359,7 @@ var ignoredResources = map[schema.GroupResource]struct{}{
{Group: "authorization.k8s.io", Resource: "subjectaccessreviews"}: {},
{Group: "authorization.k8s.io", Resource: "selfsubjectaccessreviews"}: {},
{Group: "authorization.k8s.io", Resource: "localsubjectaccessreviews"}: {},
{Group: "authorization.k8s.io", Resource: "selfsubjectrulesreviews"}: {},
{Group: "apiregistration.k8s.io", Resource: "apiservices"}: {},
{Group: "apiextensions.k8s.io", Resource: "customresourcedefinitions"}: {},
}

View File

@ -56,17 +56,20 @@ type AuthorizationConfig struct {
// New returns the right sort of union of multiple authorizer.Authorizer objects
// based on the authorizationMode or an error.
func (config AuthorizationConfig) New() (authorizer.Authorizer, error) {
func (config AuthorizationConfig) New() (authorizer.Authorizer, authorizer.RuleResolver, error) {
if len(config.AuthorizationModes) == 0 {
return nil, errors.New("At least one authorization mode should be passed")
return nil, nil, errors.New("At least one authorization mode should be passed")
}
var authorizers []authorizer.Authorizer
var (
authorizers []authorizer.Authorizer
ruleResolvers []authorizer.RuleResolver
)
authorizerMap := make(map[string]bool)
for _, authorizationMode := range config.AuthorizationModes {
if authorizerMap[authorizationMode] {
return nil, fmt.Errorf("Authorization mode %s specified more than once", authorizationMode)
return nil, nil, fmt.Errorf("Authorization mode %s specified more than once", authorizationMode)
}
// Keep cases in sync with constant list above.
switch authorizationMode {
@ -81,29 +84,35 @@ func (config AuthorizationConfig) New() (authorizer.Authorizer, error) {
authorizers = append(authorizers, nodeAuthorizer)
case modes.ModeAlwaysAllow:
authorizers = append(authorizers, authorizerfactory.NewAlwaysAllowAuthorizer())
alwaysAllowAuthorizer := authorizerfactory.NewAlwaysAllowAuthorizer()
authorizers = append(authorizers, alwaysAllowAuthorizer)
ruleResolvers = append(ruleResolvers, alwaysAllowAuthorizer)
case modes.ModeAlwaysDeny:
authorizers = append(authorizers, authorizerfactory.NewAlwaysDenyAuthorizer())
alwaysDenyAuthorizer := authorizerfactory.NewAlwaysDenyAuthorizer()
authorizers = append(authorizers, alwaysDenyAuthorizer)
ruleResolvers = append(ruleResolvers, alwaysDenyAuthorizer)
case modes.ModeABAC:
if config.PolicyFile == "" {
return nil, errors.New("ABAC's authorization policy file not passed")
return nil, nil, errors.New("ABAC's authorization policy file not passed")
}
abacAuthorizer, err := abac.NewFromFile(config.PolicyFile)
if err != nil {
return nil, err
return nil, nil, err
}
authorizers = append(authorizers, abacAuthorizer)
ruleResolvers = append(ruleResolvers, abacAuthorizer)
case modes.ModeWebhook:
if config.WebhookConfigFile == "" {
return nil, errors.New("Webhook's configuration file not passed")
return nil, nil, errors.New("Webhook's configuration file not passed")
}
webhookAuthorizer, err := webhook.New(config.WebhookConfigFile,
config.WebhookCacheAuthorizedTTL,
config.WebhookCacheUnauthorizedTTL)
if err != nil {
return nil, err
return nil, nil, err
}
authorizers = append(authorizers, webhookAuthorizer)
ruleResolvers = append(ruleResolvers, webhookAuthorizer)
case modes.ModeRBAC:
rbacAuthorizer := rbac.New(
&rbac.RoleGetter{Lister: config.InformerFactory.Rbac().InternalVersion().Roles().Lister()},
@ -112,18 +121,19 @@ func (config AuthorizationConfig) New() (authorizer.Authorizer, error) {
&rbac.ClusterRoleBindingLister{Lister: config.InformerFactory.Rbac().InternalVersion().ClusterRoleBindings().Lister()},
)
authorizers = append(authorizers, rbacAuthorizer)
ruleResolvers = append(ruleResolvers, rbacAuthorizer)
default:
return nil, fmt.Errorf("Unknown authorization mode %s specified", authorizationMode)
return nil, nil, fmt.Errorf("Unknown authorization mode %s specified", authorizationMode)
}
authorizerMap[authorizationMode] = true
}
if !authorizerMap[modes.ModeABAC] && config.PolicyFile != "" {
return nil, errors.New("Cannot specify --authorization-policy-file without mode ABAC")
return nil, nil, errors.New("Cannot specify --authorization-policy-file without mode ABAC")
}
if !authorizerMap[modes.ModeWebhook] && config.WebhookConfigFile != "" {
return nil, errors.New("Cannot specify --authorization-webhook-config-file without mode Webhook")
return nil, nil, errors.New("Cannot specify --authorization-webhook-config-file without mode Webhook")
}
return union.New(authorizers...), nil
return union.New(authorizers...), union.NewRuleResolvers(ruleResolvers...), nil
}

View File

@ -91,7 +91,7 @@ func TestNew(t *testing.T) {
}
for _, tt := range tests {
_, err := tt.config.New()
_, _, err := tt.config.New()
if tt.wantErr && (err == nil) {
t.Errorf("New %s", tt.msg)
} else if !tt.wantErr && (err != nil) {

View File

@ -256,7 +256,7 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget)
// handlers that we have.
restStorageProviders := []RESTStorageProvider{
authenticationrest.RESTStorageProvider{Authenticator: c.GenericConfig.Authenticator},
authorizationrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorizer},
authorizationrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorizer, RuleResolver: c.GenericConfig.RuleResolver},
autoscalingrest.RESTStorageProvider{},
batchrest.RESTStorageProvider{},
certificatesrest.RESTStorageProvider{},

View File

@ -28,11 +28,13 @@ import (
"k8s.io/kubernetes/pkg/apis/authorization"
"k8s.io/kubernetes/pkg/registry/authorization/localsubjectaccessreview"
"k8s.io/kubernetes/pkg/registry/authorization/selfsubjectaccessreview"
"k8s.io/kubernetes/pkg/registry/authorization/selfsubjectrulesreview"
"k8s.io/kubernetes/pkg/registry/authorization/subjectaccessreview"
)
type RESTStorageProvider struct {
Authorizer authorizer.Authorizer
Authorizer authorizer.Authorizer
RuleResolver authorizer.RuleResolver
}
func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) {
@ -70,6 +72,9 @@ func (p RESTStorageProvider) v1beta1Storage(apiResourceConfigSource serverstorag
if apiResourceConfigSource.ResourceEnabled(version.WithResource("localsubjectaccessreviews")) {
storage["localsubjectaccessreviews"] = localsubjectaccessreview.NewREST(p.Authorizer)
}
if apiResourceConfigSource.ResourceEnabled(version.WithResource("selfsubjectrulesreviews")) {
storage["selfsubjectrulesreviews"] = selfsubjectrulesreview.NewREST(p.RuleResolver)
}
return storage
}
@ -87,6 +92,9 @@ func (p RESTStorageProvider) v1Storage(apiResourceConfigSource serverstorage.API
if apiResourceConfigSource.ResourceEnabled(version.WithResource("localsubjectaccessreviews")) {
storage["localsubjectaccessreviews"] = localsubjectaccessreview.NewREST(p.Authorizer)
}
if apiResourceConfigSource.ResourceEnabled(version.WithResource("selfsubjectrulesreviews")) {
storage["selfsubjectrulesreviews"] = selfsubjectrulesreview.NewREST(p.RuleResolver)
}
return storage
}

View File

@ -0,0 +1,99 @@
/*
Copyright 2017 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 selfsubjectrulesreview
import (
"fmt"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authorization/authorizer"
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
authorizationapi "k8s.io/kubernetes/pkg/apis/authorization"
)
// REST implements a RESTStorage for selfsubjectrulesreview.
type REST struct {
ruleResolver authorizer.RuleResolver
}
// NewREST returns a RESTStorage object that will work against selfsubjectrulesreview.
func NewREST(ruleResolver authorizer.RuleResolver) *REST {
return &REST{ruleResolver}
}
// New creates a new selfsubjectrulesreview object.
func (r *REST) New() runtime.Object {
return &authorizationapi.SelfSubjectRulesReview{}
}
// Create attempts to get self subject rules in specific namespace.
func (r *REST) Create(ctx genericapirequest.Context, obj runtime.Object, includeUninitialized bool) (runtime.Object, error) {
selfSRR, ok := obj.(*authorizationapi.SelfSubjectRulesReview)
if !ok {
return nil, apierrors.NewBadRequest(fmt.Sprintf("not a SelfSubjectRulesReview: %#v", obj))
}
user, ok := genericapirequest.UserFrom(ctx)
if !ok {
return nil, apierrors.NewBadRequest("no user present on request")
}
namespace := selfSRR.Spec.Namespace
if namespace == "" {
return nil, apierrors.NewBadRequest("no namespace on request")
}
resourceInfo, nonResourceInfo, incomplete, err := r.ruleResolver.RulesFor(user, namespace)
ret := &authorizationapi.SelfSubjectRulesReview{
Status: authorizationapi.SubjectRulesReviewStatus{
ResourceRules: getResourceRules(resourceInfo),
NonResourceRules: getNonResourceRules(nonResourceInfo),
Incomplete: incomplete,
},
}
if err != nil {
ret.Status.EvaluationError = err.Error()
}
return ret, nil
}
func getResourceRules(infos []authorizer.ResourceRuleInfo) []authorizationapi.ResourceRule {
rules := make([]authorizationapi.ResourceRule, len(infos))
for i, info := range infos {
rules[i] = authorizationapi.ResourceRule{
Verbs: info.GetVerbs(),
APIGroups: info.GetAPIGroups(),
Resources: info.GetResources(),
ResourceNames: info.GetResourceNames(),
}
}
return rules
}
func getNonResourceRules(infos []authorizer.NonResourceRuleInfo) []authorizationapi.NonResourceRule {
rules := make([]authorizationapi.NonResourceRule, len(infos))
for i, info := range infos {
rules[i] = authorizationapi.NonResourceRule{
Verbs: info.GetVerbs(),
NonResourceURLs: info.GetNonResourceURLs(),
}
}
return rules
}

View File

@ -123,6 +123,36 @@ func (r *RBACAuthorizer) Authorize(requestAttributes authorizer.Attributes) (boo
return false, reason, nil
}
func (r *RBACAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
var (
resourceRules []authorizer.ResourceRuleInfo
nonResourceRules []authorizer.NonResourceRuleInfo
)
policyRules, err := r.authorizationRuleResolver.RulesFor(user, namespace)
for _, policyRule := range policyRules {
if len(policyRule.Resources) > 0 {
r := authorizer.DefaultResourceRuleInfo{
Verbs: policyRule.Verbs,
APIGroups: policyRule.APIGroups,
Resources: policyRule.Resources,
ResourceNames: policyRule.ResourceNames,
}
var resourceRule authorizer.ResourceRuleInfo = &r
resourceRules = append(resourceRules, resourceRule)
}
if len(policyRule.NonResourceURLs) > 0 {
r := authorizer.DefaultNonResourceRuleInfo{
Verbs: policyRule.Verbs,
NonResourceURLs: policyRule.NonResourceURLs,
}
var nonResourceRule authorizer.NonResourceRuleInfo = &r
nonResourceRules = append(nonResourceRules, nonResourceRule)
}
}
return resourceRules, nonResourceRules, false, err
}
func New(roles rbacregistryvalidation.RoleGetter, roleBindings rbacregistryvalidation.RoleBindingLister, clusterRoles rbacregistryvalidation.ClusterRoleGetter, clusterRoleBindings rbacregistryvalidation.ClusterRoleBindingLister) *RBACAuthorizer {
authorizer := &RBACAuthorizer{
authorizationRuleResolver: rbacregistryvalidation.NewDefaultRuleResolver(

View File

@ -44,6 +44,7 @@ var (
// Adds the list of known types to api.Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&SelfSubjectRulesReview{},
&SelfSubjectAccessReview{},
&SubjectAccessReview{},
&LocalSubjectAccessReview{},

View File

@ -180,3 +180,82 @@ type SubjectAccessReviewStatus struct {
// +optional
EvaluationError string `json:"evaluationError,omitempty" protobuf:"bytes,3,opt,name=evaluationError"`
}
// +genclient
// +genclient:nonNamespaced
// +genclient:noVerbs
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// SelfSubjectRulesReview enumerates the set of actions the current user can perform within a namespace.
// The returned list of actions may be incomplete depending on the server's authorization mode,
// and any errors experienced during the evaluation. SelfSubjectRulesReview should be used by UIs to show/hide actions,
// or to quickly let an end user reason about their permissions. It should NOT Be used by external systems to
// drive authorization decisions as this raises confused deputy, cache lifetime/revocation, and correctness concerns.
// SubjectAccessReview, and LocalAccessReview are the correct way to defer authorization decisions to the API server.
type SelfSubjectRulesReview struct {
metav1.TypeMeta `json:",inline"`
// +optional
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// Spec holds information about the request being evaluated.
Spec SelfSubjectRulesReviewSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"`
// Status is filled in by the server and indicates the set of actions a user can perform.
// +optional
Status SubjectRulesReviewStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}
type SelfSubjectRulesReviewSpec struct {
// Namespace to evaluate rules for. Required.
Namespace string `json:"namespace,omitempty" protobuf:"bytes,1,opt,name=namespace"`
}
// SubjectRulesReviewStatus contains the result of a rules check. This check can be incomplete depending on
// the set of authorizers the server is configured with and any errors experienced during evaluation.
// Because authorization rules are additive, if a rule appears in a list it's safe to assume the subject has that permission,
// even if that list is incomplete.
type SubjectRulesReviewStatus struct {
// ResourceRules is the list of actions the subject is allowed to perform on resources.
// The list ordering isn't significant, may contain duplicates, and possibly be incomplete.
ResourceRules []ResourceRule `json:"resourceRules" protobuf:"bytes,1,rep,name=resourceRules"`
// NonResourceRules is the list of actions the subject is allowed to perform on non-resources.
// The list ordering isn't significant, may contain duplicates, and possibly be incomplete.
NonResourceRules []NonResourceRule `json:"nonResourceRules" protobuf:"bytes,2,rep,name=nonResourceRules"`
// Incomplete is true when the rules returned by this call are incomplete. This is most commonly
// encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation.
Incomplete bool `json:"incomplete" protobuf:"bytes,3,rep,name=incomplete"`
// EvaluationError can appear in combination with Rules. It indicates an error occurred during
// rule evaluation, such as an authorizer that doesn't support rule evaluation, and that
// ResourceRules and/or NonResourceRules may be incomplete.
// +optional
EvaluationError string `json:"evaluationError,omitempty" protobuf:"bytes,4,opt,name=evaluationError"`
}
// ResourceRule is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant,
// may contain duplicates, and possibly be incomplete.
type ResourceRule struct {
// Verb is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. "*" means all.
Verbs []string `json:"verbs" protobuf:"bytes,1,rep,name=verbs"`
// APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of
// the enumerated resources in any API group will be allowed. "*" means all.
// +optional
APIGroups []string `json:"apiGroups,omitempty" protobuf:"bytes,2,rep,name=apiGroups"`
// Resources is a list of resources this rule applies to. ResourceAll represents all resources. "*" means all.
// +optional
Resources []string `json:"resources,omitempty" protobuf:"bytes,3,rep,name=resources"`
// ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. "*" means all.
// +optional
ResourceNames []string `json:"resourceNames,omitempty" protobuf:"bytes,4,rep,name=resourceNames"`
}
// NonResourceRule holds information that describes a rule for the non-resource
type NonResourceRule struct {
// Verb is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. "*" means all.
Verbs []string `json:"verbs" protobuf:"bytes,1,rep,name=verbs"`
// NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full,
// final step in the path. "*" means all.
// +optional
NonResourceURLs []string `json:"nonResourceURLs,omitempty" protobuf:"bytes,2,rep,name=nonResourceURLs"`
}

View File

@ -44,6 +44,7 @@ var (
// Adds the list of known types to api.Scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&SelfSubjectRulesReview{},
&SelfSubjectAccessReview{},
&SubjectAccessReview{},
&LocalSubjectAccessReview{},

View File

@ -180,3 +180,82 @@ type SubjectAccessReviewStatus struct {
// +optional
EvaluationError string `json:"evaluationError,omitempty" protobuf:"bytes,3,opt,name=evaluationError"`
}
// +genclient
// +genclient:nonNamespaced
// +genclient:noVerbs
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// SelfSubjectRulesReview enumerates the set of actions the current user can perform within a namespace.
// The returned list of actions may be incomplete depending on the server's authorization mode,
// and any errors experienced during the evaluation. SelfSubjectRulesReview should be used by UIs to show/hide actions,
// or to quickly let an end user reason about their permissions. It should NOT Be used by external systems to
// drive authorization decisions as this raises confused deputy, cache lifetime/revocation, and correctness concerns.
// SubjectAccessReview, and LocalAccessReview are the correct way to defer authorization decisions to the API server.
type SelfSubjectRulesReview struct {
metav1.TypeMeta `json:",inline"`
// +optional
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// Spec holds information about the request being evaluated.
Spec SelfSubjectRulesReviewSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"`
// Status is filled in by the server and indicates the set of actions a user can perform.
// +optional
Status SubjectRulesReviewStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}
type SelfSubjectRulesReviewSpec struct {
// Namespace to evaluate rules for. Required.
Namespace string `json:"namespace,omitempty" protobuf:"bytes,1,opt,name=namespace"`
}
// SubjectRulesReviewStatus contains the result of a rules check. This check can be incomplete depending on
// the set of authorizers the server is configured with and any errors experienced during evaluation.
// Because authorization rules are additive, if a rule appears in a list it's safe to assume the subject has that permission,
// even if that list is incomplete.
type SubjectRulesReviewStatus struct {
// ResourceRules is the list of actions the subject is allowed to perform on resources.
// The list ordering isn't significant, may contain duplicates, and possibly be incomplete.
ResourceRules []ResourceRule `json:"resourceRules" protobuf:"bytes,1,rep,name=resourceRules"`
// NonResourceRules is the list of actions the subject is allowed to perform on non-resources.
// The list ordering isn't significant, may contain duplicates, and possibly be incomplete.
NonResourceRules []NonResourceRule `json:"nonResourceRules" protobuf:"bytes,2,rep,name=nonResourceRules"`
// Incomplete is true when the rules returned by this call are incomplete. This is most commonly
// encountered when an authorizer, such as an external authorizer, doesn't support rules evaluation.
Incomplete bool `json:"incomplete" protobuf:"bytes,3,rep,name=incomplete"`
// EvaluationError can appear in combination with Rules. It indicates an error occurred during
// rule evaluation, such as an authorizer that doesn't support rule evaluation, and that
// ResourceRules and/or NonResourceRules may be incomplete.
// +optional
EvaluationError string `json:"evaluationError,omitempty" protobuf:"bytes,4,opt,name=evaluationError"`
}
// ResourceRule is the list of actions the subject is allowed to perform on resources. The list ordering isn't significant,
// may contain duplicates, and possibly be incomplete.
type ResourceRule struct {
// Verb is a list of kubernetes resource API verbs, like: get, list, watch, create, update, delete, proxy. "*" means all.
Verbs []string `json:"verbs" protobuf:"bytes,1,rep,name=verbs"`
// APIGroups is the name of the APIGroup that contains the resources. If multiple API groups are specified, any action requested against one of
// the enumerated resources in any API group will be allowed. "*" means all.
// +optional
APIGroups []string `json:"apiGroups,omitempty" protobuf:"bytes,2,rep,name=apiGroups"`
// Resources is a list of resources this rule applies to. ResourceAll represents all resources. "*" means all.
// +optional
Resources []string `json:"resources,omitempty" protobuf:"bytes,3,rep,name=resources"`
// ResourceNames is an optional white list of names that the rule applies to. An empty set means that everything is allowed. "*" means all.
// +optional
ResourceNames []string `json:"resourceNames,omitempty" protobuf:"bytes,4,rep,name=resourceNames"`
}
// NonResourceRule holds information that describes a rule for the non-resource
type NonResourceRule struct {
// Verb is a list of kubernetes non-resource API verbs, like: get, post, put, delete, patch, head, options. "*" means all.
Verbs []string `json:"verbs" protobuf:"bytes,1,rep,name=verbs"`
// NonResourceURLs is a set of partial urls that a user should have access to. *s are allowed, but only as the full,
// final step in the path. "*" means all.
// +optional
NonResourceURLs []string `json:"nonResourceURLs,omitempty" protobuf:"bytes,2,rep,name=nonResourceURLs"`
}

View File

@ -76,6 +76,12 @@ func (f AuthorizerFunc) Authorize(a Attributes) (bool, string, error) {
return f(a)
}
// RuleResolver provides a mechanism for resolving the list of rules that apply to a given user within a namespace.
type RuleResolver interface {
// RulesFor get the list of cluster wide rules, the list of rules in the specific namespace, incomplete status and errors.
RulesFor(user user.Info, namespace string) ([]ResourceRuleInfo, []NonResourceRuleInfo, bool, error)
}
// RequestAttributesGetter provides a function that extracts Attributes from an http.Request
type RequestAttributesGetter interface {
GetRequestAttributes(user.Info, *http.Request) Attributes

View File

@ -0,0 +1,73 @@
/*
Copyright 2017 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 authorizer
type ResourceRuleInfo interface {
// GetVerbs returns a list of kubernetes resource API verbs.
GetVerbs() []string
// GetAPIGroups return the names of the APIGroup that contains the resources.
GetAPIGroups() []string
// GetResources return a list of resources the rule applies to.
GetResources() []string
// GetResourceNames return a white list of names that the rule applies to.
GetResourceNames() []string
}
// DefaultResourceRuleInfo holds information that describes a rule for the resource
type DefaultResourceRuleInfo struct {
Verbs []string
APIGroups []string
Resources []string
ResourceNames []string
}
func (i *DefaultResourceRuleInfo) GetVerbs() []string {
return i.Verbs
}
func (i *DefaultResourceRuleInfo) GetAPIGroups() []string {
return i.APIGroups
}
func (i *DefaultResourceRuleInfo) GetResources() []string {
return i.Resources
}
func (i *DefaultResourceRuleInfo) GetResourceNames() []string {
return i.ResourceNames
}
type NonResourceRuleInfo interface {
// GetVerbs returns a list of kubernetes resource API verbs.
GetVerbs() []string
// GetNonResourceURLs return a set of partial urls that a user should have access to.
GetNonResourceURLs() []string
}
// DefaultNonResourceRuleInfo holds information that describes a rule for the non-resource
type DefaultNonResourceRuleInfo struct {
Verbs []string
NonResourceURLs []string
}
func (i *DefaultNonResourceRuleInfo) GetVerbs() []string {
return i.Verbs
}
func (i *DefaultNonResourceRuleInfo) GetNonResourceURLs() []string {
return i.NonResourceURLs
}

View File

@ -19,6 +19,7 @@ package authorizerfactory
import (
"errors"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
)
@ -31,7 +32,22 @@ func (alwaysAllowAuthorizer) Authorize(a authorizer.Attributes) (authorized bool
return true, "", nil
}
func NewAlwaysAllowAuthorizer() authorizer.Authorizer {
func (alwaysAllowAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
return []authorizer.ResourceRuleInfo{
&authorizer.DefaultResourceRuleInfo{
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"*"},
},
}, []authorizer.NonResourceRuleInfo{
&authorizer.DefaultNonResourceRuleInfo{
Verbs: []string{"*"},
NonResourceURLs: []string{"*"},
},
}, false, nil
}
func NewAlwaysAllowAuthorizer() *alwaysAllowAuthorizer {
return new(alwaysAllowAuthorizer)
}
@ -44,7 +60,11 @@ func (alwaysDenyAuthorizer) Authorize(a authorizer.Attributes) (authorized bool,
return false, "Everything is forbidden.", nil
}
func NewAlwaysDenyAuthorizer() authorizer.Authorizer {
func (alwaysDenyAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
return []authorizer.ResourceRuleInfo{}, []authorizer.NonResourceRuleInfo{}, false, nil
}
func NewAlwaysDenyAuthorizer() *alwaysDenyAuthorizer {
return new(alwaysDenyAuthorizer)
}

View File

@ -20,6 +20,7 @@ import (
"strings"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
)
@ -55,3 +56,40 @@ func (authzHandler unionAuthzHandler) Authorize(a authorizer.Attributes) (bool,
return false, strings.Join(reasonlist, "\n"), utilerrors.NewAggregate(errlist)
}
// unionAuthzRulesHandler authorizer against a chain of authorizer.RuleResolver
type unionAuthzRulesHandler []authorizer.RuleResolver
// NewRuleResolvers returns an authorizer that authorizes against a chain of authorizer.Authorizer objects
func NewRuleResolvers(authorizationHandlers ...authorizer.RuleResolver) authorizer.RuleResolver {
return unionAuthzRulesHandler(authorizationHandlers)
}
// RulesFor against a chain of authorizer.RuleResolver objects and returns nil if successful and returns error if unsuccessful
func (authzHandler unionAuthzRulesHandler) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
var (
errList []error
resourceRulesList []authorizer.ResourceRuleInfo
nonResourceRulesList []authorizer.NonResourceRuleInfo
)
incompleteStatus := false
for _, currAuthzHandler := range authzHandler {
resourceRules, nonResourceRules, incomplete, err := currAuthzHandler.RulesFor(user, namespace)
if incomplete == true {
incompleteStatus = true
}
if err != nil {
errList = append(errList, err)
}
if len(resourceRules) > 0 {
resourceRulesList = append(resourceRulesList, resourceRules...)
}
if len(nonResourceRules) > 0 {
nonResourceRulesList = append(nonResourceRulesList, nonResourceRules...)
}
}
return resourceRulesList, nonResourceRulesList, incompleteStatus, utilerrors.NewAggregate(errList)
}

View File

@ -18,8 +18,10 @@ package union
import (
"fmt"
"reflect"
"testing"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
)
@ -81,3 +83,143 @@ func TestAuthorizationError(t *testing.T) {
t.Errorf("Expected error: %v", err)
}
}
type mockAuthzRuleHandler struct {
resourceRules []authorizer.ResourceRuleInfo
nonResourceRules []authorizer.NonResourceRuleInfo
err error
}
func (mock *mockAuthzRuleHandler) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
if mock.err != nil {
return []authorizer.ResourceRuleInfo{}, []authorizer.NonResourceRuleInfo{}, false, mock.err
}
return mock.resourceRules, mock.nonResourceRules, false, nil
}
func TestAuthorizationResourceRules(t *testing.T) {
handler1 := &mockAuthzRuleHandler{
resourceRules: []authorizer.ResourceRuleInfo{
&authorizer.DefaultResourceRuleInfo{
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"bindings"},
},
&authorizer.DefaultResourceRuleInfo{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"*"},
},
},
}
handler2 := &mockAuthzRuleHandler{
resourceRules: []authorizer.ResourceRuleInfo{
&authorizer.DefaultResourceRuleInfo{
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"events"},
},
&authorizer.DefaultResourceRuleInfo{
Verbs: []string{"get"},
APIGroups: []string{"*"},
Resources: []string{"*"},
ResourceNames: []string{"foo"},
},
},
}
expected := []authorizer.DefaultResourceRuleInfo{
{
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"bindings"},
},
{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"*"},
},
{
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"events"},
},
{
Verbs: []string{"get"},
APIGroups: []string{"*"},
Resources: []string{"*"},
ResourceNames: []string{"foo"},
},
}
authzRulesHandler := NewRuleResolvers(handler1, handler2)
rules, _, _, _ := authzRulesHandler.RulesFor(nil, "")
actual := getResourceRules(rules)
if !reflect.DeepEqual(expected, actual) {
t.Errorf("Expected: \n%#v\n but actual: \n%#v\n", expected, actual)
}
}
func TestAuthorizationNonResourceRules(t *testing.T) {
handler1 := &mockAuthzRuleHandler{
nonResourceRules: []authorizer.NonResourceRuleInfo{
&authorizer.DefaultNonResourceRuleInfo{
Verbs: []string{"get"},
NonResourceURLs: []string{"/api"},
},
},
}
handler2 := &mockAuthzRuleHandler{
nonResourceRules: []authorizer.NonResourceRuleInfo{
&authorizer.DefaultNonResourceRuleInfo{
Verbs: []string{"get"},
NonResourceURLs: []string{"/api/*"},
},
},
}
expected := []authorizer.DefaultNonResourceRuleInfo{
{
Verbs: []string{"get"},
NonResourceURLs: []string{"/api"},
},
{
Verbs: []string{"get"},
NonResourceURLs: []string{"/api/*"},
},
}
authzRulesHandler := NewRuleResolvers(handler1, handler2)
_, rules, _, _ := authzRulesHandler.RulesFor(nil, "")
actual := getNonResourceRules(rules)
if !reflect.DeepEqual(expected, actual) {
t.Errorf("Expected: \n%#v\n but actual: \n%#v\n", expected, actual)
}
}
func getResourceRules(infos []authorizer.ResourceRuleInfo) []authorizer.DefaultResourceRuleInfo {
rules := make([]authorizer.DefaultResourceRuleInfo, len(infos))
for i, info := range infos {
rules[i] = authorizer.DefaultResourceRuleInfo{
Verbs: info.GetVerbs(),
APIGroups: info.GetAPIGroups(),
Resources: info.GetResources(),
ResourceNames: info.GetResourceNames(),
}
}
return rules
}
func getNonResourceRules(infos []authorizer.NonResourceRuleInfo) []authorizer.DefaultNonResourceRuleInfo {
rules := make([]authorizer.DefaultNonResourceRuleInfo, len(infos))
for i, info := range infos {
rules[i] = authorizer.DefaultNonResourceRuleInfo{
Verbs: info.GetVerbs(),
NonResourceURLs: info.GetNonResourceURLs(),
}
}
return rules
}

View File

@ -87,6 +87,9 @@ type Config struct {
// Authorizer determines whether the subject is allowed to make the request based only
// on the RequestURI
Authorizer authorizer.Authorizer
// RuleResolver is required to get the list of rules that apply to a given user
// in a given namespace
RuleResolver authorizer.RuleResolver
// AdmissionControl performs deep inspection of a given request (including content)
// to set values and determine whether its allowed
AdmissionControl admission.Interface

View File

@ -28,6 +28,7 @@ import (
"k8s.io/apimachinery/pkg/apimachinery/registered"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/cache"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/util/webhook"
"k8s.io/client-go/kubernetes/scheme"
@ -196,6 +197,16 @@ func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (authorized bo
return r.Status.Allowed, r.Status.Reason, nil
}
//TODO: need to finish the method to get the rules when using webhook mode
func (w *WebhookAuthorizer) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
var (
resourceRules []authorizer.ResourceRuleInfo
nonResourceRules []authorizer.NonResourceRuleInfo
)
incomplete := true
return resourceRules, nonResourceRules, incomplete, fmt.Errorf("webhook authorizer does not support user rule resolution")
}
func convertToSARExtra(extra map[string][]string) map[string]authorization.ExtraValue {
if extra == nil {
return nil

View File

@ -74,7 +74,7 @@ func TestNodeAuthorizer(t *testing.T) {
AuthorizationModes: []string{"Node", "RBAC"},
InformerFactory: informerFactory,
}
nodeRBACAuthorizer, err := authorizerConfig.New()
nodeRBACAuthorizer, _, err := authorizerConfig.New()
if err != nil {
t.Fatal(err)
}

View File

@ -394,6 +394,8 @@ var ephemeralWhiteList = createEphemeralWhiteList(
// k8s.io/kubernetes/pkg/apis/authorization/v1beta1
// SRR objects that are not stored in etcd
gvr("authorization.k8s.io", "v1beta1", "selfsubjectrulesreviews"),
// SAR objects that are not stored in etcd
gvr("authorization.k8s.io", "v1beta1", "selfsubjectaccessreviews"),
gvr("authorization.k8s.io", "v1beta1", "localsubjectaccessreviews"),
@ -402,6 +404,8 @@ var ephemeralWhiteList = createEphemeralWhiteList(
// k8s.io/kubernetes/pkg/apis/authorization/v1
// SRR objects that are not stored in etcd
gvr("authorization.k8s.io", "v1", "selfsubjectrulesreviews"),
// SAR objects that are not stored in etcd
gvr("authorization.k8s.io", "v1", "selfsubjectaccessreviews"),
gvr("authorization.k8s.io", "v1", "localsubjectaccessreviews"),