1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package v2auth
17
18 import (
19 "context"
20 "encoding/json"
21 "fmt"
22 "net/http"
23 "path"
24 "reflect"
25 "sort"
26 "strings"
27 "time"
28
29 "go.etcd.io/etcd/api/v3/etcdserverpb"
30 "go.etcd.io/etcd/client/pkg/v3/types"
31 "go.etcd.io/etcd/server/v3/etcdserver"
32 "go.etcd.io/etcd/server/v3/etcdserver/api/v2error"
33
34 "go.uber.org/zap"
35 "golang.org/x/crypto/bcrypt"
36 )
37
38 const (
39
40 StorePermsPrefix = "/2"
41
42
43 RootRoleName = "root"
44
45
46 GuestRoleName = "guest"
47 )
48
49 var rootRole = Role{
50 Role: RootRoleName,
51 Permissions: Permissions{
52 KV: RWPermission{
53 Read: []string{"/*"},
54 Write: []string{"/*"},
55 },
56 },
57 }
58
59 var guestRole = Role{
60 Role: GuestRoleName,
61 Permissions: Permissions{
62 KV: RWPermission{
63 Read: []string{"/*"},
64 Write: []string{"/*"},
65 },
66 },
67 }
68
69 type doer interface {
70 Do(context.Context, etcdserverpb.Request) (etcdserver.Response, error)
71 }
72
73 type Store interface {
74 AllUsers() ([]string, error)
75 GetUser(name string) (User, error)
76 CreateOrUpdateUser(user User) (out User, created bool, err error)
77 CreateUser(user User) (User, error)
78 DeleteUser(name string) error
79 UpdateUser(user User) (User, error)
80 AllRoles() ([]string, error)
81 GetRole(name string) (Role, error)
82 CreateRole(role Role) error
83 DeleteRole(name string) error
84 UpdateRole(role Role) (Role, error)
85 AuthEnabled() bool
86 EnableAuth() error
87 DisableAuth() error
88 PasswordStore
89 }
90
91 type PasswordStore interface {
92 CheckPassword(user User, password string) bool
93 HashPassword(password string) (string, error)
94 }
95
96 type store struct {
97 lg *zap.Logger
98 server doer
99 timeout time.Duration
100 ensuredOnce bool
101
102 PasswordStore
103 }
104
105 type User struct {
106 User string `json:"user"`
107 Password string `json:"password,omitempty"`
108 Roles []string `json:"roles"`
109 Grant []string `json:"grant,omitempty"`
110 Revoke []string `json:"revoke,omitempty"`
111 }
112
113 type Role struct {
114 Role string `json:"role"`
115 Permissions Permissions `json:"permissions"`
116 Grant *Permissions `json:"grant,omitempty"`
117 Revoke *Permissions `json:"revoke,omitempty"`
118 }
119
120 type Permissions struct {
121 KV RWPermission `json:"kv"`
122 }
123
124 func (p *Permissions) IsEmpty() bool {
125 return p == nil || (len(p.KV.Read) == 0 && len(p.KV.Write) == 0)
126 }
127
128 type RWPermission struct {
129 Read []string `json:"read"`
130 Write []string `json:"write"`
131 }
132
133 type Error struct {
134 Status int
135 Errmsg string
136 }
137
138 func (ae Error) Error() string { return ae.Errmsg }
139 func (ae Error) HTTPStatus() int { return ae.Status }
140
141 func authErr(hs int, s string, v ...interface{}) Error {
142 return Error{Status: hs, Errmsg: fmt.Sprintf("auth: "+s, v...)}
143 }
144
145 func NewStore(lg *zap.Logger, server doer, timeout time.Duration) Store {
146 if lg == nil {
147 lg = zap.NewNop()
148 }
149 s := &store{
150 lg: lg,
151 server: server,
152 timeout: timeout,
153 PasswordStore: passwordStore{},
154 }
155 return s
156 }
157
158
159 type passwordStore struct{}
160
161 func (passwordStore) CheckPassword(user User, password string) bool {
162 err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
163 return err == nil
164 }
165
166 func (passwordStore) HashPassword(password string) (string, error) {
167 hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
168 return string(hash), err
169 }
170
171 func (s *store) AllUsers() ([]string, error) {
172 resp, err := s.requestResource("/users/", false)
173 if err != nil {
174 if e, ok := err.(*v2error.Error); ok {
175 if e.ErrorCode == v2error.EcodeKeyNotFound {
176 return []string{}, nil
177 }
178 }
179 return nil, err
180 }
181 var nodes []string
182 for _, n := range resp.Event.Node.Nodes {
183 _, user := path.Split(n.Key)
184 nodes = append(nodes, user)
185 }
186 sort.Strings(nodes)
187 return nodes, nil
188 }
189
190 func (s *store) GetUser(name string) (User, error) { return s.getUser(name, false) }
191
192
193
194
195 func (s *store) CreateOrUpdateUser(user User) (out User, created bool, err error) {
196 _, err = s.getUser(user.User, true)
197 if err == nil {
198 out, err = s.UpdateUser(user)
199 return out, false, err
200 }
201 u, err := s.CreateUser(user)
202 return u, true, err
203 }
204
205 func (s *store) CreateUser(user User) (User, error) {
206
207 if user.User == "root" {
208 user = attachRootRole(user)
209 }
210 u, err := s.createUserInternal(user)
211 if err == nil {
212 s.lg.Info("created a user", zap.String("user-name", user.User))
213 }
214 return u, err
215 }
216
217 func (s *store) createUserInternal(user User) (User, error) {
218 if user.Password == "" {
219 return user, authErr(http.StatusBadRequest, "Cannot create user %s with an empty password", user.User)
220 }
221 hash, err := s.HashPassword(user.Password)
222 if err != nil {
223 return user, err
224 }
225 user.Password = hash
226
227 _, err = s.createResource("/users/"+user.User, user)
228 if err != nil {
229 if e, ok := err.(*v2error.Error); ok {
230 if e.ErrorCode == v2error.EcodeNodeExist {
231 return user, authErr(http.StatusConflict, "User %s already exists.", user.User)
232 }
233 }
234 }
235 return user, err
236 }
237
238 func (s *store) DeleteUser(name string) error {
239 if s.AuthEnabled() && name == "root" {
240 return authErr(http.StatusForbidden, "Cannot delete root user while auth is enabled.")
241 }
242 err := s.deleteResource("/users/" + name)
243 if err != nil {
244 if e, ok := err.(*v2error.Error); ok {
245 if e.ErrorCode == v2error.EcodeKeyNotFound {
246 return authErr(http.StatusNotFound, "User %s does not exist", name)
247 }
248 }
249 return err
250 }
251 s.lg.Info("deleted a user", zap.String("user-name", name))
252 return nil
253 }
254
255 func (s *store) UpdateUser(user User) (User, error) {
256 old, err := s.getUser(user.User, true)
257 if err != nil {
258 if e, ok := err.(*v2error.Error); ok {
259 if e.ErrorCode == v2error.EcodeKeyNotFound {
260 return user, authErr(http.StatusNotFound, "User %s doesn't exist.", user.User)
261 }
262 }
263 return old, err
264 }
265
266 newUser, err := old.merge(s.lg, user, s.PasswordStore)
267 if err != nil {
268 return old, err
269 }
270 if reflect.DeepEqual(old, newUser) {
271 return old, authErr(http.StatusBadRequest, "User not updated. Use grant/revoke/password to update the user.")
272 }
273 _, err = s.updateResource("/users/"+user.User, newUser)
274 if err == nil {
275 s.lg.Info("updated a user", zap.String("user-name", user.User))
276 }
277 return newUser, err
278 }
279
280 func (s *store) AllRoles() ([]string, error) {
281 nodes := []string{RootRoleName}
282 resp, err := s.requestResource("/roles/", false)
283 if err != nil {
284 if e, ok := err.(*v2error.Error); ok {
285 if e.ErrorCode == v2error.EcodeKeyNotFound {
286 return nodes, nil
287 }
288 }
289 return nil, err
290 }
291 for _, n := range resp.Event.Node.Nodes {
292 _, role := path.Split(n.Key)
293 nodes = append(nodes, role)
294 }
295 sort.Strings(nodes)
296 return nodes, nil
297 }
298
299 func (s *store) GetRole(name string) (Role, error) { return s.getRole(name, false) }
300
301 func (s *store) CreateRole(role Role) error {
302 if role.Role == RootRoleName {
303 return authErr(http.StatusForbidden, "Cannot modify role %s: is root role.", role.Role)
304 }
305 _, err := s.createResource("/roles/"+role.Role, role)
306 if err != nil {
307 if e, ok := err.(*v2error.Error); ok {
308 if e.ErrorCode == v2error.EcodeNodeExist {
309 return authErr(http.StatusConflict, "Role %s already exists.", role.Role)
310 }
311 }
312 }
313 if err == nil {
314 s.lg.Info("created a new role", zap.String("role-name", role.Role))
315 }
316 return err
317 }
318
319 func (s *store) DeleteRole(name string) error {
320 if name == RootRoleName {
321 return authErr(http.StatusForbidden, "Cannot modify role %s: is root role.", name)
322 }
323 err := s.deleteResource("/roles/" + name)
324 if err != nil {
325 if e, ok := err.(*v2error.Error); ok {
326 if e.ErrorCode == v2error.EcodeKeyNotFound {
327 return authErr(http.StatusNotFound, "Role %s doesn't exist.", name)
328 }
329 }
330 }
331 if err == nil {
332 s.lg.Info("delete a new role", zap.String("role-name", name))
333 }
334 return err
335 }
336
337 func (s *store) UpdateRole(role Role) (Role, error) {
338 if role.Role == RootRoleName {
339 return Role{}, authErr(http.StatusForbidden, "Cannot modify role %s: is root role.", role.Role)
340 }
341 old, err := s.getRole(role.Role, true)
342 if err != nil {
343 if e, ok := err.(*v2error.Error); ok {
344 if e.ErrorCode == v2error.EcodeKeyNotFound {
345 return role, authErr(http.StatusNotFound, "Role %s doesn't exist.", role.Role)
346 }
347 }
348 return old, err
349 }
350 newRole, err := old.merge(s.lg, role)
351 if err != nil {
352 return old, err
353 }
354 if reflect.DeepEqual(old, newRole) {
355 return old, authErr(http.StatusBadRequest, "Role not updated. Use grant/revoke to update the role.")
356 }
357 _, err = s.updateResource("/roles/"+role.Role, newRole)
358 if err == nil {
359 s.lg.Info("updated a new role", zap.String("role-name", role.Role))
360 }
361 return newRole, err
362 }
363
364 func (s *store) AuthEnabled() bool {
365 return s.detectAuth()
366 }
367
368 func (s *store) EnableAuth() error {
369 if s.AuthEnabled() {
370 return authErr(http.StatusConflict, "already enabled")
371 }
372
373 if _, err := s.getUser("root", true); err != nil {
374 return authErr(http.StatusConflict, "No root user available, please create one")
375 }
376 if _, err := s.getRole(GuestRoleName, true); err != nil {
377 s.lg.Info(
378 "no guest role access found; creating default",
379 zap.String("role-name", GuestRoleName),
380 )
381 if err := s.CreateRole(guestRole); err != nil {
382 s.lg.Warn(
383 "failed to create a guest role; aborting auth enable",
384 zap.String("role-name", GuestRoleName),
385 zap.Error(err),
386 )
387 return err
388 }
389 }
390
391 if err := s.enableAuth(); err != nil {
392 s.lg.Warn("failed to enable auth", zap.Error(err))
393 return err
394 }
395
396 s.lg.Info("enabled auth")
397 return nil
398 }
399
400 func (s *store) DisableAuth() error {
401 if !s.AuthEnabled() {
402 return authErr(http.StatusConflict, "already disabled")
403 }
404
405 err := s.disableAuth()
406 if err == nil {
407 s.lg.Info("disabled auth")
408 } else {
409 s.lg.Warn("failed to disable auth", zap.Error(err))
410 }
411 return err
412 }
413
414
415
416
417
418 func (ou User) merge(lg *zap.Logger, nu User, s PasswordStore) (User, error) {
419 var out User
420 if ou.User != nu.User {
421 return out, authErr(http.StatusConflict, "Merging user data with conflicting usernames: %s %s", ou.User, nu.User)
422 }
423 out.User = ou.User
424 if nu.Password != "" {
425 hash, err := s.HashPassword(nu.Password)
426 if err != nil {
427 return ou, err
428 }
429 out.Password = hash
430 } else {
431 out.Password = ou.Password
432 }
433 currentRoles := types.NewUnsafeSet(ou.Roles...)
434 for _, g := range nu.Grant {
435 if currentRoles.Contains(g) {
436 lg.Warn(
437 "attempted to grant a duplicate role for a user",
438 zap.String("user-name", nu.User),
439 zap.String("role-name", g),
440 )
441 return User{}, authErr(http.StatusConflict, fmt.Sprintf("Granting duplicate role %s for user %s", g, nu.User))
442 }
443 currentRoles.Add(g)
444 }
445 for _, r := range nu.Revoke {
446 if !currentRoles.Contains(r) {
447 lg.Warn(
448 "attempted to revoke a ungranted role for a user",
449 zap.String("user-name", nu.User),
450 zap.String("role-name", r),
451 )
452 return User{}, authErr(http.StatusConflict, fmt.Sprintf("Revoking ungranted role %s for user %s", r, nu.User))
453 }
454 currentRoles.Remove(r)
455 }
456 out.Roles = currentRoles.Values()
457 sort.Strings(out.Roles)
458 return out, nil
459 }
460
461
462
463 func (r Role) merge(lg *zap.Logger, n Role) (Role, error) {
464 var out Role
465 var err error
466 if r.Role != n.Role {
467 return out, authErr(http.StatusConflict, "Merging role with conflicting names: %s %s", r.Role, n.Role)
468 }
469 out.Role = r.Role
470 out.Permissions, err = r.Permissions.Grant(n.Grant)
471 if err != nil {
472 return out, err
473 }
474 out.Permissions, err = out.Permissions.Revoke(lg, n.Revoke)
475 return out, err
476 }
477
478 func (r Role) HasKeyAccess(key string, write bool) bool {
479 if r.Role == RootRoleName {
480 return true
481 }
482 return r.Permissions.KV.HasAccess(key, write)
483 }
484
485 func (r Role) HasRecursiveAccess(key string, write bool) bool {
486 if r.Role == RootRoleName {
487 return true
488 }
489 return r.Permissions.KV.HasRecursiveAccess(key, write)
490 }
491
492
493
494 func (p Permissions) Grant(n *Permissions) (Permissions, error) {
495 var out Permissions
496 var err error
497 if n == nil {
498 return p, nil
499 }
500 out.KV, err = p.KV.Grant(n.KV)
501 return out, err
502 }
503
504
505
506 func (p Permissions) Revoke(lg *zap.Logger, n *Permissions) (Permissions, error) {
507 var out Permissions
508 var err error
509 if n == nil {
510 return p, nil
511 }
512 out.KV, err = p.KV.Revoke(lg, n.KV)
513 return out, err
514 }
515
516
517
518 func (rw RWPermission) Grant(n RWPermission) (RWPermission, error) {
519 var out RWPermission
520 currentRead := types.NewUnsafeSet(rw.Read...)
521 for _, r := range n.Read {
522 if currentRead.Contains(r) {
523 return out, authErr(http.StatusConflict, "Granting duplicate read permission %s", r)
524 }
525 currentRead.Add(r)
526 }
527 currentWrite := types.NewUnsafeSet(rw.Write...)
528 for _, w := range n.Write {
529 if currentWrite.Contains(w) {
530 return out, authErr(http.StatusConflict, "Granting duplicate write permission %s", w)
531 }
532 currentWrite.Add(w)
533 }
534 out.Read = currentRead.Values()
535 out.Write = currentWrite.Values()
536 sort.Strings(out.Read)
537 sort.Strings(out.Write)
538 return out, nil
539 }
540
541
542
543 func (rw RWPermission) Revoke(lg *zap.Logger, n RWPermission) (RWPermission, error) {
544 var out RWPermission
545 currentRead := types.NewUnsafeSet(rw.Read...)
546 for _, r := range n.Read {
547 if !currentRead.Contains(r) {
548 lg.Info(
549 "revoking ungranted read permission",
550 zap.String("read-permission", r),
551 )
552 continue
553 }
554 currentRead.Remove(r)
555 }
556 currentWrite := types.NewUnsafeSet(rw.Write...)
557 for _, w := range n.Write {
558 if !currentWrite.Contains(w) {
559 lg.Info(
560 "revoking ungranted write permission",
561 zap.String("write-permission", w),
562 )
563 continue
564 }
565 currentWrite.Remove(w)
566 }
567 out.Read = currentRead.Values()
568 out.Write = currentWrite.Values()
569 sort.Strings(out.Read)
570 sort.Strings(out.Write)
571 return out, nil
572 }
573
574 func (rw RWPermission) HasAccess(key string, write bool) bool {
575 var list []string
576 if write {
577 list = rw.Write
578 } else {
579 list = rw.Read
580 }
581 for _, pat := range list {
582 match, err := simpleMatch(pat, key)
583 if err == nil && match {
584 return true
585 }
586 }
587 return false
588 }
589
590 func (rw RWPermission) HasRecursiveAccess(key string, write bool) bool {
591 list := rw.Read
592 if write {
593 list = rw.Write
594 }
595 for _, pat := range list {
596 match, err := prefixMatch(pat, key)
597 if err == nil && match {
598 return true
599 }
600 }
601 return false
602 }
603
604 func simpleMatch(pattern string, key string) (match bool, err error) {
605 if pattern[len(pattern)-1] == '*' {
606 return strings.HasPrefix(key, pattern[:len(pattern)-1]), nil
607 }
608 return key == pattern, nil
609 }
610
611 func prefixMatch(pattern string, key string) (match bool, err error) {
612 if pattern[len(pattern)-1] != '*' {
613 return false, nil
614 }
615 return strings.HasPrefix(key, pattern[:len(pattern)-1]), nil
616 }
617
618 func attachRootRole(u User) User {
619 inRoles := false
620 for _, r := range u.Roles {
621 if r == RootRoleName {
622 inRoles = true
623 break
624 }
625 }
626 if !inRoles {
627 u.Roles = append(u.Roles, RootRoleName)
628 }
629 return u
630 }
631
632 func (s *store) getUser(name string, quorum bool) (User, error) {
633 resp, err := s.requestResource("/users/"+name, quorum)
634 if err != nil {
635 if e, ok := err.(*v2error.Error); ok {
636 if e.ErrorCode == v2error.EcodeKeyNotFound {
637 return User{}, authErr(http.StatusNotFound, "User %s does not exist.", name)
638 }
639 }
640 return User{}, err
641 }
642 var u User
643 err = json.Unmarshal([]byte(*resp.Event.Node.Value), &u)
644 if err != nil {
645 return u, err
646 }
647
648 if u.User == "root" {
649 u = attachRootRole(u)
650 }
651 return u, nil
652 }
653
654 func (s *store) getRole(name string, quorum bool) (Role, error) {
655 if name == RootRoleName {
656 return rootRole, nil
657 }
658 resp, err := s.requestResource("/roles/"+name, quorum)
659 if err != nil {
660 if e, ok := err.(*v2error.Error); ok {
661 if e.ErrorCode == v2error.EcodeKeyNotFound {
662 return Role{}, authErr(http.StatusNotFound, "Role %s does not exist.", name)
663 }
664 }
665 return Role{}, err
666 }
667 var r Role
668 err = json.Unmarshal([]byte(*resp.Event.Node.Value), &r)
669 return r, err
670 }
671
View as plain text