| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579 |
- // Copyright 2019 Yunion
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package policy
- import (
- "context"
- "reflect"
- "sort"
- "strings"
- "sync"
- "time"
- "yunion.io/x/jsonutils"
- "yunion.io/x/log"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/gotypes"
- "yunion.io/x/pkg/util/netutils"
- "yunion.io/x/pkg/util/rbacscope"
- "yunion.io/x/onecloud/pkg/apis"
- identity_api "yunion.io/x/onecloud/pkg/apis/identity"
- "yunion.io/x/onecloud/pkg/cloudcommon/consts"
- "yunion.io/x/onecloud/pkg/httperrors"
- "yunion.io/x/onecloud/pkg/mcclient"
- "yunion.io/x/onecloud/pkg/mcclient/auth"
- "yunion.io/x/onecloud/pkg/mcclient/modules/identity"
- "yunion.io/x/onecloud/pkg/util/ctx"
- "yunion.io/x/onecloud/pkg/util/hashcache"
- "yunion.io/x/onecloud/pkg/util/rbacutils"
- "yunion.io/x/onecloud/pkg/util/tagutils"
- )
- const (
- PolicyDelegation = "delegate"
- PolicyActionList = rbacutils.ActionList
- PolicyActionGet = rbacutils.ActionGet
- PolicyActionUpdate = rbacutils.ActionUpdate
- PolicyActionPatch = rbacutils.ActionPatch
- PolicyActionCreate = rbacutils.ActionCreate
- PolicyActionDelete = rbacutils.ActionDelete
- PolicyActionPerform = rbacutils.ActionPerform
- )
- type PolicyFetchFunc func(ctx context.Context, token mcclient.TokenCredential) (*mcclient.SFetchMatchPoliciesOutput, error)
- var (
- PolicyManager *SPolicyManager
- DefaultPolicyFetcher PolicyFetchFunc
- )
- func init() {
- PolicyManager = &SPolicyManager{
- lock: &sync.Mutex{},
- }
- DefaultPolicyFetcher = auth.FetchMatchPolicies
- }
- type SPolicyManager struct {
- defaultPolicies map[rbacscope.TRbacScope][]*rbacutils.SRbacPolicy
- refreshInterval time.Duration
- policyCache *hashcache.Cache // policy cache
- permissionCache *hashcache.Cache // permission cache
- // fetchWorker *appsrv.SWorkerManager
- lock *sync.Mutex
- }
- type sPolicyData struct {
- Id string `json:"id"`
- Name string `json:"name"`
- Enabled bool `json:"enabled"`
- DomainId string `json:"domain_id"`
- IsPublic bool `json:"is_public"`
- PublicScope rbacscope.TRbacScope `json:"public_scope"`
- SharedDomains []apis.SharedDomain `json:"shared_domain"`
- Scope rbacscope.TRbacScope `json:"scope"`
- Policy jsonutils.JSONObject `json:"policy"`
- DomainTags tagutils.TTagSet `json:"domain_tags"`
- ProjectTags tagutils.TTagSet `json:"project_tags"`
- ObjectTags tagutils.TTagSet `json:"resource_tags"`
- OrgNodes []identity_api.SOrganizationNodeInfo `json:"org_nodes"`
- }
- func (data sPolicyData) getPolicy() (*rbacutils.SPolicy, error) {
- var domainTags, projectTags, objectTags tagutils.TTagSetList
- if len(data.DomainTags) > 0 {
- domainTags = domainTags.Append(data.DomainTags)
- }
- if len(data.ProjectTags) > 0 {
- projectTags = projectTags.Append(data.ProjectTags)
- }
- if len(data.ObjectTags) > 0 {
- objectTags = objectTags.Append(data.ObjectTags)
- }
- for i := range data.OrgNodes {
- orgNode := data.OrgNodes[i]
- switch orgNode.Type {
- case identity_api.OrgTypeDomain:
- domainTags = domainTags.Append(orgNode.Tags)
- case identity_api.OrgTypeProject:
- projectTags = projectTags.Append(orgNode.Tags)
- case identity_api.OrgTypeObject:
- objectTags = objectTags.Append(orgNode.Tags)
- }
- }
- return rbacutils.DecodePolicyData(domainTags, projectTags, objectTags, data.Policy)
- }
- func (manager *SPolicyManager) init(refreshInterval time.Duration, workerCount int) {
- manager.refreshInterval = refreshInterval
- // manager.InitSync(manager)
- if len(predefinedDefaultPolicies) > 0 {
- policiesMap := make(map[rbacscope.TRbacScope][]*rbacutils.SRbacPolicy)
- for i := range predefinedDefaultPolicies {
- policy := predefinedDefaultPolicies[i]
- if _, ok := policiesMap[policy.Scope]; !ok {
- policiesMap[policy.Scope] = make([]*rbacutils.SRbacPolicy, 0)
- }
- policies := policiesMap[policy.Scope]
- policies = append(policies, &policy)
- policiesMap[policy.Scope] = policies
- }
- manager.defaultPolicies = policiesMap
- }
- manager.policyCache = hashcache.NewCache(2048, refreshInterval)
- manager.permissionCache = hashcache.NewCache(2048, refreshInterval)
- defaultFetcherFuncAddr := reflect.ValueOf(DefaultPolicyFetcher).Pointer()
- remoteFetcherFuncAddr := reflect.ValueOf(auth.FetchMatchPolicies).Pointer()
- log.Debugf("DefaultPolicyFetcher: %x RemotePolicyFetcher: %x", defaultFetcherFuncAddr, remoteFetcherFuncAddr)
- // var isDB bool
- // if defaultFetcherFuncAddr == remoteFetcherFuncAddr {
- // remote fetcher, so start watcher
- // isDB = false
- // } else {
- // isDB = true
- // }
- // if workerCount <= 0 {
- // workerCount = 1
- // }
- // log.Infof("policy fetch worker count %d", workerCount)
- // manager.fetchWorker = appsrv.NewWorkerManager("policyFetchWorker", workerCount, 2048, isDB)
- }
- func getMaskedLoginIp(userCred mcclient.TokenCredential) string {
- loginIp, _ := netutils.NewIPV4Addr(userCred.GetLoginIp())
- return loginIp.NetAddr(16).String()
- }
- func policyKey(userCred mcclient.TokenCredential) string {
- if userCred == nil || len(userCred.GetTokenString()) == 0 || auth.IsGuestToken(userCred) {
- return auth.GUEST_TOKEN
- }
- keys := []string{userCred.GetProjectId()}
- roles := userCred.GetRoleIds()
- if len(roles) > 0 {
- sort.Strings(roles)
- }
- keys = append(keys, strings.Join(roles, ":"))
- keys = append(keys, getMaskedLoginIp(userCred))
- return strings.Join(keys, "-")
- }
- func permissionKey(scope rbacscope.TRbacScope, userCred mcclient.TokenCredential, service string, resource string, action string, extra ...string) string {
- queryKeys := []string{string(scope)}
- queryKeys = append(queryKeys, userCred.GetProjectId())
- roles := userCred.GetRoleIds()
- if len(roles) > 0 {
- sort.Strings(roles)
- }
- queryKeys = append(queryKeys, strings.Join(roles, ":"))
- queryKeys = append(queryKeys, getMaskedLoginIp(userCred))
- if rbacutils.WILD_MATCH == service || len(service) == 0 {
- service = rbacutils.WILD_MATCH
- }
- queryKeys = append(queryKeys, service)
- if rbacutils.WILD_MATCH == resource || len(resource) == 0 {
- resource = rbacutils.WILD_MATCH
- }
- queryKeys = append(queryKeys, resource)
- if rbacutils.WILD_MATCH == action || len(action) == 0 {
- action = rbacutils.WILD_MATCH
- }
- queryKeys = append(queryKeys, action)
- if len(extra) > 0 {
- queryKeys = append(queryKeys, extra...)
- }
- return strings.Join(queryKeys, "-")
- }
- func (manager *SPolicyManager) AllowScope(userCred mcclient.TokenCredential, service string, resource string, action string, extra ...string) (rbacscope.TRbacScope, rbacutils.SPolicyResult) {
- for _, scope := range []rbacscope.TRbacScope{
- rbacscope.ScopeSystem,
- rbacscope.ScopeDomain,
- rbacscope.ScopeProject,
- rbacscope.ScopeUser,
- } {
- result := manager.allow(scope, userCred, service, resource, action, extra...)
- if result.Result == rbacutils.Allow {
- return scope, result
- }
- }
- return rbacscope.ScopeNone, rbacutils.PolicyDeny
- }
- func (manager *SPolicyManager) Allow(targetScope rbacscope.TRbacScope, userCred mcclient.TokenCredential, service string, resource string, action string, extra ...string) rbacutils.SPolicyResult {
- var retryScopes []rbacscope.TRbacScope
- switch targetScope {
- case rbacscope.ScopeSystem:
- retryScopes = []rbacscope.TRbacScope{
- rbacscope.ScopeSystem,
- }
- case rbacscope.ScopeDomain:
- retryScopes = []rbacscope.TRbacScope{
- rbacscope.ScopeSystem,
- rbacscope.ScopeDomain,
- }
- case rbacscope.ScopeProject:
- retryScopes = []rbacscope.TRbacScope{
- rbacscope.ScopeSystem,
- rbacscope.ScopeDomain,
- rbacscope.ScopeProject,
- }
- case rbacscope.ScopeUser:
- retryScopes = []rbacscope.TRbacScope{
- rbacscope.ScopeSystem,
- rbacscope.ScopeUser,
- }
- }
- for _, scope := range retryScopes {
- result := manager.allow(scope, userCred, service, resource, action, extra...)
- if result.Result == rbacutils.Allow {
- return result
- }
- }
- return rbacutils.PolicyDeny
- }
- func (manager *SPolicyManager) fetchMatchedPolicies(userCred mcclient.TokenCredential) (*mcclient.SFetchMatchPoliciesOutput, error) {
- key := policyKey(userCred)
- val := manager.policyCache.AtomicGet(key)
- if !gotypes.IsNil(val) {
- // cache hit
- return val.(*mcclient.SFetchMatchPoliciesOutput), nil
- }
- pg, err := DefaultPolicyFetcher(ctx.CtxWithTime(), userCred)
- if err != nil {
- return nil, errors.Wrap(err, "DefaultPolicyFetcher")
- }
- manager.policyCache.AtomicSet(key, pg)
- return pg, nil
- }
- func (manager *SPolicyManager) allow(scope rbacscope.TRbacScope, userCred mcclient.TokenCredential, service string, resource string, action string, extra ...string) rbacutils.SPolicyResult {
- // check permission
- key := permissionKey(scope, userCred, service, resource, action, extra...)
- val := manager.permissionCache.AtomicGet(key)
- if !gotypes.IsNil(val) {
- if consts.IsRbacDebug() {
- log.Debugf("query %s:%s:%s:%s from cache %s", service, resource, action, extra, val)
- }
- return val.(rbacutils.SPolicyResult)
- }
- // first download userCred policy
- policies, err := manager.fetchMatchedPolicies(userCred)
- if err != nil {
- log.Errorf("fetchMatchedPolicyGroup fail %s", err)
- return rbacutils.PolicyDeny
- }
- policySet, ok := policies.Policies[scope]
- if !ok {
- policySet = rbacutils.TPolicySet{}
- }
- result := manager.allowWithoutCache(policySet, scope, userCred, service, resource, action, extra...)
- manager.permissionCache.AtomicSet(key, result)
- return result
- }
- /*
- func (manager *SPolicyManager) findPolicyByName(scope rbacscope.TRbacScope, name string) *rbacscope.SRbacPolicyCore {
- if policies, ok := manager.policies[scope]; ok {
- for i := range policies {
- if policies[i].Id == name || policies[i].Name == name {
- return policies[i].Policy
- }
- }
- }
- return nil
- }
- func getMatchedPolicyNames(policies []rbacscope.SPolicyInfo, userCred rbacscope.IRbacIdentity) []string {
- _, matchNames := rbacscope.GetMatchedPolicies(policies, userCred)
- return matchNames
- }
- func getMatchedPolicyRules(policies []rbacscope.SPolicyInfo, userCred rbacscope.IRbacIdentity, service string, resource string, action string, extra ...string) ([]rbacscope.SRbacRule, bool) {
- matchPolicies, _ := rbacscope.GetMatchedPolicies(policies, userCred)
- if len(matchPolicies) == 0 {
- return nil, false
- }
- return matchPolicies.GetMatchRules(service, resource, action, extra...), true
- }
- */
- func (manager *SPolicyManager) allowWithoutCache(policies rbacutils.TPolicySet, scope rbacscope.TRbacScope, userCred mcclient.TokenCredential, service string, resource string, action string, extra ...string) rbacutils.SPolicyResult {
- matchRules := rbacutils.TPolicyMatches{}
- if len(policies) == 0 {
- user, token := auth.GUEST_USER, auth.GUEST_TOKEN
- if userCred != nil {
- user = userCred.GetUserName()
- token = userCred.GetTokenString()
- }
- log.Warningf("no policies fetched for scope %s user %s token %s key %s", scope, user, token, policyKey(userCred))
- } else {
- matchRules = policies.GetMatchRules(service, resource, action, extra...)
- if consts.IsRbacDebug() {
- log.Debugf("service %s resource %s action %s extra %s matchRules: %s", service, resource, action, jsonutils.Marshal(extra), jsonutils.Marshal(matchRules))
- }
- }
- scopedDeny := false
- switch scope {
- case rbacscope.ScopeUser:
- if !isUserResource(service, resource) {
- scopedDeny = true
- }
- case rbacscope.ScopeProject:
- if !isProjectResource(service, resource) {
- scopedDeny = true
- }
- case rbacscope.ScopeDomain:
- if isSystemResource(service, resource) {
- scopedDeny = true
- }
- case rbacscope.ScopeSystem:
- // no deny at all for system scope
- }
- if scopedDeny {
- rule := rbacutils.SPolicyMatch{
- Rule: rbacutils.SRbacRule{
- Service: service,
- Resource: resource,
- Result: rbacutils.Deny,
- },
- }
- matchRules = append(matchRules, rule)
- }
- result := matchRules.GetResult()
- if result.Result.IsDeny() {
- // denied, try default policies
- defaultPolicies, ok := manager.defaultPolicies[scope]
- if ok {
- for i := range defaultPolicies {
- isMatched, _ := defaultPolicies[i].Match(userCred)
- if !isMatched {
- continue
- }
- rule := defaultPolicies[i].Rules.GetMatchRule(service, resource, action, extra...)
- if rule != nil {
- if consts.IsRbacDebug() {
- log.Debugf("service: %s resource: %s action: %s extra: %s match default policy: %s match rule: %s", service, resource, action, jsonutils.Marshal(extra), jsonutils.Marshal(defaultPolicies[i]), jsonutils.Marshal(rule))
- }
- matchRules = append(matchRules,
- rbacutils.SPolicyMatch{
- Rule: *rule,
- },
- )
- }
- }
- }
- result = matchRules.GetResult()
- }
- if consts.IsRbacDebug() {
- log.Debugf("[RBAC: %s] %s %s %s %s permission %s userCred: %s MatchRules: %d(%s)", scope, service, resource, action, jsonutils.Marshal(extra), result, userCred, len(matchRules), jsonutils.Marshal(matchRules))
- }
- return result
- }
- // result: allow/deny for the named policy
- // userResult: allow/deny for the matched policies of userCred
- func explainPolicy(userCred mcclient.TokenCredential, policyReq jsonutils.JSONObject, policyData *sPolicyData) ([]string, rbacutils.SPolicyResult, rbacutils.SPolicyResult, error) {
- _, request, result, userResult, err := explainPolicyInternal(userCred, policyReq, policyData)
- return request, result, userResult, err
- }
- func fetchPolicyDataByIdOrName(ctx context.Context, id string) (*sPolicyData, error) {
- s := auth.GetAdminSession(ctx, consts.GetRegion())
- data, err := identity.Policies.Get(s, id, nil)
- if err != nil {
- return nil, errors.Wrap(err, "modules.Policies.Get")
- }
- pdata := &sPolicyData{}
- err = data.Unmarshal(&pdata)
- if err != nil {
- return nil, errors.Wrap(err, "Unmarshal Policy Data")
- }
- return pdata, nil
- }
- func explainPolicyInternal(userCred mcclient.TokenCredential, policyReq jsonutils.JSONObject, policyData *sPolicyData) (rbacscope.TRbacScope, []string, rbacutils.SPolicyResult, rbacutils.SPolicyResult, error) {
- policySeq, err := policyReq.GetArray()
- if err != nil {
- return rbacscope.ScopeSystem, nil, rbacutils.PolicyDeny, rbacutils.PolicyDeny, httperrors.NewInputParameterError("invalid format")
- }
- service := rbacutils.WILD_MATCH
- resource := rbacutils.WILD_MATCH
- action := rbacutils.WILD_MATCH
- extra := make([]string, 0)
- if len(policySeq) > 1 {
- service, _ = policySeq[1].GetString()
- }
- if len(policySeq) > 2 {
- resource, _ = policySeq[2].GetString()
- }
- if len(policySeq) > 3 {
- action, _ = policySeq[3].GetString()
- }
- if len(policySeq) > 4 {
- for i := 4; i < len(policySeq); i += 1 {
- ev, _ := policySeq[i].GetString()
- extra = append(extra, ev)
- }
- }
- reqStrs := []string{service, resource, action}
- if len(extra) > 0 {
- reqStrs = append(reqStrs, extra...)
- }
- scopeStr, _ := policySeq[0].GetString()
- scope := rbacscope.String2Scope(scopeStr)
- userResult := PolicyManager.Allow(scope, userCred, service, resource, action, extra...)
- result := userResult
- if policyData != nil {
- if scope.HigherThan(policyData.Scope) {
- result = rbacutils.PolicyDeny
- } else {
- policy, err := policyData.getPolicy()
- if err != nil {
- return scope, reqStrs, rbacutils.PolicyDeny, userResult, errors.Wrap(err, "getPolicy")
- }
- match := policy.GetMatchRule(service, resource, action, extra...)
- result = rbacutils.PolicyDeny
- if match != nil {
- result.Result = match.Rule.Result
- result.DomainTags = match.DomainTags
- result.ProjectTags = match.ProjectTags
- result.ObjectTags = match.ObjectTags
- }
- }
- }
- return scope, reqStrs, result, userResult, nil
- }
- func ExplainRpc(ctx context.Context, userCred mcclient.TokenCredential, params jsonutils.JSONObject, name string) (jsonutils.JSONObject, error) {
- paramDict, err := params.GetMap()
- if err != nil {
- return nil, httperrors.NewInputParameterError("invalid input format")
- }
- var policyData *sPolicyData
- if len(name) > 0 {
- policyData, err = fetchPolicyDataByIdOrName(ctx, name)
- if err != nil {
- return nil, errors.Wrap(err, "fetchPolicyDataByIdOrName")
- }
- }
- ret := jsonutils.NewDict()
- for key, policyReq := range paramDict {
- reqStrs, result, userResult, err := explainPolicy(userCred, policyReq, policyData)
- if err != nil {
- return nil, err
- }
- reqStrs = append(reqStrs, string(result.Result))
- if len(name) > 0 {
- reqStrs = append(reqStrs, string(userResult.Result))
- }
- ret.Add(jsonutils.NewStringArray(reqStrs), key)
- }
- return ret, nil
- }
- func (manager *SPolicyManager) IsScopeCapable(userCred mcclient.TokenCredential, scope rbacscope.TRbacScope) bool {
- policies, err := manager.fetchMatchedPolicies(userCred)
- if err != nil {
- log.Errorf("fetchMatchedPolicyGroup fail %s", err)
- return false
- }
- if set, ok := policies.Policies[scope]; ok && len(set) > 0 {
- return true
- }
- return false
- }
- /*
- func (manager *SPolicyManager) MatchedPolicyNames(ctx context.Context, scope rbacscope.TRbacScope, ident rbacscope.IRbacIdentity) []string {
- policies, err := manager.fetchMatchedPolicies(ctx, userCred)
- if err != nil {
- log.Errorf("fetchMatchedPolicyGroup fail %s", err)
- return false
- }
- ret := make([]string, 0)
- policies, ok := manager.policies[scope]
- if !ok {
- return ret
- }
- return getMatchedPolicyNames(policies, userCred)
- }*/
- /*
- func (manager *SPolicyManager) AllPolicies() map[string][]string {
- ret := make(map[string][]string)
- for scope, p := range manager.policies {
- k := string(scope)
- ret[k] = make([]string, len(p))
- for i := range p {
- ret[k][i] = p[i].Name
- }
- }
- return ret
- }
- func (manager *SPolicyManager) RoleMatchPolicies(roleName string) []string {
- ident := rbacscope.NewRbacIdentity("", "", []string{roleName})
- ret := make([]string, 0)
- for _, policies := range manager.policies {
- for i := range policies {
- if matched, _ := policies[i].Policy.Match(ident); matched {
- ret = append(ret, policies[i].Name)
- }
- }
- }
- return ret
- }
- func (manager *SPolicyManager) GetMatchedPolicySet(userCred rbacscope.IRbacIdentity) (rbacscope.TRbacScope, rbacscope.TPolicySet) {
- for _, scope := range []rbacscope.TRbacScope{
- rbacscope.ScopeSystem,
- rbacscope.ScopeDomain,
- rbacscope.ScopeProject,
- } {
- macthed, _ := rbacscope.GetMatchedPolicies(manager.policies[scope], userCred)
- if len(macthed) > 0 {
- return scope, macthed
- }
- }
- return rbacscope.ScopeNone, nil
- }
- */
|