...

Source file src/go.etcd.io/etcd/server/v3/etcdserver/api/v2auth/auth.go

Documentation: go.etcd.io/etcd/server/v3/etcdserver/api/v2auth

     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.
    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  	// 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  
    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  // passwordStore implements PasswordStore using bcrypt to hash user passwords
   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  // 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)
   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  	// 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  
   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  // 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
   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  // merge for a role works the same as User above -- atomic Role application to
   462  // each of the substructures.
   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  // Grant adds a set of permissions to the permission object on which it is called,
   493  // returning a new permission object.
   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  // Revoke removes a set of permissions to the permission object on which it is called,
   505  // returning a new permission object.
   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  // Grant adds a set of permissions to the permission object on which it is called,
   517  // returning a new permission object.
   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  // Revoke removes a set of permissions to the permission object on which it is called,
   542  // returning a new permission object.
   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  	// Attach root role to root user.
   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