go.etcd.io/etcd@v3.3.27+incompatible/etcdserver/auth/auth.go (about)

     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 auth implements etcd authentication.
    16  package auth
    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  	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  
    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 (
    50  	plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "etcdserver/auth")
    51  )
    52  
    53  var rootRole = Role{
    54  	Role: RootRoleName,
    55  	Permissions: Permissions{
    56  		KV: RWPermission{
    57  			Read:  []string{"/*"},
    58  			Write: []string{"/*"},
    59  		},
    60  	},
    61  }
    62  
    63  var guestRole = Role{
    64  	Role: GuestRoleName,
    65  	Permissions: Permissions{
    66  		KV: RWPermission{
    67  			Read:  []string{"/*"},
    68  			Write: []string{"/*"},
    69  		},
    70  	},
    71  }
    72  
    73  type doer interface {
    74  	Do(context.Context, etcdserverpb.Request) (etcdserver.Response, error)
    75  }
    76  
    77  type 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  
    95  type PasswordStore interface {
    96  	CheckPassword(user User, password string) bool
    97  	HashPassword(password string) (string, error)
    98  }
    99  
   100  type store struct {
   101  	server      doer
   102  	timeout     time.Duration
   103  	ensuredOnce bool
   104  
   105  	PasswordStore
   106  }
   107  
   108  type 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  
   116  type 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  
   123  type Permissions struct {
   124  	KV RWPermission `json:"kv"`
   125  }
   126  
   127  func (p *Permissions) IsEmpty() bool {
   128  	return p == nil || (len(p.KV.Read) == 0 && len(p.KV.Write) == 0)
   129  }
   130  
   131  type RWPermission struct {
   132  	Read  []string `json:"read"`
   133  	Write []string `json:"write"`
   134  }
   135  
   136  type Error struct {
   137  	Status int
   138  	Errmsg string
   139  }
   140  
   141  func (ae Error) Error() string   { return ae.Errmsg }
   142  func (ae Error) HTTPStatus() int { return ae.Status }
   143  
   144  func authErr(hs int, s string, v ...interface{}) Error {
   145  	return Error{Status: hs, Errmsg: fmt.Sprintf("auth: "+s, v...)}
   146  }
   147  
   148  func 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
   158  type passwordStore struct{}
   159  
   160  func (_ passwordStore) CheckPassword(user User, password string) bool {
   161  	err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
   162  	return err == nil
   163  }
   164  
   165  func (_ passwordStore) HashPassword(password string) (string, error) {
   166  	hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
   167  	return string(hash), err
   168  }
   169  
   170  func (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  
   189  func (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)
   194  func (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  
   204  func (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  
   216  func (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  
   237  func (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  
   254  func (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  
   279  func (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  
   298  func (s *store) GetRole(name string) (Role, error) { return s.getRole(name, false) }
   299  
   300  func (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  
   318  func (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  
   336  func (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  
   363  func (s *store) AuthEnabled() bool {
   364  	return s.detectAuth()
   365  }
   366  
   367  func (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  
   392  func (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
   410  func (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.
   447  func (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  
   462  func (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  
   469  func (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.
   478  func (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.
   490  func (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.
   502  func (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.
   527  func (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  
   552  func (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  
   568  func (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  
   582  func 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  
   589  func 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  
   596  func 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  
   610  func (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  
   632  func (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  }