github.com/polarismesh/polaris@v1.17.8/auth/defaultauth/auth_checker.go (about)

     1  /**
     2   * Tencent is pleased to support the open source community by making Polaris available.
     3   *
     4   * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
     5   *
     6   * Licensed under the BSD 3-Clause License (the "License");
     7   * you may not use this file except in compliance with the License.
     8   * You may obtain a copy of the License at
     9   *
    10   * https://opensource.org/licenses/BSD-3-Clause
    11   *
    12   * Unless required by applicable law or agreed to in writing, software distributed
    13   * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    14   * CONDITIONS OF ANY KIND, either express or implied. See the License for the
    15   * specific language governing permissions and limitations under the License.
    16   */
    17  
    18  package defaultauth
    19  
    20  import (
    21  	"context"
    22  	"encoding/json"
    23  	"strings"
    24  
    25  	"github.com/pkg/errors"
    26  	apisecurity "github.com/polarismesh/specification/source/go/api/v1/security"
    27  	"go.uber.org/zap"
    28  
    29  	"github.com/polarismesh/polaris/auth"
    30  	"github.com/polarismesh/polaris/cache"
    31  	api "github.com/polarismesh/polaris/common/api/v1"
    32  	"github.com/polarismesh/polaris/common/model"
    33  	"github.com/polarismesh/polaris/common/utils"
    34  	"github.com/polarismesh/polaris/store"
    35  )
    36  
    37  var (
    38  	// ErrorNotAllowedAccess 鉴权失败
    39  	ErrorNotAllowedAccess error = errors.New(api.Code2Info(api.NotAllowedAccess))
    40  	// ErrorInvalidParameter 不合法的参数
    41  	ErrorInvalidParameter error = errors.New(api.Code2Info(api.InvalidParameter))
    42  	// ErrorNotPermission .
    43  	ErrorNotPermission = errors.New("no permission")
    44  )
    45  
    46  // DefaultAuthChecker 北极星自带的默认鉴权中心
    47  type DefaultAuthChecker struct {
    48  	cacheMgn *cache.CacheManager
    49  }
    50  
    51  func (d *DefaultAuthChecker) SetCacheMgr(mgr *cache.CacheManager) {
    52  	d.cacheMgn = mgr
    53  }
    54  
    55  // Initialize 执行初始化动作
    56  func (d *DefaultAuthChecker) Initialize(options *auth.Config, s store.Store, cacheMgn *cache.CacheManager) error {
    57  	// 新版本鉴权策略配置均从auth.Option中迁移至auth.user.option及auth.strategy.option中
    58  	var (
    59  		strategyContentBytes []byte
    60  		userContentBytes     []byte
    61  		authContentBytes     []byte
    62  		err                  error
    63  	)
    64  
    65  	cfg := DefaultAuthConfig()
    66  
    67  	// 一旦设置了auth.user.option或auth.strategy.option,将不会继续读取auth.option
    68  	if len(options.Strategy.Option) > 0 || len(options.User.Option) > 0 {
    69  		// 判断auth.option是否还有值,有则不兼容
    70  		if len(options.Option) > 0 {
    71  			log.Warn("auth.user.option or auth.strategy.option has set, auth.option will ignore")
    72  		}
    73  		strategyContentBytes, err = json.Marshal(options.Strategy.Option)
    74  		if err != nil {
    75  			return err
    76  		}
    77  		if err := json.Unmarshal(strategyContentBytes, cfg); err != nil {
    78  			return err
    79  		}
    80  		userContentBytes, err = json.Marshal(options.User.Option)
    81  		if err != nil {
    82  			return err
    83  		}
    84  		if err := json.Unmarshal(userContentBytes, cfg); err != nil {
    85  			return err
    86  		}
    87  	} else {
    88  		log.Warn("[Auth][Checker] auth.option has deprecated, use auth.user.option and auth.strategy.option instead.")
    89  		authContentBytes, err = json.Marshal(options.Option)
    90  		if err != nil {
    91  			return err
    92  		}
    93  		if err := json.Unmarshal(authContentBytes, cfg); err != nil {
    94  			return err
    95  		}
    96  	}
    97  
    98  	if err := cfg.Verify(); err != nil {
    99  		return err
   100  	}
   101  	// 兼容原本老的配置逻辑
   102  	if cfg.Strict {
   103  		cfg.ConsoleOpen = cfg.Strict
   104  	}
   105  	AuthOption = cfg
   106  	d.cacheMgn = cacheMgn
   107  	return nil
   108  }
   109  
   110  // Cache 获取缓存统一管理
   111  func (d *DefaultAuthChecker) Cache() *cache.CacheManager {
   112  	return d.cacheMgn
   113  }
   114  
   115  // IsOpenConsoleAuth 针对控制台是否开启了操作鉴权
   116  func (d *DefaultAuthChecker) IsOpenConsoleAuth() bool {
   117  	return AuthOption.ConsoleOpen
   118  }
   119  
   120  // IsOpenClientAuth 针对客户端是否开启了操作鉴权
   121  func (d *DefaultAuthChecker) IsOpenClientAuth() bool {
   122  	return AuthOption.ClientOpen
   123  }
   124  
   125  // IsOpenAuth 返回对于控制台/客户端任意其中的一个是否开启了操作鉴权
   126  func (d *DefaultAuthChecker) IsOpenAuth() bool {
   127  	return d.IsOpenConsoleAuth() || d.IsOpenClientAuth()
   128  }
   129  
   130  // CheckClientPermission 执行检查客户端动作判断是否有权限,并且对 RequestContext 注入操作者数据
   131  func (d *DefaultAuthChecker) CheckClientPermission(preCtx *model.AcquireContext) (bool, error) {
   132  	if !d.IsOpenClientAuth() {
   133  		return true, nil
   134  	}
   135  	preCtx.SetFromClient()
   136  	return d.CheckPermission(preCtx)
   137  }
   138  
   139  // CheckConsolePermission 执行检查控制台动作判断是否有权限,并且对 RequestContext 注入操作者数据
   140  func (d *DefaultAuthChecker) CheckConsolePermission(preCtx *model.AcquireContext) (bool, error) {
   141  	if !d.IsOpenConsoleAuth() {
   142  		return true, nil
   143  	}
   144  	preCtx.SetFromConsole()
   145  	if preCtx.GetModule() == model.MaintainModule {
   146  		return d.checkMaintainPermission(preCtx)
   147  	}
   148  	return d.CheckPermission(preCtx)
   149  }
   150  
   151  // CheckMaintainPermission 执行检查运维动作判断是否有权限
   152  func (d *DefaultAuthChecker) checkMaintainPermission(preCtx *model.AcquireContext) (bool, error) {
   153  	if err := d.VerifyCredential(preCtx); err != nil {
   154  		return false, err
   155  	}
   156  	if preCtx.GetOperation() == model.Read {
   157  		return true, nil
   158  	}
   159  
   160  	tokenInfo := preCtx.GetAttachment(model.TokenDetailInfoKey).(OperatorInfo)
   161  
   162  	if tokenInfo.Disable {
   163  		return false, model.ErrorTokenDisabled
   164  	}
   165  	if !tokenInfo.IsUserToken {
   166  		return false, errors.New("only user role can access maintain API")
   167  	}
   168  	if tokenInfo.Role != model.OwnerUserRole {
   169  		return false, errors.New("only owner account can access maintain API")
   170  	}
   171  	return true, nil
   172  }
   173  
   174  // CheckPermission 执行检查动作判断是否有权限
   175  //
   176  //	step 1. 判断是否开启了鉴权
   177  //	step 2. 对token进行检查判断
   178  //		case 1. 如果 token 被禁用
   179  //				a. 读操作,直接放通
   180  //				b. 写操作,快速失败
   181  //	step 3. 拉取token对应的操作者相关信息,注入到请求上下文中
   182  //	step 4. 进行权限检查
   183  func (d *DefaultAuthChecker) CheckPermission(authCtx *model.AcquireContext) (bool, error) {
   184  	if err := d.VerifyCredential(authCtx); err != nil {
   185  		return false, err
   186  	}
   187  
   188  	if authCtx.GetOperation() == model.Read {
   189  		return true, nil
   190  	}
   191  
   192  	operatorInfo := authCtx.GetAttachment(model.TokenDetailInfoKey).(OperatorInfo)
   193  	// 这里需要检查当 token 被禁止的情况,如果 token 被禁止,无论是否可以操作目标资源,都无法进行写操作
   194  	if operatorInfo.Disable {
   195  		return false, model.ErrorTokenDisabled
   196  	}
   197  
   198  	log.Debug("[Auth][Checker] check permission args", utils.RequestID(authCtx.GetRequestContext()),
   199  		zap.String("method", authCtx.GetMethod()), zap.Any("resources", authCtx.GetAccessResources()))
   200  
   201  	ok, err := d.doCheckPermission(authCtx)
   202  	if ok {
   203  		return ok, nil
   204  	}
   205  
   206  	// 强制同步一次db中strategy数据到cache
   207  	if err = d.cacheMgn.AuthStrategy().ForceSync(); err != nil {
   208  		log.Error("[Auth][Checker] force sync strategy to cache failed",
   209  			utils.RequestID(authCtx.GetRequestContext()), zap.Error(err))
   210  		return false, err
   211  	}
   212  
   213  	return d.doCheckPermission(authCtx)
   214  }
   215  
   216  func canDowngradeAnonymous(authCtx *model.AcquireContext, err error) bool {
   217  	if authCtx.GetModule() == model.AuthModule {
   218  		return false
   219  	}
   220  	if authCtx.IsFromClient() && AuthOption.ClientStrict {
   221  		return false
   222  	}
   223  	if authCtx.IsFromConsole() && AuthOption.ConsoleStrict {
   224  		return false
   225  	}
   226  	if errors.Is(err, model.ErrorTokenInvalid) {
   227  		return true
   228  	}
   229  	if errors.Is(err, model.ErrorTokenNotExist) {
   230  		return true
   231  	}
   232  	return false
   233  }
   234  
   235  // VerifyCredential 对 token 进行检查验证,并将 verify 过程中解析出的数据注入到 model.AcquireContext 中
   236  // step 1. 首先对 token 进行解析,获取相关的数据信息,注入到整个的 AcquireContext 中
   237  // step 2. 最后对 token 进行一些验证步骤的执行
   238  // step 3. 兜底措施:如果开启了鉴权的非严格模式,则根据错误的类型,判断是否转为匿名用户进行访问
   239  //   - 如果是访问权限控制相关模块(用户、用户组、权限策略),不得转为匿名用户
   240  func (d *DefaultAuthChecker) VerifyCredential(authCtx *model.AcquireContext) error {
   241  	reqId := utils.ParseRequestID(authCtx.GetRequestContext())
   242  
   243  	checkErr := func() error {
   244  		authToken := utils.ParseAuthToken(authCtx.GetRequestContext())
   245  		operator, err := d.decodeToken(authToken)
   246  		if err != nil {
   247  			log.Error("[Auth][Checker] decode token", zap.Error(err))
   248  			return model.ErrorTokenInvalid
   249  		}
   250  
   251  		ownerId, isOwner, err := d.checkToken(&operator)
   252  		if err != nil {
   253  			log.Errorf("[Auth][Checker] check token err : %s", errors.WithStack(err).Error())
   254  			return err
   255  		}
   256  
   257  		operator.OwnerID = ownerId
   258  		ctx := authCtx.GetRequestContext()
   259  		ctx = context.WithValue(ctx, utils.ContextIsOwnerKey, isOwner)
   260  		ctx = context.WithValue(ctx, utils.ContextUserIDKey, operator.OperatorID)
   261  		ctx = context.WithValue(ctx, utils.ContextOwnerIDKey, ownerId)
   262  		authCtx.SetRequestContext(ctx)
   263  		d.parseOperatorInfo(operator, authCtx)
   264  		if operator.Disable {
   265  			log.Warn("[Auth][Checker] token already disabled", utils.ZapRequestID(reqId),
   266  				zap.Any("token", operator.String()))
   267  		}
   268  		return nil
   269  	}()
   270  
   271  	if checkErr != nil {
   272  		if !canDowngradeAnonymous(authCtx, checkErr) {
   273  			return checkErr
   274  		}
   275  		log.Warn("[Auth][Checker] parse operator info, downgrade to anonymous", utils.ZapRequestID(reqId),
   276  			zap.Error(checkErr))
   277  		// 操作者信息解析失败,降级为匿名用户
   278  		authCtx.SetAttachment(model.TokenDetailInfoKey, newAnonymous())
   279  	}
   280  
   281  	return nil
   282  }
   283  
   284  func (d *DefaultAuthChecker) parseOperatorInfo(operator OperatorInfo, authCtx *model.AcquireContext) {
   285  	ctx := authCtx.GetRequestContext()
   286  	if operator.IsUserToken {
   287  		user := d.Cache().User().GetUserByID(operator.OperatorID)
   288  		if user != nil {
   289  			operator.Role = user.Type
   290  			ctx = context.WithValue(ctx, utils.ContextOperator, user.Name)
   291  			ctx = context.WithValue(ctx, utils.ContextUserNameKey, user.Name)
   292  			ctx = context.WithValue(ctx, utils.ContextUserRoleIDKey, user.Type)
   293  		}
   294  	} else {
   295  		userGroup := d.Cache().User().GetGroup(operator.OperatorID)
   296  		if userGroup != nil {
   297  			ctx = context.WithValue(ctx, utils.ContextOperator, userGroup.Name)
   298  			ctx = context.WithValue(ctx, utils.ContextUserNameKey, userGroup.Name)
   299  		}
   300  	}
   301  
   302  	authCtx.SetAttachment(model.OperatorRoleKey, operator.Role)
   303  	authCtx.SetAttachment(model.OperatorPrincipalType, func() model.PrincipalType {
   304  		if operator.IsUserToken {
   305  			return model.PrincipalUser
   306  		}
   307  		return model.PrincipalGroup
   308  	}())
   309  	authCtx.SetAttachment(model.OperatorIDKey, operator.OperatorID)
   310  	authCtx.SetAttachment(model.OperatorOwnerKey, operator)
   311  	authCtx.SetAttachment(model.TokenDetailInfoKey, operator)
   312  
   313  	authCtx.SetRequestContext(ctx)
   314  }
   315  
   316  // DecodeToken
   317  func (d *DefaultAuthChecker) DecodeToken(t string) (OperatorInfo, error) {
   318  	return d.decodeToken(t)
   319  }
   320  
   321  // decodeToken 解析 token 信息,如果 t == "",直接返回一个空对象
   322  func (d *DefaultAuthChecker) decodeToken(t string) (OperatorInfo, error) {
   323  	if t == "" {
   324  		return OperatorInfo{}, model.ErrorTokenInvalid
   325  	}
   326  
   327  	ret, err := decryptMessage([]byte(AuthOption.Salt), t)
   328  	if err != nil {
   329  		return OperatorInfo{}, err
   330  	}
   331  	tokenDetails := strings.Split(ret, TokenSplit)
   332  	if len(tokenDetails) != 2 {
   333  		return OperatorInfo{}, model.ErrorTokenInvalid
   334  	}
   335  
   336  	detail := strings.Split(tokenDetails[1], "/")
   337  	if len(detail) != 2 {
   338  		return OperatorInfo{}, model.ErrorTokenInvalid
   339  	}
   340  
   341  	tokenInfo := OperatorInfo{
   342  		Origin:      t,
   343  		IsUserToken: detail[0] == model.TokenForUser,
   344  		OperatorID:  detail[1],
   345  		Role:        model.UnknownUserRole,
   346  	}
   347  	return tokenInfo, nil
   348  }
   349  
   350  // checkToken 对 token 进行检查,如果 token 是一个空,直接返回默认值,但是不返回错误
   351  // return {owner-id} {is-owner} {error}
   352  func (d *DefaultAuthChecker) checkToken(tokenInfo *OperatorInfo) (string, bool, error) {
   353  	if IsEmptyOperator(*tokenInfo) {
   354  		return "", false, nil
   355  	}
   356  
   357  	id := tokenInfo.OperatorID
   358  	if tokenInfo.IsUserToken {
   359  		user := d.Cache().User().GetUserByID(id)
   360  		if user == nil {
   361  			return "", false, model.ErrorNoUser
   362  		}
   363  
   364  		if tokenInfo.Origin != user.Token {
   365  			return "", false, model.ErrorTokenNotExist
   366  		}
   367  
   368  		tokenInfo.Disable = !user.TokenEnable
   369  		if user.Owner == "" {
   370  			return user.ID, true, nil
   371  		}
   372  
   373  		return user.Owner, false, nil
   374  	}
   375  	group := d.Cache().User().GetGroup(id)
   376  	if group == nil {
   377  		return "", false, model.ErrorNoUserGroup
   378  	}
   379  
   380  	if tokenInfo.Origin != group.Token {
   381  		return "", false, model.ErrorTokenNotExist
   382  	}
   383  
   384  	tokenInfo.Disable = !group.TokenEnable
   385  	return group.Owner, false, nil
   386  }
   387  
   388  func (d *DefaultAuthChecker) isResourceEditable(
   389  	principal model.Principal,
   390  	resourceType apisecurity.ResourceType,
   391  	resEntries []model.ResourceEntry) bool {
   392  	for _, entry := range resEntries {
   393  		if !d.cacheMgn.AuthStrategy().IsResourceEditable(principal, resourceType, entry.ID) {
   394  			return false
   395  		}
   396  	}
   397  	return true
   398  }
   399  
   400  // doCheckPermission 执行权限检查
   401  func (d *DefaultAuthChecker) doCheckPermission(authCtx *model.AcquireContext) (bool, error) {
   402  
   403  	var checkNamespace, checkSvc, checkCfgGroup bool
   404  
   405  	reqRes := authCtx.GetAccessResources()
   406  	nsResEntries := reqRes[apisecurity.ResourceType_Namespaces]
   407  	svcResEntries := reqRes[apisecurity.ResourceType_Services]
   408  	cfgResEntries := reqRes[apisecurity.ResourceType_ConfigGroups]
   409  
   410  	principleID, _ := authCtx.GetAttachment(model.OperatorIDKey).(string)
   411  	principleType, _ := authCtx.GetAttachment(model.OperatorPrincipalType).(model.PrincipalType)
   412  	p := model.Principal{
   413  		PrincipalID:   principleID,
   414  		PrincipalRole: principleType,
   415  	}
   416  	checkNamespace = d.isResourceEditable(p, apisecurity.ResourceType_Namespaces, nsResEntries)
   417  	checkSvc = d.isResourceEditable(p, apisecurity.ResourceType_Services, svcResEntries)
   418  	checkCfgGroup = d.isResourceEditable(p, apisecurity.ResourceType_ConfigGroups, cfgResEntries)
   419  
   420  	checkAllResEntries := checkNamespace && checkSvc && checkCfgGroup
   421  
   422  	var err error
   423  	if !checkAllResEntries {
   424  		err = ErrorNotPermission
   425  	}
   426  	return checkAllResEntries, err
   427  }
   428  
   429  // checkAction 检查操作是否和策略匹配
   430  func (d *DefaultAuthChecker) checkAction(expect string, actual model.ResourceOperation, method string) bool {
   431  	// TODO 后续可针对读写操作进行鉴权, 并且可以针对具体的方法调用进行鉴权控制
   432  	return true
   433  }