client_auth_test.go 16 KB

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