...

Source file src/k8s.io/kubernetes/pkg/registry/rbac/validation/rule_test.go

Documentation: k8s.io/kubernetes/pkg/registry/rbac/validation

     1  /*
     2  Copyright 2016 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 validation
    18  
    19  import (
    20  	"hash/fnv"
    21  	"io"
    22  	"reflect"
    23  	"sort"
    24  	"testing"
    25  
    26  	"github.com/google/go-cmp/cmp"
    27  	rbacv1 "k8s.io/api/rbac/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apiserver/pkg/authentication/user"
    30  )
    31  
    32  // compute a hash of a policy rule so we can sort in a deterministic order
    33  func hashOf(p rbacv1.PolicyRule) string {
    34  	hash := fnv.New32()
    35  	writeStrings := func(slis ...[]string) {
    36  		for _, sli := range slis {
    37  			for _, s := range sli {
    38  				io.WriteString(hash, s)
    39  			}
    40  		}
    41  	}
    42  	writeStrings(p.Verbs, p.APIGroups, p.Resources, p.ResourceNames, p.NonResourceURLs)
    43  	return string(hash.Sum(nil))
    44  }
    45  
    46  // byHash sorts a set of policy rules by a hash of its fields
    47  type byHash []rbacv1.PolicyRule
    48  
    49  func (b byHash) Len() int           { return len(b) }
    50  func (b byHash) Less(i, j int) bool { return hashOf(b[i]) < hashOf(b[j]) }
    51  func (b byHash) Swap(i, j int)      { b[i], b[j] = b[j], b[i] }
    52  
    53  func TestDefaultRuleResolver(t *testing.T) {
    54  	ruleReadPods := rbacv1.PolicyRule{
    55  		Verbs:     []string{"GET", "WATCH"},
    56  		APIGroups: []string{"v1"},
    57  		Resources: []string{"pods"},
    58  	}
    59  	ruleReadServices := rbacv1.PolicyRule{
    60  		Verbs:     []string{"GET", "WATCH"},
    61  		APIGroups: []string{"v1"},
    62  		Resources: []string{"services"},
    63  	}
    64  	ruleWriteNodes := rbacv1.PolicyRule{
    65  		Verbs:     []string{"PUT", "CREATE", "UPDATE"},
    66  		APIGroups: []string{"v1"},
    67  		Resources: []string{"nodes"},
    68  	}
    69  	ruleAdmin := rbacv1.PolicyRule{
    70  		Verbs:     []string{"*"},
    71  		APIGroups: []string{"*"},
    72  		Resources: []string{"*"},
    73  	}
    74  
    75  	staticRoles1 := StaticRoles{
    76  		roles: []*rbacv1.Role{
    77  			{
    78  				ObjectMeta: metav1.ObjectMeta{Namespace: "namespace1", Name: "readthings"},
    79  				Rules:      []rbacv1.PolicyRule{ruleReadPods, ruleReadServices},
    80  			},
    81  		},
    82  		clusterRoles: []*rbacv1.ClusterRole{
    83  			{
    84  				ObjectMeta: metav1.ObjectMeta{Name: "cluster-admin"},
    85  				Rules:      []rbacv1.PolicyRule{ruleAdmin},
    86  			},
    87  			{
    88  				ObjectMeta: metav1.ObjectMeta{Name: "write-nodes"},
    89  				Rules:      []rbacv1.PolicyRule{ruleWriteNodes},
    90  			},
    91  		},
    92  		roleBindings: []*rbacv1.RoleBinding{
    93  			{
    94  				ObjectMeta: metav1.ObjectMeta{Namespace: "namespace1"},
    95  				Subjects: []rbacv1.Subject{
    96  					{Kind: rbacv1.UserKind, Name: "foobar"},
    97  					{Kind: rbacv1.GroupKind, Name: "group1"},
    98  				},
    99  				RoleRef: rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "Role", Name: "readthings"},
   100  			},
   101  		},
   102  		clusterRoleBindings: []*rbacv1.ClusterRoleBinding{
   103  			{
   104  				Subjects: []rbacv1.Subject{
   105  					{Kind: rbacv1.UserKind, Name: "admin"},
   106  					{Kind: rbacv1.GroupKind, Name: "admin"},
   107  				},
   108  				RoleRef: rbacv1.RoleRef{APIGroup: rbacv1.GroupName, Kind: "ClusterRole", Name: "cluster-admin"},
   109  			},
   110  		},
   111  	}
   112  
   113  	tests := []struct {
   114  		StaticRoles
   115  
   116  		// For a given context, what are the rules that apply?
   117  		user           user.Info
   118  		namespace      string
   119  		effectiveRules []rbacv1.PolicyRule
   120  	}{
   121  		{
   122  			StaticRoles:    staticRoles1,
   123  			user:           &user.DefaultInfo{Name: "foobar"},
   124  			namespace:      "namespace1",
   125  			effectiveRules: []rbacv1.PolicyRule{ruleReadPods, ruleReadServices},
   126  		},
   127  		{
   128  			StaticRoles:    staticRoles1,
   129  			user:           &user.DefaultInfo{Name: "foobar"},
   130  			namespace:      "namespace2",
   131  			effectiveRules: nil,
   132  		},
   133  		{
   134  			StaticRoles: staticRoles1,
   135  			// Same as above but without a namespace. Only cluster rules should apply.
   136  			user:           &user.DefaultInfo{Name: "foobar", Groups: []string{"admin"}},
   137  			effectiveRules: []rbacv1.PolicyRule{ruleAdmin},
   138  		},
   139  		{
   140  			StaticRoles:    staticRoles1,
   141  			user:           &user.DefaultInfo{},
   142  			effectiveRules: nil,
   143  		},
   144  	}
   145  
   146  	for i, tc := range tests {
   147  		ruleResolver := newMockRuleResolver(&tc.StaticRoles)
   148  		rules, err := ruleResolver.RulesFor(tc.user, tc.namespace)
   149  		if err != nil {
   150  			t.Errorf("case %d: GetEffectivePolicyRules(context)=%v", i, err)
   151  			continue
   152  		}
   153  
   154  		// Sort for deep equals
   155  		sort.Sort(byHash(rules))
   156  		sort.Sort(byHash(tc.effectiveRules))
   157  
   158  		if !reflect.DeepEqual(rules, tc.effectiveRules) {
   159  			ruleDiff := cmp.Diff(rules, tc.effectiveRules)
   160  			t.Errorf("case %d: %s", i, ruleDiff)
   161  		}
   162  	}
   163  }
   164  
   165  func TestAppliesTo(t *testing.T) {
   166  	tests := []struct {
   167  		subjects  []rbacv1.Subject
   168  		user      user.Info
   169  		namespace string
   170  		appliesTo bool
   171  		index     int
   172  		testCase  string
   173  	}{
   174  		{
   175  			subjects: []rbacv1.Subject{
   176  				{Kind: rbacv1.UserKind, Name: "foobar"},
   177  			},
   178  			user:      &user.DefaultInfo{Name: "foobar"},
   179  			appliesTo: true,
   180  			index:     0,
   181  			testCase:  "single subject that matches username",
   182  		},
   183  		{
   184  			subjects: []rbacv1.Subject{
   185  				{Kind: rbacv1.UserKind, Name: "barfoo"},
   186  				{Kind: rbacv1.UserKind, Name: "foobar"},
   187  			},
   188  			user:      &user.DefaultInfo{Name: "foobar"},
   189  			appliesTo: true,
   190  			index:     1,
   191  			testCase:  "multiple subjects, one that matches username",
   192  		},
   193  		{
   194  			subjects: []rbacv1.Subject{
   195  				{Kind: rbacv1.UserKind, Name: "barfoo"},
   196  				{Kind: rbacv1.UserKind, Name: "foobar"},
   197  			},
   198  			user:      &user.DefaultInfo{Name: "zimzam"},
   199  			appliesTo: false,
   200  			testCase:  "multiple subjects, none that match username",
   201  		},
   202  		{
   203  			subjects: []rbacv1.Subject{
   204  				{Kind: rbacv1.UserKind, Name: "barfoo"},
   205  				{Kind: rbacv1.GroupKind, Name: "foobar"},
   206  			},
   207  			user:      &user.DefaultInfo{Name: "zimzam", Groups: []string{"foobar"}},
   208  			appliesTo: true,
   209  			index:     1,
   210  			testCase:  "multiple subjects, one that match group",
   211  		},
   212  		{
   213  			subjects: []rbacv1.Subject{
   214  				{Kind: rbacv1.UserKind, Name: "barfoo"},
   215  				{Kind: rbacv1.GroupKind, Name: "foobar"},
   216  			},
   217  			user:      &user.DefaultInfo{Name: "zimzam", Groups: []string{"foobar"}},
   218  			namespace: "namespace1",
   219  			appliesTo: true,
   220  			index:     1,
   221  			testCase:  "multiple subjects, one that match group, should ignore namespace",
   222  		},
   223  		{
   224  			subjects: []rbacv1.Subject{
   225  				{Kind: rbacv1.UserKind, Name: "barfoo"},
   226  				{Kind: rbacv1.GroupKind, Name: "foobar"},
   227  				{Kind: rbacv1.ServiceAccountKind, Namespace: "kube-system", Name: "default"},
   228  			},
   229  			user:      &user.DefaultInfo{Name: "system:serviceaccount:kube-system:default"},
   230  			namespace: "default",
   231  			appliesTo: true,
   232  			index:     2,
   233  			testCase:  "multiple subjects with a service account that matches",
   234  		},
   235  		{
   236  			subjects: []rbacv1.Subject{
   237  				{Kind: rbacv1.UserKind, Name: "*"},
   238  			},
   239  			user:      &user.DefaultInfo{Name: "foobar"},
   240  			namespace: "default",
   241  			appliesTo: false,
   242  			testCase:  "* user subject name doesn't match all users",
   243  		},
   244  		{
   245  			subjects: []rbacv1.Subject{
   246  				{Kind: rbacv1.GroupKind, Name: user.AllAuthenticated},
   247  				{Kind: rbacv1.GroupKind, Name: user.AllUnauthenticated},
   248  			},
   249  			user:      &user.DefaultInfo{Name: "foobar", Groups: []string{user.AllAuthenticated}},
   250  			namespace: "default",
   251  			appliesTo: true,
   252  			index:     0,
   253  			testCase:  "binding to all authenticated and unauthenticated subjects matches authenticated user",
   254  		},
   255  		{
   256  			subjects: []rbacv1.Subject{
   257  				{Kind: rbacv1.GroupKind, Name: user.AllAuthenticated},
   258  				{Kind: rbacv1.GroupKind, Name: user.AllUnauthenticated},
   259  			},
   260  			user:      &user.DefaultInfo{Name: "system:anonymous", Groups: []string{user.AllUnauthenticated}},
   261  			namespace: "default",
   262  			appliesTo: true,
   263  			index:     1,
   264  			testCase:  "binding to all authenticated and unauthenticated subjects matches anonymous user",
   265  		},
   266  	}
   267  
   268  	for _, tc := range tests {
   269  		gotIndex, got := appliesTo(tc.user, tc.subjects, tc.namespace)
   270  		if got != tc.appliesTo {
   271  			t.Errorf("case %q want appliesTo=%t, got appliesTo=%t", tc.testCase, tc.appliesTo, got)
   272  		}
   273  		if gotIndex != tc.index {
   274  			t.Errorf("case %q want index %d, got %d", tc.testCase, tc.index, gotIndex)
   275  		}
   276  	}
   277  }
   278  

View as plain text