...

Source file src/go.etcd.io/etcd/server/v3/auth/range_perm_cache.go

Documentation: go.etcd.io/etcd/server/v3/auth

     1  // Copyright 2016 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
    16  
    17  import (
    18  	"go.etcd.io/etcd/api/v3/authpb"
    19  	"go.etcd.io/etcd/pkg/v3/adt"
    20  	"go.etcd.io/etcd/server/v3/mvcc/backend"
    21  
    22  	"go.uber.org/zap"
    23  )
    24  
    25  func getMergedPerms(lg *zap.Logger, tx backend.ReadTx, userName string) *unifiedRangePermissions {
    26  	user := getUser(lg, tx, userName)
    27  	if user == nil {
    28  		return nil
    29  	}
    30  
    31  	readPerms := adt.NewIntervalTree()
    32  	writePerms := adt.NewIntervalTree()
    33  
    34  	for _, roleName := range user.Roles {
    35  		role := getRole(lg, tx, roleName)
    36  		if role == nil {
    37  			continue
    38  		}
    39  
    40  		for _, perm := range role.KeyPermission {
    41  			var ivl adt.Interval
    42  			var rangeEnd []byte
    43  
    44  			if len(perm.RangeEnd) != 1 || perm.RangeEnd[0] != 0 {
    45  				rangeEnd = perm.RangeEnd
    46  			}
    47  
    48  			if len(perm.RangeEnd) != 0 {
    49  				ivl = adt.NewBytesAffineInterval(perm.Key, rangeEnd)
    50  			} else {
    51  				ivl = adt.NewBytesAffinePoint(perm.Key)
    52  			}
    53  
    54  			switch perm.PermType {
    55  			case authpb.READWRITE:
    56  				readPerms.Insert(ivl, struct{}{})
    57  				writePerms.Insert(ivl, struct{}{})
    58  
    59  			case authpb.READ:
    60  				readPerms.Insert(ivl, struct{}{})
    61  
    62  			case authpb.WRITE:
    63  				writePerms.Insert(ivl, struct{}{})
    64  			}
    65  		}
    66  	}
    67  
    68  	return &unifiedRangePermissions{
    69  		readPerms:  readPerms,
    70  		writePerms: writePerms,
    71  	}
    72  }
    73  
    74  func checkKeyInterval(
    75  	lg *zap.Logger,
    76  	cachedPerms *unifiedRangePermissions,
    77  	key, rangeEnd []byte,
    78  	permtyp authpb.Permission_Type) bool {
    79  	if isOpenEnded(rangeEnd) {
    80  		rangeEnd = nil
    81  		// nil rangeEnd will be converetd to []byte{}, the largest element of BytesAffineComparable,
    82  		// in NewBytesAffineInterval().
    83  	}
    84  
    85  	ivl := adt.NewBytesAffineInterval(key, rangeEnd)
    86  	switch permtyp {
    87  	case authpb.READ:
    88  		return cachedPerms.readPerms.Contains(ivl)
    89  	case authpb.WRITE:
    90  		return cachedPerms.writePerms.Contains(ivl)
    91  	default:
    92  		lg.Panic("unknown auth type", zap.String("auth-type", permtyp.String()))
    93  	}
    94  	return false
    95  }
    96  
    97  func checkKeyPoint(lg *zap.Logger, cachedPerms *unifiedRangePermissions, key []byte, permtyp authpb.Permission_Type) bool {
    98  	pt := adt.NewBytesAffinePoint(key)
    99  	switch permtyp {
   100  	case authpb.READ:
   101  		return cachedPerms.readPerms.Intersects(pt)
   102  	case authpb.WRITE:
   103  		return cachedPerms.writePerms.Intersects(pt)
   104  	default:
   105  		lg.Panic("unknown auth type", zap.String("auth-type", permtyp.String()))
   106  	}
   107  	return false
   108  }
   109  
   110  func (as *authStore) isRangeOpPermitted(userName string, key, rangeEnd []byte, permtyp authpb.Permission_Type) bool {
   111  	as.rangePermCacheMu.RLock()
   112  	defer as.rangePermCacheMu.RUnlock()
   113  
   114  	rangePerm, ok := as.rangePermCache[userName]
   115  	if !ok {
   116  		as.lg.Error(
   117  			"user doesn't exist",
   118  			zap.String("user-name", userName),
   119  		)
   120  		return false
   121  	}
   122  
   123  	if len(rangeEnd) == 0 {
   124  		return checkKeyPoint(as.lg, rangePerm, key, permtyp)
   125  	}
   126  
   127  	return checkKeyInterval(as.lg, rangePerm, key, rangeEnd, permtyp)
   128  }
   129  
   130  func (as *authStore) refreshRangePermCache(tx backend.ReadTx) {
   131  	// Note that every authentication configuration update calls this method and it invalidates the entire
   132  	// rangePermCache and reconstruct it based on information of users and roles stored in the backend.
   133  	// This can be a costly operation.
   134  	as.rangePermCacheMu.Lock()
   135  	defer as.rangePermCacheMu.Unlock()
   136  
   137  	as.rangePermCache = make(map[string]*unifiedRangePermissions)
   138  
   139  	users := getAllUsers(as.lg, tx)
   140  	for _, user := range users {
   141  		userName := string(user.Name)
   142  		perms := getMergedPerms(as.lg, tx, userName)
   143  		if perms == nil {
   144  			as.lg.Error(
   145  				"failed to create a merged permission",
   146  				zap.String("user-name", userName),
   147  			)
   148  			continue
   149  		}
   150  		as.rangePermCache[userName] = perms
   151  	}
   152  }
   153  
   154  type unifiedRangePermissions struct {
   155  	readPerms  adt.IntervalTree
   156  	writePerms adt.IntervalTree
   157  }
   158  
   159  // Constraints related to key range
   160  // Assumptions:
   161  // a1. key must be non-nil
   162  // a2. []byte{} (in the case of string, "") is not a valid key of etcd
   163  // For representing an open-ended range, BytesAffineComparable uses []byte{} as the largest element.
   164  // a3. []byte{0x00} is the minimum valid etcd key
   165  //
   166  // Based on the above assumptions, key and rangeEnd must follow below rules:
   167  // b1. for representing a single key point, rangeEnd should be nil or zero length byte array (in the case of string, "")
   168  // Rule a2 guarantees that (X, []byte{}) for any X is not a valid range. So such ranges can be used for representing
   169  // a single key permission.
   170  //
   171  // b2. key range with upper limit, like (X, Y), larger or equal to X and smaller than Y
   172  //
   173  // b3. key range with open-ended, like (X, <open ended>), is represented like (X, []byte{0x00})
   174  // Because of rule a3, if we have (X, []byte{0x00}), such a range represents an empty range and makes no sense to have
   175  // such a permission. So we use []byte{0x00} for representing an open-ended permission.
   176  // Note that rangeEnd with []byte{0x00} will be converted into []byte{} before inserted into the interval tree
   177  // (rule a2 ensures that this is the largest element).
   178  // Special range like key = []byte{0x00} and rangeEnd = []byte{0x00} is treated as a range which matches with all keys.
   179  //
   180  // Treating a range whose rangeEnd with []byte{0x00} as an open-ended comes from the rules of Range() and Watch() API.
   181  
   182  func isOpenEnded(rangeEnd []byte) bool { // check rule b3
   183  	return len(rangeEnd) == 1 && rangeEnd[0] == 0
   184  }
   185  
   186  func isValidPermissionRange(key, rangeEnd []byte) bool {
   187  	if len(key) == 0 {
   188  		return false
   189  	}
   190  	if rangeEnd == nil || len(rangeEnd) == 0 { // ensure rule b1
   191  		return true
   192  	}
   193  
   194  	begin := adt.BytesAffineComparable(key)
   195  	end := adt.BytesAffineComparable(rangeEnd)
   196  	if begin.Compare(end) == -1 { // rule b2
   197  		return true
   198  	}
   199  
   200  	if isOpenEnded(rangeEnd) {
   201  		return true
   202  	}
   203  
   204  	return false
   205  }
   206  

View as plain text