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