1
16
17 package auth
18
19 import (
20 "context"
21 "errors"
22 "net/http"
23 "strings"
24 "sync"
25 "testing"
26
27 authorizationapi "k8s.io/api/authorization/v1"
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 "k8s.io/apiserver/pkg/authentication/authenticator"
30 "k8s.io/apiserver/pkg/authentication/user"
31 "k8s.io/apiserver/pkg/authorization/authorizer"
32 api "k8s.io/kubernetes/pkg/apis/core"
33 "k8s.io/kubernetes/pkg/controlplane"
34 "k8s.io/kubernetes/test/integration/framework"
35 "k8s.io/kubernetes/test/utils/ktesting"
36 )
37
38
39
40 type sarAuthorizer struct{}
41
42 func (sarAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
43 if a.GetUser().GetName() == "dave" {
44 return authorizer.DecisionNoOpinion, "no", errors.New("I'm sorry, Dave")
45 }
46
47 return authorizer.DecisionAllow, "you're not dave", nil
48 }
49
50 func alwaysAlice(req *http.Request) (*authenticator.Response, bool, error) {
51 return &authenticator.Response{
52 User: &user.DefaultInfo{
53 Name: "alice",
54 UID: "alice",
55 Groups: []string{user.AllAuthenticated},
56 },
57 }, true, nil
58 }
59
60 func TestSubjectAccessReview(t *testing.T) {
61 tCtx := ktesting.Init(t)
62 clientset, _, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
63 ModifyServerConfig: func(config *controlplane.Config) {
64
65 config.GenericConfig.LoopbackClientConfig.BearerToken = ""
66 config.GenericConfig.Authentication.Authenticator = authenticator.RequestFunc(alwaysAlice)
67 config.GenericConfig.Authorization.Authorizer = sarAuthorizer{}
68 },
69 })
70 defer tearDownFn()
71
72 tests := []struct {
73 name string
74 sar *authorizationapi.SubjectAccessReview
75 expectedError string
76 expectedStatus authorizationapi.SubjectAccessReviewStatus
77 }{
78 {
79 name: "simple allow",
80 sar: &authorizationapi.SubjectAccessReview{
81 Spec: authorizationapi.SubjectAccessReviewSpec{
82 ResourceAttributes: &authorizationapi.ResourceAttributes{
83 Verb: "list",
84 Group: api.GroupName,
85 Version: "v1",
86 Resource: "pods",
87 },
88 User: "alice",
89 },
90 },
91 expectedStatus: authorizationapi.SubjectAccessReviewStatus{
92 Allowed: true,
93 Reason: "you're not dave",
94 },
95 },
96 {
97 name: "simple deny",
98 sar: &authorizationapi.SubjectAccessReview{
99 Spec: authorizationapi.SubjectAccessReviewSpec{
100 ResourceAttributes: &authorizationapi.ResourceAttributes{
101 Verb: "list",
102 Group: api.GroupName,
103 Version: "v1",
104 Resource: "pods",
105 },
106 User: "dave",
107 },
108 },
109 expectedStatus: authorizationapi.SubjectAccessReviewStatus{
110 Allowed: false,
111 Reason: "no",
112 EvaluationError: "I'm sorry, Dave",
113 },
114 },
115 {
116 name: "simple error",
117 sar: &authorizationapi.SubjectAccessReview{
118 Spec: authorizationapi.SubjectAccessReviewSpec{
119 ResourceAttributes: &authorizationapi.ResourceAttributes{
120 Verb: "list",
121 Group: api.GroupName,
122 Version: "v1",
123 Resource: "pods",
124 },
125 },
126 },
127 expectedError: "at least one of user or group must be specified",
128 },
129 }
130
131 for _, test := range tests {
132 response, err := clientset.AuthorizationV1().SubjectAccessReviews().Create(tCtx, test.sar, metav1.CreateOptions{})
133 switch {
134 case err == nil && len(test.expectedError) == 0:
135
136 case err != nil && strings.Contains(err.Error(), test.expectedError):
137 continue
138
139 case err != nil && len(test.expectedError) != 0:
140 t.Errorf("%s: unexpected error: %v", test.name, err)
141 continue
142 default:
143 t.Errorf("%s: expected %v, got %v", test.name, test.expectedError, err)
144 continue
145 }
146 if response.Status != test.expectedStatus {
147 t.Errorf("%s: expected %v, got %v", test.name, test.expectedStatus, response.Status)
148 continue
149 }
150 }
151 }
152
153 func TestSelfSubjectAccessReview(t *testing.T) {
154 tCtx := ktesting.Init(t)
155
156 var mutex sync.Mutex
157
158 username := "alice"
159 authenticatorFunc := func(req *http.Request) (*authenticator.Response, bool, error) {
160 mutex.Lock()
161 defer mutex.Unlock()
162
163 return &authenticator.Response{
164 User: &user.DefaultInfo{
165 Name: username,
166 UID: username,
167 Groups: []string{user.AllAuthenticated},
168 },
169 }, true, nil
170 }
171
172 clientset, _, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
173 ModifyServerConfig: func(config *controlplane.Config) {
174
175 config.GenericConfig.LoopbackClientConfig.BearerToken = ""
176 config.GenericConfig.Authentication.Authenticator = authenticator.RequestFunc(authenticatorFunc)
177 config.GenericConfig.Authorization.Authorizer = sarAuthorizer{}
178 },
179 })
180 defer tearDownFn()
181
182 tests := []struct {
183 name string
184 username string
185 sar *authorizationapi.SelfSubjectAccessReview
186 expectedError string
187 expectedStatus authorizationapi.SubjectAccessReviewStatus
188 }{
189 {
190 name: "simple allow",
191 username: "alice",
192 sar: &authorizationapi.SelfSubjectAccessReview{
193 Spec: authorizationapi.SelfSubjectAccessReviewSpec{
194 ResourceAttributes: &authorizationapi.ResourceAttributes{
195 Verb: "list",
196 Group: api.GroupName,
197 Version: "v1",
198 Resource: "pods",
199 },
200 },
201 },
202 expectedStatus: authorizationapi.SubjectAccessReviewStatus{
203 Allowed: true,
204 Reason: "you're not dave",
205 },
206 },
207 {
208 name: "simple deny",
209 username: "dave",
210 sar: &authorizationapi.SelfSubjectAccessReview{
211 Spec: authorizationapi.SelfSubjectAccessReviewSpec{
212 ResourceAttributes: &authorizationapi.ResourceAttributes{
213 Verb: "list",
214 Group: api.GroupName,
215 Version: "v1",
216 Resource: "pods",
217 },
218 },
219 },
220 expectedStatus: authorizationapi.SubjectAccessReviewStatus{
221 Allowed: false,
222 Reason: "no",
223 EvaluationError: "I'm sorry, Dave",
224 },
225 },
226 }
227
228 for _, test := range tests {
229 mutex.Lock()
230 username = test.username
231 mutex.Unlock()
232
233 response, err := clientset.AuthorizationV1().SelfSubjectAccessReviews().Create(tCtx, test.sar, metav1.CreateOptions{})
234 switch {
235 case err == nil && len(test.expectedError) == 0:
236
237 case err != nil && strings.Contains(err.Error(), test.expectedError):
238 continue
239
240 case err != nil && len(test.expectedError) != 0:
241 t.Errorf("%s: unexpected error: %v", test.name, err)
242 continue
243 default:
244 t.Errorf("%s: expected %v, got %v", test.name, test.expectedError, err)
245 continue
246 }
247 if response.Status != test.expectedStatus {
248 t.Errorf("%s: expected %v, got %v", test.name, test.expectedStatus, response.Status)
249 continue
250 }
251 }
252 }
253
254 func TestLocalSubjectAccessReview(t *testing.T) {
255 tCtx := ktesting.Init(t)
256 clientset, _, tearDownFn := framework.StartTestServer(tCtx, t, framework.TestServerSetup{
257 ModifyServerConfig: func(config *controlplane.Config) {
258
259 config.GenericConfig.LoopbackClientConfig.BearerToken = ""
260 config.GenericConfig.Authentication.Authenticator = authenticator.RequestFunc(alwaysAlice)
261 config.GenericConfig.Authorization.Authorizer = sarAuthorizer{}
262 },
263 })
264 defer tearDownFn()
265
266 tests := []struct {
267 name string
268 namespace string
269 sar *authorizationapi.LocalSubjectAccessReview
270 expectedError string
271 expectedStatus authorizationapi.SubjectAccessReviewStatus
272 }{
273 {
274 name: "simple allow",
275 namespace: "foo",
276 sar: &authorizationapi.LocalSubjectAccessReview{
277 ObjectMeta: metav1.ObjectMeta{Namespace: "foo"},
278 Spec: authorizationapi.SubjectAccessReviewSpec{
279 ResourceAttributes: &authorizationapi.ResourceAttributes{
280 Verb: "list",
281 Group: api.GroupName,
282 Version: "v1",
283 Resource: "pods",
284 Namespace: "foo",
285 },
286 User: "alice",
287 },
288 },
289 expectedStatus: authorizationapi.SubjectAccessReviewStatus{
290 Allowed: true,
291 Reason: "you're not dave",
292 },
293 },
294 {
295 name: "simple deny",
296 namespace: "foo",
297 sar: &authorizationapi.LocalSubjectAccessReview{
298 ObjectMeta: metav1.ObjectMeta{Namespace: "foo"},
299 Spec: authorizationapi.SubjectAccessReviewSpec{
300 ResourceAttributes: &authorizationapi.ResourceAttributes{
301 Verb: "list",
302 Group: api.GroupName,
303 Version: "v1",
304 Resource: "pods",
305 Namespace: "foo",
306 },
307 User: "dave",
308 },
309 },
310 expectedStatus: authorizationapi.SubjectAccessReviewStatus{
311 Allowed: false,
312 Reason: "no",
313 EvaluationError: "I'm sorry, Dave",
314 },
315 },
316 {
317 name: "conflicting namespace",
318 namespace: "foo",
319 sar: &authorizationapi.LocalSubjectAccessReview{
320 ObjectMeta: metav1.ObjectMeta{Namespace: "foo"},
321 Spec: authorizationapi.SubjectAccessReviewSpec{
322 ResourceAttributes: &authorizationapi.ResourceAttributes{
323 Verb: "list",
324 Group: api.GroupName,
325 Version: "v1",
326 Resource: "pods",
327 Namespace: "bar",
328 },
329 User: "dave",
330 },
331 },
332 expectedError: "must match metadata.namespace",
333 },
334 {
335 name: "missing namespace",
336 namespace: "foo",
337 sar: &authorizationapi.LocalSubjectAccessReview{
338 ObjectMeta: metav1.ObjectMeta{Namespace: "foo"},
339 Spec: authorizationapi.SubjectAccessReviewSpec{
340 ResourceAttributes: &authorizationapi.ResourceAttributes{
341 Verb: "list",
342 Group: api.GroupName,
343 Version: "v1",
344 Resource: "pods",
345 },
346 User: "dave",
347 },
348 },
349 expectedError: "must match metadata.namespace",
350 },
351 }
352
353 for _, test := range tests {
354 response, err := clientset.AuthorizationV1().LocalSubjectAccessReviews(test.namespace).Create(tCtx, test.sar, metav1.CreateOptions{})
355 switch {
356 case err == nil && len(test.expectedError) == 0:
357
358 case err != nil && strings.Contains(err.Error(), test.expectedError):
359 continue
360
361 case err != nil && len(test.expectedError) != 0:
362 t.Errorf("%s: unexpected error: %v", test.name, err)
363 continue
364 default:
365 t.Errorf("%s: expected %v, got %v", test.name, test.expectedError, err)
366 continue
367 }
368 if response.Status != test.expectedStatus {
369 t.Errorf("%s: expected %#v, got %#v", test.name, test.expectedStatus, response.Status)
370 continue
371 }
372 }
373 }
374
View as plain text