client_auth_test.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. // Copyright 2015 CoreOS, Inc.
  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 v2http
  15. import (
  16. "errors"
  17. "net/http"
  18. "net/http/httptest"
  19. "path"
  20. "strings"
  21. "testing"
  22. "github.com/coreos/etcd/etcdserver/auth"
  23. )
  24. const goodPassword = "good"
  25. func mustJSONRequest(t *testing.T, method string, p string, body string) *http.Request {
  26. req, err := http.NewRequest(method, path.Join(authPrefix, p), strings.NewReader(body))
  27. if err != nil {
  28. t.Fatalf("Error making JSON request: %s %s %s\n", method, p, body)
  29. }
  30. req.Header.Set("Content-Type", "application/json")
  31. return req
  32. }
  33. type mockAuthStore struct {
  34. users map[string]*auth.User
  35. roles map[string]*auth.Role
  36. err error
  37. enabled bool
  38. }
  39. func (s *mockAuthStore) AllUsers() ([]string, error) { return []string{"alice", "bob", "root"}, s.err }
  40. func (s *mockAuthStore) GetUser(name string) (auth.User, error) {
  41. u, ok := s.users[name]
  42. if !ok {
  43. return auth.User{}, s.err
  44. }
  45. return *u, s.err
  46. }
  47. func (s *mockAuthStore) CreateOrUpdateUser(user auth.User) (out auth.User, created bool, err error) {
  48. if s.users == nil {
  49. u, err := s.CreateUser(user)
  50. return u, true, err
  51. }
  52. u, err := s.UpdateUser(user)
  53. return u, false, err
  54. }
  55. func (s *mockAuthStore) CreateUser(user auth.User) (auth.User, error) { return user, s.err }
  56. func (s *mockAuthStore) DeleteUser(name string) error { return s.err }
  57. func (s *mockAuthStore) UpdateUser(user auth.User) (auth.User, error) {
  58. return *s.users[user.User], s.err
  59. }
  60. func (s *mockAuthStore) AllRoles() ([]string, error) {
  61. return []string{"awesome", "guest", "root"}, s.err
  62. }
  63. func (s *mockAuthStore) GetRole(name string) (auth.Role, error) { return *s.roles[name], s.err }
  64. func (s *mockAuthStore) CreateRole(role auth.Role) error { return s.err }
  65. func (s *mockAuthStore) DeleteRole(name string) error { return s.err }
  66. func (s *mockAuthStore) UpdateRole(role auth.Role) (auth.Role, error) {
  67. return *s.roles[role.Role], s.err
  68. }
  69. func (s *mockAuthStore) AuthEnabled() bool { return s.enabled }
  70. func (s *mockAuthStore) EnableAuth() error { return s.err }
  71. func (s *mockAuthStore) DisableAuth() error { return s.err }
  72. func (s *mockAuthStore) CheckPassword(user auth.User, password string) bool {
  73. return user.Password == password
  74. }
  75. func (s *mockAuthStore) HashPassword(password string) (string, error) {
  76. return password, nil
  77. }
  78. func TestAuthFlow(t *testing.T) {
  79. enableMapMu.Lock()
  80. enabledMap = make(map[capability]bool)
  81. enabledMap[authCapability] = true
  82. enableMapMu.Unlock()
  83. var testCases = []struct {
  84. req *http.Request
  85. store mockAuthStore
  86. wcode int
  87. wbody string
  88. }{
  89. {
  90. req: mustJSONRequest(t, "PUT", "users/alice", `{{{{{{{`),
  91. store: mockAuthStore{},
  92. wcode: http.StatusBadRequest,
  93. wbody: `{"message":"Invalid JSON in request body."}`,
  94. },
  95. {
  96. req: mustJSONRequest(t, "PUT", "users/alice", `{"user": "alice", "password": "goodpassword"}`),
  97. store: mockAuthStore{enabled: true},
  98. wcode: http.StatusUnauthorized,
  99. wbody: `{"message":"Insufficient credentials"}`,
  100. },
  101. // Users
  102. {
  103. req: mustJSONRequest(t, "GET", "users", ""),
  104. store: mockAuthStore{
  105. users: map[string]*auth.User{
  106. "alice": {
  107. User: "alice",
  108. Roles: []string{"alicerole", "guest"},
  109. Password: "wheeee",
  110. },
  111. "bob": {
  112. User: "bob",
  113. Roles: []string{"guest"},
  114. Password: "wheeee",
  115. },
  116. "root": {
  117. User: "root",
  118. Roles: []string{"root"},
  119. Password: "wheeee",
  120. },
  121. },
  122. roles: map[string]*auth.Role{
  123. "alicerole": {
  124. Role: "alicerole",
  125. },
  126. "guest": {
  127. Role: "guest",
  128. },
  129. "root": {
  130. Role: "root",
  131. },
  132. },
  133. },
  134. wcode: http.StatusOK,
  135. wbody: `{"users":[` +
  136. `{"user":"alice","roles":[` +
  137. `{"role":"alicerole","permissions":{"kv":{"read":null,"write":null}}},` +
  138. `{"role":"guest","permissions":{"kv":{"read":null,"write":null}}}` +
  139. `]},` +
  140. `{"user":"bob","roles":[{"role":"guest","permissions":{"kv":{"read":null,"write":null}}}]},` +
  141. `{"user":"root","roles":[{"role":"root","permissions":{"kv":{"read":null,"write":null}}}]}]}`,
  142. },
  143. {
  144. req: mustJSONRequest(t, "GET", "users/alice", ""),
  145. store: mockAuthStore{
  146. users: map[string]*auth.User{
  147. "alice": {
  148. User: "alice",
  149. Roles: []string{"alicerole"},
  150. Password: "wheeee",
  151. },
  152. },
  153. roles: map[string]*auth.Role{
  154. "alicerole": {
  155. Role: "alicerole",
  156. },
  157. },
  158. },
  159. wcode: http.StatusOK,
  160. wbody: `{"user":"alice","roles":[{"role":"alicerole","permissions":{"kv":{"read":null,"write":null}}}]}`,
  161. },
  162. {
  163. req: mustJSONRequest(t, "PUT", "users/alice", `{"user": "alice", "password": "goodpassword"}`),
  164. store: mockAuthStore{},
  165. wcode: http.StatusCreated,
  166. wbody: `{"user":"alice","roles":null}`,
  167. },
  168. {
  169. req: mustJSONRequest(t, "DELETE", "users/alice", ``),
  170. store: mockAuthStore{},
  171. wcode: http.StatusOK,
  172. wbody: ``,
  173. },
  174. {
  175. req: mustJSONRequest(t, "PUT", "users/alice", `{"user": "alice", "password": "goodpassword"}`),
  176. store: mockAuthStore{
  177. users: map[string]*auth.User{
  178. "alice": {
  179. User: "alice",
  180. Roles: []string{"alicerole", "guest"},
  181. Password: "wheeee",
  182. },
  183. },
  184. },
  185. wcode: http.StatusOK,
  186. wbody: `{"user":"alice","roles":["alicerole","guest"]}`,
  187. },
  188. {
  189. req: mustJSONRequest(t, "PUT", "users/alice", `{"user": "alice", "grant": ["alicerole"]}`),
  190. store: mockAuthStore{
  191. users: map[string]*auth.User{
  192. "alice": {
  193. User: "alice",
  194. Roles: []string{"alicerole", "guest"},
  195. Password: "wheeee",
  196. },
  197. },
  198. },
  199. wcode: http.StatusOK,
  200. wbody: `{"user":"alice","roles":["alicerole","guest"]}`,
  201. },
  202. {
  203. req: mustJSONRequest(t, "GET", "users/alice", ``),
  204. store: mockAuthStore{
  205. users: map[string]*auth.User{},
  206. err: auth.Error{Status: http.StatusNotFound, Errmsg: "auth: User alice doesn't exist."},
  207. },
  208. wcode: http.StatusNotFound,
  209. wbody: `{"message":"auth: User alice doesn't exist."}`,
  210. },
  211. {
  212. req: mustJSONRequest(t, "GET", "roles/manager", ""),
  213. store: mockAuthStore{
  214. roles: map[string]*auth.Role{
  215. "manager": {
  216. Role: "manager",
  217. },
  218. },
  219. },
  220. wcode: http.StatusOK,
  221. wbody: `{"role":"manager","permissions":{"kv":{"read":null,"write":null}}}`,
  222. },
  223. {
  224. req: mustJSONRequest(t, "DELETE", "roles/manager", ``),
  225. store: mockAuthStore{},
  226. wcode: http.StatusOK,
  227. wbody: ``,
  228. },
  229. {
  230. req: mustJSONRequest(t, "PUT", "roles/manager", `{"role":"manager","permissions":{"kv":{"read":[],"write":[]}}}`),
  231. store: mockAuthStore{},
  232. wcode: http.StatusCreated,
  233. wbody: `{"role":"manager","permissions":{"kv":{"read":[],"write":[]}}}`,
  234. },
  235. {
  236. req: mustJSONRequest(t, "PUT", "roles/manager", `{"role":"manager","revoke":{"kv":{"read":["foo"],"write":[]}}}`),
  237. store: mockAuthStore{
  238. roles: map[string]*auth.Role{
  239. "manager": {
  240. Role: "manager",
  241. },
  242. },
  243. },
  244. wcode: http.StatusOK,
  245. wbody: `{"role":"manager","permissions":{"kv":{"read":null,"write":null}}}`,
  246. },
  247. {
  248. req: mustJSONRequest(t, "GET", "roles", ""),
  249. store: mockAuthStore{
  250. roles: map[string]*auth.Role{
  251. "awesome": {
  252. Role: "awesome",
  253. },
  254. "guest": {
  255. Role: "guest",
  256. },
  257. "root": {
  258. Role: "root",
  259. },
  260. },
  261. },
  262. wcode: http.StatusOK,
  263. wbody: `{"roles":[{"role":"awesome","permissions":{"kv":{"read":null,"write":null}}},` +
  264. `{"role":"guest","permissions":{"kv":{"read":null,"write":null}}},` +
  265. `{"role":"root","permissions":{"kv":{"read":null,"write":null}}}]}`,
  266. },
  267. {
  268. req: mustJSONRequest(t, "GET", "enable", ""),
  269. store: mockAuthStore{
  270. enabled: true,
  271. },
  272. wcode: http.StatusOK,
  273. wbody: `{"enabled":true}`,
  274. },
  275. {
  276. req: mustJSONRequest(t, "PUT", "enable", ""),
  277. store: mockAuthStore{
  278. enabled: false,
  279. },
  280. wcode: http.StatusOK,
  281. wbody: ``,
  282. },
  283. {
  284. req: (func() *http.Request {
  285. req := mustJSONRequest(t, "DELETE", "enable", "")
  286. req.SetBasicAuth("root", "good")
  287. return req
  288. })(),
  289. store: mockAuthStore{
  290. enabled: true,
  291. users: map[string]*auth.User{
  292. "root": {
  293. User: "root",
  294. Password: goodPassword,
  295. Roles: []string{"root"},
  296. },
  297. },
  298. roles: map[string]*auth.Role{
  299. "root": {
  300. Role: "root",
  301. },
  302. },
  303. },
  304. wcode: http.StatusOK,
  305. wbody: ``,
  306. },
  307. {
  308. req: (func() *http.Request {
  309. req := mustJSONRequest(t, "DELETE", "enable", "")
  310. req.SetBasicAuth("root", "bad")
  311. return req
  312. })(),
  313. store: mockAuthStore{
  314. enabled: true,
  315. users: map[string]*auth.User{
  316. "root": {
  317. User: "root",
  318. Password: goodPassword,
  319. Roles: []string{"root"},
  320. },
  321. },
  322. roles: map[string]*auth.Role{
  323. "root": {
  324. Role: "guest",
  325. },
  326. },
  327. },
  328. wcode: http.StatusUnauthorized,
  329. wbody: `{"message":"Insufficient credentials"}`,
  330. },
  331. }
  332. for i, tt := range testCases {
  333. mux := http.NewServeMux()
  334. h := &authHandler{
  335. sec: &tt.store,
  336. cluster: &fakeCluster{id: 1},
  337. }
  338. handleAuth(mux, h)
  339. rw := httptest.NewRecorder()
  340. mux.ServeHTTP(rw, tt.req)
  341. if rw.Code != tt.wcode {
  342. t.Errorf("#%d: got code=%d, want %d", i, rw.Code, tt.wcode)
  343. }
  344. g := rw.Body.String()
  345. g = strings.TrimSpace(g)
  346. if g != tt.wbody {
  347. t.Errorf("#%d: got body=%s, want %s", i, g, tt.wbody)
  348. }
  349. }
  350. }
  351. func mustAuthRequest(method, username, password string) *http.Request {
  352. req, err := http.NewRequest(method, "path", strings.NewReader(""))
  353. if err != nil {
  354. panic("Cannot make auth request: " + err.Error())
  355. }
  356. req.SetBasicAuth(username, password)
  357. return req
  358. }
  359. func TestPrefixAccess(t *testing.T) {
  360. var table = []struct {
  361. key string
  362. req *http.Request
  363. store *mockAuthStore
  364. hasRoot bool
  365. hasKeyPrefixAccess bool
  366. hasRecursiveAccess bool
  367. }{
  368. {
  369. key: "/foo",
  370. req: mustAuthRequest("GET", "root", "good"),
  371. store: &mockAuthStore{
  372. users: map[string]*auth.User{
  373. "root": {
  374. User: "root",
  375. Password: goodPassword,
  376. Roles: []string{"root"},
  377. },
  378. },
  379. roles: map[string]*auth.Role{
  380. "root": {
  381. Role: "root",
  382. },
  383. },
  384. enabled: true,
  385. },
  386. hasRoot: true,
  387. hasKeyPrefixAccess: true,
  388. hasRecursiveAccess: true,
  389. },
  390. {
  391. key: "/foo",
  392. req: mustAuthRequest("GET", "user", "good"),
  393. store: &mockAuthStore{
  394. users: map[string]*auth.User{
  395. "user": {
  396. User: "user",
  397. Password: goodPassword,
  398. Roles: []string{"foorole"},
  399. },
  400. },
  401. roles: map[string]*auth.Role{
  402. "foorole": {
  403. Role: "foorole",
  404. Permissions: auth.Permissions{
  405. KV: auth.RWPermission{
  406. Read: []string{"/foo"},
  407. Write: []string{"/foo"},
  408. },
  409. },
  410. },
  411. },
  412. enabled: true,
  413. },
  414. hasRoot: false,
  415. hasKeyPrefixAccess: true,
  416. hasRecursiveAccess: false,
  417. },
  418. {
  419. key: "/foo",
  420. req: mustAuthRequest("GET", "user", "good"),
  421. store: &mockAuthStore{
  422. users: map[string]*auth.User{
  423. "user": {
  424. User: "user",
  425. Password: goodPassword,
  426. Roles: []string{"foorole"},
  427. },
  428. },
  429. roles: map[string]*auth.Role{
  430. "foorole": {
  431. Role: "foorole",
  432. Permissions: auth.Permissions{
  433. KV: auth.RWPermission{
  434. Read: []string{"/foo*"},
  435. Write: []string{"/foo*"},
  436. },
  437. },
  438. },
  439. },
  440. enabled: true,
  441. },
  442. hasRoot: false,
  443. hasKeyPrefixAccess: true,
  444. hasRecursiveAccess: true,
  445. },
  446. {
  447. key: "/foo",
  448. req: mustAuthRequest("GET", "user", "bad"),
  449. store: &mockAuthStore{
  450. users: map[string]*auth.User{
  451. "user": {
  452. User: "user",
  453. Password: goodPassword,
  454. Roles: []string{"foorole"},
  455. },
  456. },
  457. roles: map[string]*auth.Role{
  458. "foorole": {
  459. Role: "foorole",
  460. Permissions: auth.Permissions{
  461. KV: auth.RWPermission{
  462. Read: []string{"/foo*"},
  463. Write: []string{"/foo*"},
  464. },
  465. },
  466. },
  467. },
  468. enabled: true,
  469. },
  470. hasRoot: false,
  471. hasKeyPrefixAccess: false,
  472. hasRecursiveAccess: false,
  473. },
  474. {
  475. key: "/foo",
  476. req: mustAuthRequest("GET", "user", "good"),
  477. store: &mockAuthStore{
  478. users: map[string]*auth.User{},
  479. err: errors.New("Not the user"),
  480. enabled: true,
  481. },
  482. hasRoot: false,
  483. hasKeyPrefixAccess: false,
  484. hasRecursiveAccess: false,
  485. },
  486. {
  487. key: "/foo",
  488. req: mustJSONRequest(t, "GET", "somepath", ""),
  489. store: &mockAuthStore{
  490. users: map[string]*auth.User{
  491. "user": {
  492. User: "user",
  493. Password: goodPassword,
  494. Roles: []string{"foorole"},
  495. },
  496. },
  497. roles: map[string]*auth.Role{
  498. "guest": {
  499. Role: "guest",
  500. Permissions: auth.Permissions{
  501. KV: auth.RWPermission{
  502. Read: []string{"/foo*"},
  503. Write: []string{"/foo*"},
  504. },
  505. },
  506. },
  507. },
  508. enabled: true,
  509. },
  510. hasRoot: false,
  511. hasKeyPrefixAccess: true,
  512. hasRecursiveAccess: true,
  513. },
  514. {
  515. key: "/bar",
  516. req: mustJSONRequest(t, "GET", "somepath", ""),
  517. store: &mockAuthStore{
  518. users: map[string]*auth.User{
  519. "user": {
  520. User: "user",
  521. Password: goodPassword,
  522. Roles: []string{"foorole"},
  523. },
  524. },
  525. roles: map[string]*auth.Role{
  526. "guest": {
  527. Role: "guest",
  528. Permissions: auth.Permissions{
  529. KV: auth.RWPermission{
  530. Read: []string{"/foo*"},
  531. Write: []string{"/foo*"},
  532. },
  533. },
  534. },
  535. },
  536. enabled: true,
  537. },
  538. hasRoot: false,
  539. hasKeyPrefixAccess: false,
  540. hasRecursiveAccess: false,
  541. },
  542. // check access for multiple roles
  543. {
  544. key: "/foo",
  545. req: mustAuthRequest("GET", "user", "good"),
  546. store: &mockAuthStore{
  547. users: map[string]*auth.User{
  548. "user": {
  549. User: "user",
  550. Password: goodPassword,
  551. Roles: []string{"role1", "role2"},
  552. },
  553. },
  554. roles: map[string]*auth.Role{
  555. "role1": {
  556. Role: "role1",
  557. },
  558. "role2": {
  559. Role: "role2",
  560. Permissions: auth.Permissions{
  561. KV: auth.RWPermission{
  562. Read: []string{"/foo"},
  563. Write: []string{"/foo"},
  564. },
  565. },
  566. },
  567. },
  568. enabled: true,
  569. },
  570. hasRoot: false,
  571. hasKeyPrefixAccess: true,
  572. hasRecursiveAccess: false,
  573. },
  574. {
  575. key: "/foo",
  576. req: (func() *http.Request {
  577. req := mustJSONRequest(t, "GET", "somepath", "")
  578. req.Header.Set("Authorization", "malformedencoding")
  579. return req
  580. })(),
  581. store: &mockAuthStore{
  582. enabled: true,
  583. users: map[string]*auth.User{
  584. "root": {
  585. User: "root",
  586. Password: goodPassword,
  587. Roles: []string{"root"},
  588. },
  589. },
  590. roles: map[string]*auth.Role{
  591. "guest": {
  592. Role: "guest",
  593. Permissions: auth.Permissions{
  594. KV: auth.RWPermission{
  595. Read: []string{"/foo*"},
  596. Write: []string{"/foo*"},
  597. },
  598. },
  599. },
  600. },
  601. },
  602. hasRoot: false,
  603. hasKeyPrefixAccess: false,
  604. hasRecursiveAccess: false,
  605. },
  606. }
  607. for i, tt := range table {
  608. if tt.hasRoot != hasRootAccess(tt.store, tt.req) {
  609. t.Errorf("#%d: hasRoot doesn't match (expected %v)", i, tt.hasRoot)
  610. }
  611. if tt.hasKeyPrefixAccess != hasKeyPrefixAccess(tt.store, tt.req, tt.key, false) {
  612. t.Errorf("#%d: hasKeyPrefixAccess doesn't match (expected %v)", i, tt.hasRoot)
  613. }
  614. if tt.hasRecursiveAccess != hasKeyPrefixAccess(tt.store, tt.req, tt.key, true) {
  615. t.Errorf("#%d: hasRecursiveAccess doesn't match (expected %v)", i, tt.hasRoot)
  616. }
  617. }
  618. }