auth_test.go 16 KB

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