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 15// Package v2auth implements etcd authentication. 16package v2auth 17 18import ( 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/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 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 rootRole = Role{ 50 Role: RootRoleName, 51 Permissions: Permissions{ 52 KV: RWPermission{ 53 Read: []string{"/*"}, 54 Write: []string{"/*"}, 55 }, 56 }, 57} 58 59var guestRole = Role{ 60 Role: GuestRoleName, 61 Permissions: Permissions{ 62 KV: RWPermission{ 63 Read: []string{"/*"}, 64 Write: []string{"/*"}, 65 }, 66 }, 67} 68 69type doer interface { 70 Do(context.Context, etcdserverpb.Request) (etcdserver.Response, error) 71} 72 73type 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 91type PasswordStore interface { 92 CheckPassword(user User, password string) bool 93 HashPassword(password string) (string, error) 94} 95 96type store struct { 97 lg *zap.Logger 98 server doer 99 timeout time.Duration 100 ensuredOnce bool 101 102 PasswordStore 103} 104 105type 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 113type 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 120type Permissions struct { 121 KV RWPermission `json:"kv"` 122} 123 124func (p *Permissions) IsEmpty() bool { 125 return p == nil || (len(p.KV.Read) == 0 && len(p.KV.Write) == 0) 126} 127 128type RWPermission struct { 129 Read []string `json:"read"` 130 Write []string `json:"write"` 131} 132 133type Error struct { 134 Status int 135 Errmsg string 136} 137 138func (ae Error) Error() string { return ae.Errmsg } 139func (ae Error) HTTPStatus() int { return ae.Status } 140 141func authErr(hs int, s string, v ...interface{}) Error { 142 return Error{Status: hs, Errmsg: fmt.Sprintf("auth: "+s, v...)} 143} 144 145func 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// passwordStore implements PasswordStore using bcrypt to hash user passwords 159type passwordStore struct{} 160 161func (passwordStore) CheckPassword(user User, password string) bool { 162 err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)) 163 return err == nil 164} 165 166func (passwordStore) HashPassword(password string) (string, error) { 167 hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) 168 return string(hash), err 169} 170 171func (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 190func (s *store) GetUser(name string) (User, error) { return s.getUser(name, false) } 191 192// CreateOrUpdateUser should be only used for creating the new user or when you are not 193// sure if it is a create or update. (When only password is passed in, we are not sure 194// if it is a update or create) 195func (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 205func (s *store) CreateUser(user User) (User, error) { 206 // Attach root role to root user. 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 217func (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 238func (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 255func (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 280func (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 299func (s *store) GetRole(name string) (Role, error) { return s.getRole(name, false) } 300 301func (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 319func (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 337func (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 364func (s *store) AuthEnabled() bool { 365 return s.detectAuth() 366} 367 368func (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 400func (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// merge applies the properties of the passed-in User to the User on which it 415// is called and returns a new User with these modifications applied. Think of 416// all Users as immutable sets of data. Merge allows you to perform the set 417// operations (desired grants and revokes) atomically 418func (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// merge for a role works the same as User above -- atomic Role application to 462// each of the substructures. 463func (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 478func (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 485func (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// Grant adds a set of permissions to the permission object on which it is called, 493// returning a new permission object. 494func (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// Revoke removes a set of permissions to the permission object on which it is called, 505// returning a new permission object. 506func (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// Grant adds a set of permissions to the permission object on which it is called, 517// returning a new permission object. 518func (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// Revoke removes a set of permissions to the permission object on which it is called, 542// returning a new permission object. 543func (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 574func (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 590func (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 604func 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 611func 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 618func 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 632func (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 // Attach root role to root user. 648 if u.User == "root" { 649 u = attachRootRole(u) 650 } 651 return u, nil 652} 653 654func (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