github.com/polarismesh/polaris@v1.17.8/cache/auth/strategy.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 auth 19 20 import ( 21 "fmt" 22 "math" 23 "time" 24 25 apisecurity "github.com/polarismesh/specification/source/go/api/v1/security" 26 "go.uber.org/zap" 27 "golang.org/x/sync/singleflight" 28 29 types "github.com/polarismesh/polaris/cache/api" 30 "github.com/polarismesh/polaris/common/model" 31 "github.com/polarismesh/polaris/common/utils" 32 "github.com/polarismesh/polaris/store" 33 ) 34 35 const ( 36 removePrincipalChSize = 8 37 ) 38 39 // strategyCache 40 type strategyCache struct { 41 *types.BaseCache 42 43 storage store.Store 44 strategys *utils.SyncMap[string, *model.StrategyDetailCache] 45 uid2Strategy *utils.SyncMap[string, *utils.SyncSet[string]] 46 groupid2Strategy *utils.SyncMap[string, *utils.SyncSet[string]] 47 48 namespace2Strategy *utils.SyncMap[string, *utils.SyncSet[string]] 49 service2Strategy *utils.SyncMap[string, *utils.SyncSet[string]] 50 configGroup2Strategy *utils.SyncMap[string, *utils.SyncSet[string]] 51 52 lastMtime int64 53 userCache *userCache 54 singleFlight *singleflight.Group 55 } 56 57 // NewStrategyCache 58 func NewStrategyCache(storage store.Store, cacheMgr types.CacheManager) types.StrategyCache { 59 return &strategyCache{ 60 BaseCache: types.NewBaseCache(storage, cacheMgr), 61 storage: storage, 62 } 63 } 64 65 func (sc *strategyCache) Initialize(c map[string]interface{}) error { 66 sc.userCache = sc.BaseCache.CacheMgr.GetCacher(types.CacheUser).(*userCache) 67 sc.strategys = utils.NewSyncMap[string, *model.StrategyDetailCache]() 68 sc.uid2Strategy = utils.NewSyncMap[string, *utils.SyncSet[string]]() 69 sc.groupid2Strategy = utils.NewSyncMap[string, *utils.SyncSet[string]]() 70 sc.namespace2Strategy = utils.NewSyncMap[string, *utils.SyncSet[string]]() 71 sc.service2Strategy = utils.NewSyncMap[string, *utils.SyncSet[string]]() 72 sc.configGroup2Strategy = utils.NewSyncMap[string, *utils.SyncSet[string]]() 73 sc.singleFlight = new(singleflight.Group) 74 sc.lastMtime = 0 75 return nil 76 } 77 78 func (sc *strategyCache) Update() error { 79 // 多个线程竞争,只有一个线程进行更新 80 _, err, _ := sc.singleFlight.Do(sc.Name(), func() (interface{}, error) { 81 return nil, sc.DoCacheUpdate(sc.Name(), sc.realUpdate) 82 }) 83 return err 84 } 85 86 func (sc *strategyCache) ForceSync() error { 87 return sc.Update() 88 } 89 90 func (sc *strategyCache) realUpdate() (map[string]time.Time, int64, error) { 91 // 获取几秒前的全部数据 92 var ( 93 start = time.Now() 94 lastTime = sc.LastFetchTime() 95 strategies, err = sc.storage.GetStrategyDetailsForCache(lastTime, sc.IsFirstUpdate()) 96 ) 97 if err != nil { 98 log.Errorf("[Cache][AuthStrategy] refresh auth strategy cache err: %s", err.Error()) 99 return nil, -1, err 100 } 101 102 lastMtimes, add, update, del := sc.setStrategys(strategies) 103 timeDiff := time.Since(start) 104 if timeDiff > time.Second { 105 log.Info("[Cache][AuthStrategy] get more auth strategy", 106 zap.Int("add", add), zap.Int("update", update), zap.Int("delete", del), 107 zap.Time("last", lastTime), zap.Duration("used", time.Since(start))) 108 } 109 return lastMtimes, int64(len(strategies)), nil 110 } 111 112 // setStrategys 处理策略的数据更新情况 113 // step 1. 先处理resource以及principal的数据更新情况(主要是为了能够获取到新老数据进行对比计算) 114 // step 2. 处理真正的 strategy 的缓存更新 115 func (sc *strategyCache) setStrategys(strategies []*model.StrategyDetail) (map[string]time.Time, int, int, int) { 116 var add, remove, update int 117 118 sc.handlerResourceStrategy(strategies) 119 sc.handlerPrincipalStrategy(strategies) 120 121 lastMtime := sc.LastMtime(sc.Name()).Unix() 122 123 for index := range strategies { 124 rule := strategies[index] 125 if !rule.Valid { 126 sc.strategys.Delete(rule.ID) 127 remove++ 128 } else { 129 _, ok := sc.strategys.Load(rule.ID) 130 if !ok { 131 add++ 132 } else { 133 update++ 134 } 135 sc.strategys.Store(rule.ID, buildEnchanceStrategyDetail(rule)) 136 } 137 138 lastMtime = int64(math.Max(float64(lastMtime), float64(rule.ModifyTime.Unix()))) 139 } 140 141 return map[string]time.Time{sc.Name(): time.Unix(lastMtime, 0)}, add, update, remove 142 } 143 144 func buildEnchanceStrategyDetail(strategy *model.StrategyDetail) *model.StrategyDetailCache { 145 users := make(map[string]model.Principal, 0) 146 groups := make(map[string]model.Principal, 0) 147 148 for index := range strategy.Principals { 149 principal := strategy.Principals[index] 150 if principal.PrincipalRole == model.PrincipalUser { 151 users[principal.PrincipalID] = principal 152 } else { 153 groups[principal.PrincipalID] = principal 154 } 155 } 156 157 return &model.StrategyDetailCache{ 158 StrategyDetail: strategy, 159 UserPrincipal: users, 160 GroupPrincipal: groups, 161 } 162 } 163 164 func (sc *strategyCache) writeSet(linkContainers *utils.SyncMap[string, *utils.SyncSet[string]], key, val string, isDel bool) { 165 if isDel { 166 values, ok := linkContainers.Load(key) 167 if ok { 168 values.Remove(val) 169 } 170 } else { 171 if _, ok := linkContainers.Load(key); !ok { 172 linkContainers.Store(key, utils.NewSyncSet[string]()) 173 } 174 values, _ := linkContainers.Load(key) 175 values.Add(val) 176 } 177 } 178 179 // handlerResourceStrategy 处理资源视角下策略的缓存 180 // 根据新老策略的资源列表比对,计算出哪些资源不在和该策略存在关联关系,哪些资源新增了相关的策略 181 func (sc *strategyCache) handlerResourceStrategy(strategies []*model.StrategyDetail) { 182 operateLink := func(resType int32, resId, strategyId string, remove bool) { 183 switch resType { 184 case int32(apisecurity.ResourceType_Namespaces): 185 sc.writeSet(sc.namespace2Strategy, resId, strategyId, remove) 186 case int32(apisecurity.ResourceType_Services): 187 sc.writeSet(sc.service2Strategy, resId, strategyId, remove) 188 case int32(apisecurity.ResourceType_ConfigGroups): 189 sc.writeSet(sc.configGroup2Strategy, resId, strategyId, remove) 190 } 191 } 192 193 for sIndex := range strategies { 194 rule := strategies[sIndex] 195 addRes := rule.Resources 196 197 if oldRule, exist := sc.strategys.Load(rule.ID); exist { 198 delRes := make([]model.StrategyResource, 0, 8) 199 // 计算前后对比, resource 的变化 200 newRes := make(map[string]struct{}, len(addRes)) 201 for i := range addRes { 202 newRes[fmt.Sprintf("%d_%s", addRes[i].ResType, addRes[i].ResID)] = struct{}{} 203 } 204 205 // 筛选出从策略中被踢出的 resource 列表 206 for i := range oldRule.Resources { 207 item := oldRule.Resources[i] 208 if _, ok := newRes[fmt.Sprintf("%d_%s", item.ResType, item.ResID)]; !ok { 209 delRes = append(delRes, item) 210 } 211 } 212 213 // 针对被剔除的 resource 列表,清理掉所关联的鉴权策略信息 214 for rIndex := range delRes { 215 resource := delRes[rIndex] 216 operateLink(resource.ResType, resource.ResID, rule.ID, true) 217 } 218 } 219 220 for rIndex := range addRes { 221 resource := addRes[rIndex] 222 if rule.Valid { 223 operateLink(resource.ResType, resource.ResID, rule.ID, false) 224 } else { 225 operateLink(resource.ResType, resource.ResID, rule.ID, true) 226 } 227 } 228 } 229 } 230 231 // handlerPrincipalStrategy 232 func (sc *strategyCache) handlerPrincipalStrategy(strategies []*model.StrategyDetail) { 233 for index := range strategies { 234 rule := strategies[index] 235 // 计算 uid -> auth rule 236 principals := rule.Principals 237 238 if oldRule, exist := sc.strategys.Load(rule.ID); exist { 239 delMembers := make([]model.Principal, 0, 8) 240 // 计算前后对比, principal 的变化 241 newRes := make(map[string]struct{}, len(principals)) 242 for i := range principals { 243 newRes[fmt.Sprintf("%d_%s", principals[i].PrincipalRole, principals[i].PrincipalID)] = struct{}{} 244 } 245 246 // 筛选出从策略中被踢出的 principal 列表 247 for i := range oldRule.Principals { 248 item := oldRule.Principals[i] 249 if _, ok := newRes[fmt.Sprintf("%d_%s", item.PrincipalRole, item.PrincipalID)]; !ok { 250 delMembers = append(delMembers, item) 251 } 252 } 253 254 // 针对被剔除的 principal 列表,清理掉所关联的鉴权策略信息 255 for rIndex := range delMembers { 256 principal := delMembers[rIndex] 257 sc.removePrincipalLink(principal, rule) 258 } 259 } 260 if rule.Valid { 261 for pos := range principals { 262 principal := principals[pos] 263 sc.addPrincipalLink(principal, rule) 264 } 265 } else { 266 for pos := range principals { 267 principal := principals[pos] 268 sc.removePrincipalLink(principal, rule) 269 } 270 } 271 } 272 } 273 274 func (sc *strategyCache) removePrincipalLink(principal model.Principal, rule *model.StrategyDetail) { 275 linkContainers := sc.uid2Strategy 276 if principal.PrincipalRole != model.PrincipalUser { 277 linkContainers = sc.groupid2Strategy 278 } 279 sc.writeSet(linkContainers, principal.PrincipalID, rule.ID, true) 280 } 281 282 func (sc *strategyCache) addPrincipalLink(principal model.Principal, rule *model.StrategyDetail) { 283 linkContainers := sc.uid2Strategy 284 if principal.PrincipalRole != model.PrincipalUser { 285 linkContainers = sc.groupid2Strategy 286 } 287 sc.writeSet(linkContainers, principal.PrincipalID, rule.ID, false) 288 } 289 290 func (sc *strategyCache) Clear() error { 291 sc.BaseCache.Clear() 292 sc.strategys = utils.NewSyncMap[string, *model.StrategyDetailCache]() 293 sc.uid2Strategy = utils.NewSyncMap[string, *utils.SyncSet[string]]() 294 sc.groupid2Strategy = utils.NewSyncMap[string, *utils.SyncSet[string]]() 295 sc.namespace2Strategy = utils.NewSyncMap[string, *utils.SyncSet[string]]() 296 sc.service2Strategy = utils.NewSyncMap[string, *utils.SyncSet[string]]() 297 sc.configGroup2Strategy = utils.NewSyncMap[string, *utils.SyncSet[string]]() 298 sc.lastMtime = 0 299 return nil 300 } 301 302 func (sc *strategyCache) Name() string { 303 return types.StrategyRuleName 304 } 305 306 // 对于 check 逻辑,如果是计算 * 策略,则必须要求 * 资源下必须有策略 307 // 如果是具体的资源ID,则该资源下不必有策略,如果没有策略就认为这个资源是可以被任何人编辑的 308 func (sc *strategyCache) checkResourceEditable(strategIds *utils.SyncSet[string], principal model.Principal, mustCheck bool) bool { 309 // 是否可以编辑 310 editable := false 311 // 是否真的包含策略 312 isCheck := strategIds.Len() != 0 313 314 // 如果根本没有遍历过,则表示该资源下没有对应的策略列表,直接返回可编辑状态即可 315 if !isCheck && !mustCheck { 316 return true 317 } 318 319 strategIds.Range(func(strategyId string) { 320 isCheck = true 321 if rule, ok := sc.strategys.Load(strategyId); ok { 322 if principal.PrincipalRole == model.PrincipalUser { 323 _, exist := rule.UserPrincipal[principal.PrincipalID] 324 editable = editable || exist 325 } else { 326 _, exist := rule.GroupPrincipal[principal.PrincipalID] 327 editable = editable || exist 328 } 329 } 330 }) 331 332 return editable 333 } 334 335 // IsResourceEditable 判断当前资源是否可以操作 336 // 这里需要考虑两种情况,一种是 “ * ” 策略,另一种是明确指出了具体的资源ID的策略 337 func (sc *strategyCache) IsResourceEditable( 338 principal model.Principal, resType apisecurity.ResourceType, resId string) bool { 339 var ( 340 valAll, val *utils.SyncSet[string] 341 ok bool 342 ) 343 switch resType { 344 case apisecurity.ResourceType_Namespaces: 345 val, ok = sc.namespace2Strategy.Load(resId) 346 valAll, _ = sc.namespace2Strategy.Load("*") 347 case apisecurity.ResourceType_Services: 348 val, ok = sc.service2Strategy.Load(resId) 349 valAll, _ = sc.service2Strategy.Load("*") 350 case apisecurity.ResourceType_ConfigGroups: 351 val, ok = sc.configGroup2Strategy.Load(resId) 352 valAll, _ = sc.configGroup2Strategy.Load("*") 353 } 354 355 // 代表该资源没有关联到任何策略,任何人都可以编辑 356 if !ok { 357 return true 358 } 359 360 principals := make([]model.Principal, 0, 4) 361 principals = append(principals, principal) 362 if principal.PrincipalRole == model.PrincipalUser { 363 groupids := sc.userCache.GetUserLinkGroupIds(principal.PrincipalID) 364 for i := range groupids { 365 principals = append(principals, model.Principal{ 366 PrincipalID: groupids[i], 367 PrincipalRole: model.PrincipalGroup, 368 }) 369 } 370 } 371 372 for i := range principals { 373 item := principals[i] 374 if valAll != nil && sc.checkResourceEditable(valAll, item, true) { 375 return true 376 } 377 378 if sc.checkResourceEditable(val, item, false) { 379 return true 380 } 381 } 382 383 return false 384 } 385 386 func (sc *strategyCache) GetStrategyDetailsByUID(uid string) []*model.StrategyDetail { 387 return sc.getStrategyDetails(uid, "") 388 } 389 390 func (sc *strategyCache) GetStrategyDetailsByGroupID(groupid string) []*model.StrategyDetail { 391 return sc.getStrategyDetails("", groupid) 392 } 393 394 func (sc *strategyCache) getStrategyDetails(uid string, gid string) []*model.StrategyDetail { 395 var ( 396 strategyIds []string 397 ) 398 if uid != "" { 399 sets, ok := sc.uid2Strategy.Load(uid) 400 if !ok { 401 return nil 402 } 403 strategyIds = sets.ToSlice() 404 } else if gid != "" { 405 sets, ok := sc.groupid2Strategy.Load(gid) 406 if !ok { 407 return nil 408 } 409 strategyIds = sets.ToSlice() 410 } 411 412 if len(strategyIds) > 0 { 413 result := make([]*model.StrategyDetail, 0, 16) 414 for i := range strategyIds { 415 strategy, ok := sc.strategys.Load(strategyIds[i]) 416 if ok { 417 result = append(result, strategy.StrategyDetail) 418 } 419 } 420 421 return result 422 } 423 424 return nil 425 } 426 427 // IsResourceLinkStrategy 校验 428 func (sc *strategyCache) IsResourceLinkStrategy(resType apisecurity.ResourceType, resId string) bool { 429 switch resType { 430 case apisecurity.ResourceType_Namespaces: 431 val, ok := sc.namespace2Strategy.Load(resId) 432 return ok && hasLinkRule(val) 433 case apisecurity.ResourceType_Services: 434 val, ok := sc.service2Strategy.Load(resId) 435 return ok && hasLinkRule(val) 436 case apisecurity.ResourceType_ConfigGroups: 437 val, ok := sc.configGroup2Strategy.Load(resId) 438 return ok && hasLinkRule(val) 439 default: 440 return true 441 } 442 } 443 444 func hasLinkRule(sets *utils.SyncSet[string]) bool { 445 return sets.Len() != 0 446 }