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