1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package v2auth
16
17 import (
18 "context"
19 "reflect"
20 "testing"
21 "time"
22
23 "go.etcd.io/etcd/api/v3/etcdserverpb"
24 "go.etcd.io/etcd/server/v3/etcdserver"
25 "go.etcd.io/etcd/server/v3/etcdserver/api/v2error"
26 "go.etcd.io/etcd/server/v3/etcdserver/api/v2store"
27
28 "go.uber.org/zap"
29 )
30
31 type fakeDoer struct{}
32
33 func (fakeDoer) Do(context.Context, etcdserverpb.Request) (etcdserver.Response, error) {
34 return etcdserver.Response{}, nil
35 }
36
37 func TestCheckPassword(t *testing.T) {
38 st := NewStore(zap.NewExample(), fakeDoer{}, 5*time.Second)
39 u := User{Password: "$2a$10$I3iddh1D..EIOXXQtsra4u8AjOtgEa2ERxVvYGfXFBJDo1omXwP.q"}
40 matched := st.CheckPassword(u, "foo")
41 if matched {
42 t.Fatalf("expected false, got %v", matched)
43 }
44 }
45
46 const testTimeout = time.Millisecond
47
48 func TestMergeUser(t *testing.T) {
49 tbl := []struct {
50 input User
51 merge User
52 expect User
53 iserr bool
54 }{
55 {
56 User{User: "foo"},
57 User{User: "bar"},
58 User{},
59 true,
60 },
61 {
62 User{User: "foo"},
63 User{User: "foo"},
64 User{User: "foo", Roles: []string{}},
65 false,
66 },
67 {
68 User{User: "foo"},
69 User{User: "foo", Grant: []string{"role1"}},
70 User{User: "foo", Roles: []string{"role1"}},
71 false,
72 },
73 {
74 User{User: "foo", Roles: []string{"role1"}},
75 User{User: "foo", Grant: []string{"role1"}},
76 User{},
77 true,
78 },
79 {
80 User{User: "foo", Roles: []string{"role1"}},
81 User{User: "foo", Revoke: []string{"role2"}},
82 User{},
83 true,
84 },
85 {
86 User{User: "foo", Roles: []string{"role1"}},
87 User{User: "foo", Grant: []string{"role2"}},
88 User{User: "foo", Roles: []string{"role1", "role2"}},
89 false,
90 },
91 {
92 User{User: "foo", Password: "foo", Roles: []string{}},
93 User{User: "foo", Password: ""},
94 User{User: "foo", Password: "foo", Roles: []string{}},
95 false,
96 },
97 }
98
99 for i, tt := range tbl {
100 out, err := tt.input.merge(zap.NewExample(), tt.merge, passwordStore{})
101 if err != nil && !tt.iserr {
102 t.Fatalf("Got unexpected error on item %d", i)
103 }
104 if !tt.iserr {
105 if !reflect.DeepEqual(out, tt.expect) {
106 t.Errorf("Unequal merge expectation on item %d: got: %#v, expect: %#v", i, out, tt.expect)
107 }
108 }
109 }
110 }
111
112 func TestMergeRole(t *testing.T) {
113 tbl := []struct {
114 input Role
115 merge Role
116 expect Role
117 iserr bool
118 }{
119 {
120 Role{Role: "foo"},
121 Role{Role: "bar"},
122 Role{},
123 true,
124 },
125 {
126 Role{Role: "foo"},
127 Role{Role: "foo", Grant: &Permissions{KV: RWPermission{Read: []string{"/foodir"}, Write: []string{"/foodir"}}}},
128 Role{Role: "foo", Permissions: Permissions{KV: RWPermission{Read: []string{"/foodir"}, Write: []string{"/foodir"}}}},
129 false,
130 },
131 {
132 Role{Role: "foo", Permissions: Permissions{KV: RWPermission{Read: []string{"/foodir"}, Write: []string{"/foodir"}}}},
133 Role{Role: "foo", Revoke: &Permissions{KV: RWPermission{Read: []string{"/foodir"}, Write: []string{"/foodir"}}}},
134 Role{Role: "foo", Permissions: Permissions{KV: RWPermission{Read: []string{}, Write: []string{}}}},
135 false,
136 },
137 {
138 Role{Role: "foo", Permissions: Permissions{KV: RWPermission{Read: []string{"/bardir"}}}},
139 Role{Role: "foo", Revoke: &Permissions{KV: RWPermission{Read: []string{"/foodir"}}}},
140 Role{},
141 true,
142 },
143 }
144 for i, tt := range tbl {
145 out, err := tt.input.merge(zap.NewExample(), tt.merge)
146 if err != nil && !tt.iserr {
147 t.Fatalf("Got unexpected error on item %d", i)
148 }
149 if !tt.iserr {
150 if !reflect.DeepEqual(out, tt.expect) {
151 t.Errorf("Unequal merge expectation on item %d: got: %#v, expect: %#v", i, out, tt.expect)
152 }
153 }
154 }
155 }
156
157 type testDoer struct {
158 get []etcdserver.Response
159 put []etcdserver.Response
160 getindex int
161 putindex int
162 explicitlyEnabled bool
163 }
164
165 func (td *testDoer) Do(_ context.Context, req etcdserverpb.Request) (etcdserver.Response, error) {
166 if td.explicitlyEnabled && (req.Path == StorePermsPrefix+"/enabled") {
167 t := "true"
168 return etcdserver.Response{
169 Event: &v2store.Event{
170 Action: v2store.Get,
171 Node: &v2store.NodeExtern{
172 Key: StorePermsPrefix + "/users/cat",
173 Value: &t,
174 },
175 },
176 }, nil
177 }
178 if (req.Method == "GET" || req.Method == "QGET") && td.get != nil {
179 res := td.get[td.getindex]
180 if res.Event == nil {
181 td.getindex++
182 return etcdserver.Response{}, &v2error.Error{
183 ErrorCode: v2error.EcodeKeyNotFound,
184 }
185 }
186 td.getindex++
187 return res, nil
188 }
189 if req.Method == "PUT" && td.put != nil {
190 res := td.put[td.putindex]
191 if res.Event == nil {
192 td.putindex++
193 return etcdserver.Response{}, &v2error.Error{
194 ErrorCode: v2error.EcodeNodeExist,
195 }
196 }
197 td.putindex++
198 return res, nil
199 }
200 return etcdserver.Response{}, nil
201 }
202
203 func TestAllUsers(t *testing.T) {
204 d := &testDoer{
205 get: []etcdserver.Response{
206 {
207 Event: &v2store.Event{
208 Action: v2store.Get,
209 Node: &v2store.NodeExtern{
210 Nodes: v2store.NodeExterns([]*v2store.NodeExtern{
211 {
212 Key: StorePermsPrefix + "/users/cat",
213 },
214 {
215 Key: StorePermsPrefix + "/users/dog",
216 },
217 }),
218 },
219 },
220 },
221 },
222 }
223 expected := []string{"cat", "dog"}
224
225 s := store{lg: zap.NewExample(), server: d, timeout: testTimeout, ensuredOnce: false}
226 users, err := s.AllUsers()
227 if err != nil {
228 t.Error("Unexpected error", err)
229 }
230 if !reflect.DeepEqual(users, expected) {
231 t.Error("AllUsers doesn't match given store. Got", users, "expected", expected)
232 }
233 }
234
235 func TestGetAndDeleteUser(t *testing.T) {
236 data := `{"user": "cat", "roles" : ["animal"]}`
237 d := &testDoer{
238 get: []etcdserver.Response{
239 {
240 Event: &v2store.Event{
241 Action: v2store.Get,
242 Node: &v2store.NodeExtern{
243 Key: StorePermsPrefix + "/users/cat",
244 Value: &data,
245 },
246 },
247 },
248 },
249 explicitlyEnabled: true,
250 }
251 expected := User{User: "cat", Roles: []string{"animal"}}
252
253 s := store{lg: zap.NewExample(), server: d, timeout: testTimeout, ensuredOnce: false}
254 out, err := s.GetUser("cat")
255 if err != nil {
256 t.Error("Unexpected error", err)
257 }
258 if !reflect.DeepEqual(out, expected) {
259 t.Error("GetUser doesn't match given store. Got", out, "expected", expected)
260 }
261 err = s.DeleteUser("cat")
262 if err != nil {
263 t.Error("Unexpected error", err)
264 }
265 }
266
267 func TestAllRoles(t *testing.T) {
268 d := &testDoer{
269 get: []etcdserver.Response{
270 {
271 Event: &v2store.Event{
272 Action: v2store.Get,
273 Node: &v2store.NodeExtern{
274 Nodes: v2store.NodeExterns([]*v2store.NodeExtern{
275 {
276 Key: StorePermsPrefix + "/roles/animal",
277 },
278 {
279 Key: StorePermsPrefix + "/roles/human",
280 },
281 }),
282 },
283 },
284 },
285 },
286 explicitlyEnabled: true,
287 }
288 expected := []string{"animal", "human", "root"}
289
290 s := store{lg: zap.NewExample(), server: d, timeout: testTimeout, ensuredOnce: false}
291 out, err := s.AllRoles()
292 if err != nil {
293 t.Error("Unexpected error", err)
294 }
295 if !reflect.DeepEqual(out, expected) {
296 t.Error("AllRoles doesn't match given store. Got", out, "expected", expected)
297 }
298 }
299
300 func TestGetAndDeleteRole(t *testing.T) {
301 data := `{"role": "animal"}`
302 d := &testDoer{
303 get: []etcdserver.Response{
304 {
305 Event: &v2store.Event{
306 Action: v2store.Get,
307 Node: &v2store.NodeExtern{
308 Key: StorePermsPrefix + "/roles/animal",
309 Value: &data,
310 },
311 },
312 },
313 },
314 explicitlyEnabled: true,
315 }
316 expected := Role{Role: "animal"}
317
318 s := store{lg: zap.NewExample(), server: d, timeout: testTimeout, ensuredOnce: false}
319 out, err := s.GetRole("animal")
320 if err != nil {
321 t.Error("Unexpected error", err)
322 }
323 if !reflect.DeepEqual(out, expected) {
324 t.Error("GetRole doesn't match given store. Got", out, "expected", expected)
325 }
326 err = s.DeleteRole("animal")
327 if err != nil {
328 t.Error("Unexpected error", err)
329 }
330 }
331
332 func TestEnsure(t *testing.T) {
333 d := &testDoer{
334 get: []etcdserver.Response{
335 {
336 Event: &v2store.Event{
337 Action: v2store.Set,
338 Node: &v2store.NodeExtern{
339 Key: StorePermsPrefix,
340 Dir: true,
341 },
342 },
343 },
344 {
345 Event: &v2store.Event{
346 Action: v2store.Set,
347 Node: &v2store.NodeExtern{
348 Key: StorePermsPrefix + "/users/",
349 Dir: true,
350 },
351 },
352 },
353 {
354 Event: &v2store.Event{
355 Action: v2store.Set,
356 Node: &v2store.NodeExtern{
357 Key: StorePermsPrefix + "/roles/",
358 Dir: true,
359 },
360 },
361 },
362 },
363 }
364
365 s := store{lg: zap.NewExample(), server: d, timeout: testTimeout, ensuredOnce: false}
366 err := s.ensureAuthDirectories()
367 if err != nil {
368 t.Error("Unexpected error", err)
369 }
370 }
371
372 type fastPasswordStore struct {
373 }
374
375 func (fastPasswordStore) CheckPassword(user User, password string) bool {
376 return user.Password == password
377 }
378
379 func (fastPasswordStore) HashPassword(password string) (string, error) { return password, nil }
380
381 func TestCreateAndUpdateUser(t *testing.T) {
382 olduser := `{"user": "cat", "roles" : ["animal"]}`
383 newuser := `{"user": "cat", "roles" : ["animal", "pet"]}`
384 d := &testDoer{
385 get: []etcdserver.Response{
386 {
387 Event: nil,
388 },
389 {
390 Event: &v2store.Event{
391 Action: v2store.Get,
392 Node: &v2store.NodeExtern{
393 Key: StorePermsPrefix + "/users/cat",
394 Value: &olduser,
395 },
396 },
397 },
398 {
399 Event: &v2store.Event{
400 Action: v2store.Get,
401 Node: &v2store.NodeExtern{
402 Key: StorePermsPrefix + "/users/cat",
403 Value: &olduser,
404 },
405 },
406 },
407 },
408 put: []etcdserver.Response{
409 {
410 Event: &v2store.Event{
411 Action: v2store.Update,
412 Node: &v2store.NodeExtern{
413 Key: StorePermsPrefix + "/users/cat",
414 Value: &olduser,
415 },
416 },
417 },
418 {
419 Event: &v2store.Event{
420 Action: v2store.Update,
421 Node: &v2store.NodeExtern{
422 Key: StorePermsPrefix + "/users/cat",
423 Value: &newuser,
424 },
425 },
426 },
427 },
428 explicitlyEnabled: true,
429 }
430 user := User{User: "cat", Password: "meow", Roles: []string{"animal"}}
431 update := User{User: "cat", Grant: []string{"pet"}}
432 expected := User{User: "cat", Roles: []string{"animal", "pet"}}
433
434 s := store{lg: zap.NewExample(), server: d, timeout: testTimeout, ensuredOnce: true, PasswordStore: fastPasswordStore{}}
435 out, created, err := s.CreateOrUpdateUser(user)
436 if !created {
437 t.Error("Should have created user, instead updated?")
438 }
439 if err != nil {
440 t.Error("Unexpected error", err)
441 }
442 out.Password = "meow"
443 if !reflect.DeepEqual(out, user) {
444 t.Error("UpdateUser doesn't match given update. Got", out, "expected", expected)
445 }
446 out, created, err = s.CreateOrUpdateUser(update)
447 if created {
448 t.Error("Should have updated user, instead created?")
449 }
450 if err != nil {
451 t.Error("Unexpected error", err)
452 }
453 if !reflect.DeepEqual(out, expected) {
454 t.Error("UpdateUser doesn't match given update. Got", out, "expected", expected)
455 }
456 }
457
458 func TestUpdateRole(t *testing.T) {
459 oldrole := `{"role": "animal", "permissions" : {"kv": {"read": ["/animal"], "write": []}}}`
460 newrole := `{"role": "animal", "permissions" : {"kv": {"read": ["/animal"], "write": ["/animal"]}}}`
461 d := &testDoer{
462 get: []etcdserver.Response{
463 {
464 Event: &v2store.Event{
465 Action: v2store.Get,
466 Node: &v2store.NodeExtern{
467 Key: StorePermsPrefix + "/roles/animal",
468 Value: &oldrole,
469 },
470 },
471 },
472 },
473 put: []etcdserver.Response{
474 {
475 Event: &v2store.Event{
476 Action: v2store.Update,
477 Node: &v2store.NodeExtern{
478 Key: StorePermsPrefix + "/roles/animal",
479 Value: &newrole,
480 },
481 },
482 },
483 },
484 explicitlyEnabled: true,
485 }
486 update := Role{Role: "animal", Grant: &Permissions{KV: RWPermission{Read: []string{}, Write: []string{"/animal"}}}}
487 expected := Role{Role: "animal", Permissions: Permissions{KV: RWPermission{Read: []string{"/animal"}, Write: []string{"/animal"}}}}
488
489 s := store{lg: zap.NewExample(), server: d, timeout: testTimeout, ensuredOnce: true}
490 out, err := s.UpdateRole(update)
491 if err != nil {
492 t.Error("Unexpected error", err)
493 }
494 if !reflect.DeepEqual(out, expected) {
495 t.Error("UpdateRole doesn't match given update. Got", out, "expected", expected)
496 }
497 }
498
499 func TestCreateRole(t *testing.T) {
500 role := `{"role": "animal", "permissions" : {"kv": {"read": ["/animal"], "write": []}}}`
501 d := &testDoer{
502 put: []etcdserver.Response{
503 {
504 Event: &v2store.Event{
505 Action: v2store.Create,
506 Node: &v2store.NodeExtern{
507 Key: StorePermsPrefix + "/roles/animal",
508 Value: &role,
509 },
510 },
511 },
512 {
513 Event: nil,
514 },
515 },
516 explicitlyEnabled: true,
517 }
518 r := Role{Role: "animal", Permissions: Permissions{KV: RWPermission{Read: []string{"/animal"}, Write: []string{}}}}
519
520 s := store{lg: zap.NewExample(), server: d, timeout: testTimeout, ensuredOnce: true}
521 err := s.CreateRole(Role{Role: "root"})
522 if err == nil {
523 t.Error("Should error creating root role")
524 }
525 err = s.CreateRole(r)
526 if err != nil {
527 t.Error("Unexpected error", err)
528 }
529 err = s.CreateRole(r)
530 if err == nil {
531 t.Error("Creating duplicate role, should error")
532 }
533 }
534
535 func TestEnableAuth(t *testing.T) {
536 rootUser := `{"user": "root", "password": ""}`
537 guestRole := `{"role": "guest", "permissions" : {"kv": {"read": ["*"], "write": ["*"]}}}`
538 trueval := "true"
539 falseval := "false"
540 d := &testDoer{
541 get: []etcdserver.Response{
542 {
543 Event: &v2store.Event{
544 Action: v2store.Get,
545 Node: &v2store.NodeExtern{
546 Key: StorePermsPrefix + "/enabled",
547 Value: &falseval,
548 },
549 },
550 },
551 {
552 Event: &v2store.Event{
553 Action: v2store.Get,
554 Node: &v2store.NodeExtern{
555 Key: StorePermsPrefix + "/user/root",
556 Value: &rootUser,
557 },
558 },
559 },
560 {
561 Event: nil,
562 },
563 },
564 put: []etcdserver.Response{
565 {
566 Event: &v2store.Event{
567 Action: v2store.Create,
568 Node: &v2store.NodeExtern{
569 Key: StorePermsPrefix + "/roles/guest",
570 Value: &guestRole,
571 },
572 },
573 },
574 {
575 Event: &v2store.Event{
576 Action: v2store.Update,
577 Node: &v2store.NodeExtern{
578 Key: StorePermsPrefix + "/enabled",
579 Value: &trueval,
580 },
581 },
582 },
583 },
584 explicitlyEnabled: false,
585 }
586 s := store{lg: zap.NewExample(), server: d, timeout: testTimeout, ensuredOnce: true}
587 err := s.EnableAuth()
588 if err != nil {
589 t.Error("Unexpected error", err)
590 }
591 }
592
593 func TestDisableAuth(t *testing.T) {
594 trueval := "true"
595 falseval := "false"
596 d := &testDoer{
597 get: []etcdserver.Response{
598 {
599 Event: &v2store.Event{
600 Action: v2store.Get,
601 Node: &v2store.NodeExtern{
602 Key: StorePermsPrefix + "/enabled",
603 Value: &falseval,
604 },
605 },
606 },
607 {
608 Event: &v2store.Event{
609 Action: v2store.Get,
610 Node: &v2store.NodeExtern{
611 Key: StorePermsPrefix + "/enabled",
612 Value: &trueval,
613 },
614 },
615 },
616 },
617 put: []etcdserver.Response{
618 {
619 Event: &v2store.Event{
620 Action: v2store.Update,
621 Node: &v2store.NodeExtern{
622 Key: StorePermsPrefix + "/enabled",
623 Value: &falseval,
624 },
625 },
626 },
627 },
628 explicitlyEnabled: false,
629 }
630 s := store{lg: zap.NewExample(), server: d, timeout: testTimeout, ensuredOnce: true}
631 err := s.DisableAuth()
632 if err == nil {
633 t.Error("Expected error; already disabled")
634 }
635
636 err = s.DisableAuth()
637 if err != nil {
638 t.Error("Unexpected error", err)
639 }
640 }
641
642 func TestSimpleMatch(t *testing.T) {
643 role := Role{Role: "foo", Permissions: Permissions{KV: RWPermission{Read: []string{"/foodir/*", "/fookey"}, Write: []string{"/bardir/*", "/barkey"}}}}
644 if !role.HasKeyAccess("/foodir/foo/bar", false) {
645 t.Fatal("role lacks expected access")
646 }
647 if !role.HasKeyAccess("/fookey", false) {
648 t.Fatal("role lacks expected access")
649 }
650 if !role.HasRecursiveAccess("/foodir/*", false) {
651 t.Fatal("role lacks expected access")
652 }
653 if !role.HasRecursiveAccess("/foodir/foo*", false) {
654 t.Fatal("role lacks expected access")
655 }
656 if !role.HasRecursiveAccess("/bardir/*", true) {
657 t.Fatal("role lacks expected access")
658 }
659 if !role.HasKeyAccess("/bardir/bar/foo", true) {
660 t.Fatal("role lacks expected access")
661 }
662 if !role.HasKeyAccess("/barkey", true) {
663 t.Fatal("role lacks expected access")
664 }
665
666 if role.HasKeyAccess("/bardir/bar/foo", false) {
667 t.Fatal("role has unexpected access")
668 }
669 if role.HasKeyAccess("/barkey", false) {
670 t.Fatal("role has unexpected access")
671 }
672 if role.HasKeyAccess("/foodir/foo/bar", true) {
673 t.Fatal("role has unexpected access")
674 }
675 if role.HasKeyAccess("/fookey", true) {
676 t.Fatal("role has unexpected access")
677 }
678 }
679
View as plain text