1
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
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
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
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
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
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