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 }