k8s.io/kubernetes@v1.29.3/pkg/registry/rbac/validation/rule_test.go (about) 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 }