...

Source file src/k8s.io/kubernetes/pkg/registry/rbac/role/policybased/storage_test.go

Documentation: k8s.io/kubernetes/pkg/registry/rbac/role/policybased

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package policybased
    18  
    19  import (
    20  	"context"
    21  	"testing"
    22  
    23  	rbacv1 "k8s.io/api/rbac/v1"
    24  	"k8s.io/apimachinery/pkg/api/errors"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  	"k8s.io/apiserver/pkg/authentication/user"
    28  	"k8s.io/apiserver/pkg/authorization/authorizer"
    29  	"k8s.io/apiserver/pkg/endpoints/request"
    30  	"k8s.io/apiserver/pkg/registry/rest"
    31  	"k8s.io/kubernetes/pkg/apis/rbac"
    32  	_ "k8s.io/kubernetes/pkg/apis/rbac/install"
    33  	"k8s.io/kubernetes/pkg/registry/rbac/validation"
    34  )
    35  
    36  func TestEscalation(t *testing.T) {
    37  	createContext := request.WithRequestInfo(request.WithNamespace(context.TODO(), "myns"), &request.RequestInfo{
    38  		IsResourceRequest: true,
    39  		Verb:              "create",
    40  		APIGroup:          "rbac.authorization.k8s.io",
    41  		APIVersion:        "v1",
    42  		Namespace:         "myns",
    43  		Resource:          "roles",
    44  		Name:              "",
    45  	})
    46  	updateContext := request.WithRequestInfo(request.WithNamespace(context.TODO(), "myns"), &request.RequestInfo{
    47  		IsResourceRequest: true,
    48  		Verb:              "update",
    49  		APIGroup:          "rbac.authorization.k8s.io",
    50  		APIVersion:        "v1",
    51  		Namespace:         "myns",
    52  		Resource:          "roles",
    53  		Name:              "myrole",
    54  	})
    55  
    56  	superuser := &user.DefaultInfo{Name: "superuser", Groups: []string{"system:masters"}}
    57  	bob := &user.DefaultInfo{Name: "bob"}
    58  	steve := &user.DefaultInfo{Name: "steve"}
    59  	alice := &user.DefaultInfo{Name: "alice"}
    60  
    61  	authzCalled := 0
    62  	fakeStorage := &fakeStorage{}
    63  	fakeAuthorizer := authorizer.AuthorizerFunc(func(ctx context.Context, attr authorizer.Attributes) (authorizer.Decision, string, error) {
    64  		authzCalled++
    65  		if attr.GetUser().GetName() == "steve" {
    66  			return authorizer.DecisionAllow, "", nil
    67  		}
    68  		return authorizer.DecisionNoOpinion, "", nil
    69  	})
    70  	fakeRuleResolver, _ := validation.NewTestRuleResolver(
    71  		nil,
    72  		nil,
    73  		[]*rbacv1.ClusterRole{{ObjectMeta: metav1.ObjectMeta{Name: "alice-role"}, Rules: []rbacv1.PolicyRule{{APIGroups: []string{"*"}, Resources: []string{"*"}, Verbs: []string{"*"}}}}},
    74  		[]*rbacv1.ClusterRoleBinding{{RoleRef: rbacv1.RoleRef{Name: "alice-role", APIGroup: "rbac.authorization.k8s.io", Kind: "ClusterRole"}, Subjects: []rbacv1.Subject{{Name: "alice", Kind: "User", APIGroup: "rbac.authorization.k8s.io"}}}},
    75  	)
    76  
    77  	role := &rbac.Role{
    78  		ObjectMeta: metav1.ObjectMeta{Name: "myrole", Namespace: "myns"},
    79  		Rules:      []rbac.PolicyRule{{APIGroups: []string{""}, Verbs: []string{"get"}, Resources: []string{"pods"}}},
    80  	}
    81  
    82  	s := NewStorage(fakeStorage, fakeAuthorizer, fakeRuleResolver)
    83  
    84  	testcases := []struct {
    85  		name          string
    86  		user          user.Info
    87  		expectAllowed bool
    88  		expectAuthz   bool
    89  	}{
    90  		// superuser doesn't even trigger an authz check, and is allowed
    91  		{
    92  			name:          "superuser",
    93  			user:          superuser,
    94  			expectAuthz:   false,
    95  			expectAllowed: true,
    96  		},
    97  		// bob triggers an authz check, is disallowed by the authorizer, and has no RBAC permissions, so is not allowed
    98  		{
    99  			name:          "bob",
   100  			user:          bob,
   101  			expectAuthz:   true,
   102  			expectAllowed: false,
   103  		},
   104  		// steve triggers an authz check, is allowed by the authorizer, and has no RBAC permissions, but is still allowed
   105  		{
   106  			name:          "steve",
   107  			user:          steve,
   108  			expectAuthz:   true,
   109  			expectAllowed: true,
   110  		},
   111  		// alice triggers an authz check, is denied by the authorizer, but has RBAC permissions in the fakeRuleResolver, so is allowed
   112  		{
   113  			name:          "alice",
   114  			user:          alice,
   115  			expectAuthz:   true,
   116  			expectAllowed: true,
   117  		},
   118  	}
   119  
   120  	for _, tc := range testcases {
   121  		t.Run(tc.name, func(t *testing.T) {
   122  			authzCalled, fakeStorage.created, fakeStorage.updated = 0, 0, 0
   123  			_, err := s.Create(request.WithUser(createContext, tc.user), role, nil, nil)
   124  
   125  			if tc.expectAllowed {
   126  				if err != nil {
   127  					t.Error(err)
   128  					return
   129  				}
   130  				if fakeStorage.created != 1 {
   131  					t.Errorf("unexpected calls to underlying storage.Create: %d", fakeStorage.created)
   132  					return
   133  				}
   134  			} else {
   135  				if !errors.IsForbidden(err) {
   136  					t.Errorf("expected forbidden, got %v", err)
   137  					return
   138  				}
   139  				if fakeStorage.created != 0 {
   140  					t.Errorf("unexpected calls to underlying storage.Create: %d", fakeStorage.created)
   141  					return
   142  				}
   143  			}
   144  
   145  			if tc.expectAuthz != (authzCalled > 0) {
   146  				t.Fatalf("expected authz=%v, saw %d calls", tc.expectAuthz, authzCalled)
   147  			}
   148  
   149  			authzCalled, fakeStorage.created, fakeStorage.updated = 0, 0, 0
   150  			_, _, err = s.Update(request.WithUser(updateContext, tc.user), role.Name, rest.DefaultUpdatedObjectInfo(role), nil, nil, false, nil)
   151  
   152  			if tc.expectAllowed {
   153  				if err != nil {
   154  					t.Error(err)
   155  					return
   156  				}
   157  				if fakeStorage.updated != 1 {
   158  					t.Errorf("unexpected calls to underlying storage.Update: %d", fakeStorage.updated)
   159  					return
   160  				}
   161  			} else {
   162  				if !errors.IsForbidden(err) {
   163  					t.Errorf("expected forbidden, got %v", err)
   164  					return
   165  				}
   166  				if fakeStorage.updated != 0 {
   167  					t.Errorf("unexpected calls to underlying storage.Update: %d", fakeStorage.updated)
   168  					return
   169  				}
   170  			}
   171  
   172  			if tc.expectAuthz != (authzCalled > 0) {
   173  				t.Fatalf("expected authz=%v, saw %d calls", tc.expectAuthz, authzCalled)
   174  			}
   175  		})
   176  	}
   177  }
   178  
   179  type fakeStorage struct {
   180  	updated int
   181  	created int
   182  	rest.StandardStorage
   183  }
   184  
   185  func (f *fakeStorage) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
   186  	f.created++
   187  	return nil, nil
   188  }
   189  
   190  func (f *fakeStorage) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
   191  	obj, err := objInfo.UpdatedObject(ctx, &rbac.Role{})
   192  	if err != nil {
   193  		return obj, false, err
   194  	}
   195  	f.updated++
   196  	return nil, false, nil
   197  }
   198  

View as plain text