diff --git a/build/climc/root/opt/yunion/scripts/rbac/domainadmin.yaml b/build/climc/root/opt/yunion/scripts/rbac/domainadmin.yaml deleted file mode 100644 index 7055cae468..0000000000 --- a/build/climc/root/opt/yunion/scripts/rbac/domainadmin.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# rbac for project owner, not allow for delete -roles: - - domain_admin -scope: domain -policy: - '*': allow diff --git a/build/climc/root/opt/yunion/scripts/rbac/member.yaml b/build/climc/root/opt/yunion/scripts/rbac/editor.yaml similarity index 50% rename from build/climc/root/opt/yunion/scripts/rbac/member.yaml rename to build/climc/root/opt/yunion/scripts/rbac/editor.yaml index fe00061b88..7cf29aa66a 100644 --- a/build/climc/root/opt/yunion/scripts/rbac/member.yaml +++ b/build/climc/root/opt/yunion/scripts/rbac/editor.yaml @@ -1,9 +1,10 @@ -# rbac for normal user, not allow for delete -scope: project policy: '*': '*': '*': allow create: deny - update: deny delete: deny + perform: + purge: deny + clone: deny + '*': allow diff --git a/build/climc/root/opt/yunion/scripts/rbac/owner.yaml b/build/climc/root/opt/yunion/scripts/rbac/owner.yaml new file mode 100644 index 0000000000..80b25a8fa3 --- /dev/null +++ b/build/climc/root/opt/yunion/scripts/rbac/owner.yaml @@ -0,0 +1,2 @@ +policy: + '*': allow diff --git a/build/climc/root/opt/yunion/scripts/rbac/projectowner.yaml b/build/climc/root/opt/yunion/scripts/rbac/projectowner.yaml deleted file mode 100644 index c9c0990c8f..0000000000 --- a/build/climc/root/opt/yunion/scripts/rbac/projectowner.yaml +++ /dev/null @@ -1,6 +0,0 @@ -# rbac for project owner, not allow for delete -roles: - - project_owner -scope: project -policy: - '*': allow diff --git a/build/climc/root/opt/yunion/scripts/rbac/sysadmin.yaml b/build/climc/root/opt/yunion/scripts/rbac/sysadmin.yaml deleted file mode 100644 index 8749adf33e..0000000000 --- a/build/climc/root/opt/yunion/scripts/rbac/sysadmin.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# rbac for sysadmin -projects: - - system -roles: - - admin -scope: system -policy: - '*': allow diff --git a/build/climc/root/opt/yunion/scripts/rbac/viewer.yaml b/build/climc/root/opt/yunion/scripts/rbac/viewer.yaml new file mode 100644 index 0000000000..cb33f13bdc --- /dev/null +++ b/build/climc/root/opt/yunion/scripts/rbac/viewer.yaml @@ -0,0 +1,6 @@ +policy: + '*': + '*': + get: allow + list: allow + '*': deny diff --git a/cmd/climc/shell/identity/policies.go b/cmd/climc/shell/identity/policies.go index ce357e6478..991523b690 100644 --- a/cmd/climc/shell/identity/policies.go +++ b/cmd/climc/shell/identity/policies.go @@ -38,6 +38,7 @@ func init() { type PolicyListOptions struct { options.BaseListOptions Type string `help:"filter by type"` + IsSystem *bool `help:"filter by is_system" negative:"is_no_system"` Format string `help:"policy format, default to yaml" default:"yaml" choices:"yaml|json"` OrderByDomain string `help:"order by domain name" choices:"asc|desc"` } @@ -100,6 +101,7 @@ func init() { Enabled bool `help:"update policy enabled"` Disabled bool `help:"update policy disabled"` Desc string `help:"Description"` + IsSystem *bool `help:"is_system"` } updateFunc := func(s *mcclient.ClientSession, args *PolicyPatchOptions) error { policyId, err := modules.Policies.GetId(s, args.ID, nil) @@ -125,6 +127,13 @@ func init() { if len(args.Desc) > 0 { params.Add(jsonutils.NewString(args.Desc), "description") } + if args.IsSystem != nil { + if *args.IsSystem { + params.Add(jsonutils.JSONTrue, "is_system") + } else { + params.Add(jsonutils.JSONFalse, "is_system") + } + } result, err := modules.Policies.Patch(s, policyId, params) if err != nil { return err @@ -236,11 +245,12 @@ func init() { UserDomain string `help:"Domain for user"` Project string `help:"Role assignments for project"` ProjectDomain string `help:"Domain for project"` - Role []string `help:"Roles"` + Role []string `help:"Role name list"` + RoleId []string `help:"Role Id list"` } R(&PolicyAdminCapableOptions{}, "policy-admin-capable", "Check admin capable", func(s *mcclient.ClientSession, args *PolicyAdminCapableOptions) error { auth.InitFromClientSession(s) - policy.EnableGlobalRbac(15*time.Second, 15*time.Second, false) + policy.EnableGlobalRbac(15*time.Second, false) var token mcclient.TokenCredential if len(args.User) > 0 { @@ -250,6 +260,7 @@ func init() { Project: args.Project, ProjectDomain: args.ProjectDomain, Roles: strings.Join(args.Role, ","), + RoleIds: strings.Join(args.RoleId, ","), } } else { token = s.GetToken() @@ -277,6 +288,7 @@ func init() { UserDomain string `help:"Domain for user"` Project string `help:"Role assignments for project"` Role []string `help:"Roles"` + RoleId []string `help:"Role Id list"` Request []string `help:"explain request, in format of key:scope:service:resource:action:extra"` Name string `help:"policy name"` Debug bool `help:"enable RBAC debug"` @@ -292,26 +304,11 @@ func init() { rbacutils.ShowMatchRuleDebug = true } auth.InitFromClientSession(s) - policy.EnableGlobalRbac(15*time.Second, 15*time.Second, false) + policy.EnableGlobalRbac(15*time.Second, false) if args.Debug { consts.EnableRbacDebug() } - findPolicy := false - for !findPolicy { - all := policy.PolicyManager.AllPolicies() - for _, allP := range all { - if len(allP) > 0 { - findPolicy = true - break - } - } - if findPolicy { - break - } - time.Sleep(time.Second) - } - req := jsonutils.NewDict() for i := 0; i < len(args.Request); i += 1 { parts := strings.Split(args.Request[i], ":") @@ -374,6 +371,7 @@ func init() { ProjectDomain: projDom, ProjectDomainId: projDomId, Roles: strings.Join(args.Role, ","), + RoleIds: strings.Join(args.RoleId, ","), Context: mcclient.SAuthContext{ Ip: args.Ip, }, @@ -389,7 +387,7 @@ func init() { } printObject(result) - for _, r := range args.Role { + /*for _, r := range args.Role { fmt.Println("role", r, "matched policies:", policy.PolicyManager.RoleMatchPolicies(r)) } @@ -405,7 +403,7 @@ func init() { fmt.Println("matched", scope, "policies:", m) } - fmt.Println("all_policies", policy.PolicyManager.AllPolicies()) + fmt.Println("all_policies", policy.PolicyManager.AllPolicies())*/ return nil }) } diff --git a/cmd/climc/shell/identity/rolepolicies.go b/cmd/climc/shell/identity/rolepolicies.go new file mode 100644 index 0000000000..c42a3b2ca0 --- /dev/null +++ b/cmd/climc/shell/identity/rolepolicies.go @@ -0,0 +1,63 @@ +// 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 identity + +import ( + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/identity" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +func init() { + type RolePolicyListOptions struct { + api.RolePolicyListInput + } + R(&RolePolicyListOptions{}, "role-policy-list", "List associated policies of a role", func(s *mcclient.ClientSession, args *RolePolicyListOptions) error { + results, err := modules.RolePolicies.List(s, jsonutils.Marshal(args)) + if err != nil { + return err + } + printList(results, modules.RolePolicies.GetColumns(s)) + return nil + }) + + type RoleAddPolicyOptions struct { + ID string `json:"-" help:"role id or name to add policy"` + api.RolePerformAddPolicyInput + } + R(&RoleAddPolicyOptions{}, "role-add-policy", "Add policy to a role", func(s *mcclient.ClientSession, args *RoleAddPolicyOptions) error { + result, err := modules.RolesV3.PerformAction(s, args.ID, "add-policy", jsonutils.Marshal(args)) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type RoleRemovePolicyOptions struct { + ID string `json:"-" help:"role id or name to remove policy"` + api.RolePerformRemovePolicyInput + } + R(&RoleRemovePolicyOptions{}, "role-remove-policy", "Remove policy from a role", func(s *mcclient.ClientSession, args *RoleRemovePolicyOptions) error { + result, err := modules.RolesV3.PerformAction(s, args.ID, "remove-policy", jsonutils.Marshal(args)) + if err != nil { + return err + } + printObject(result) + return nil + }) +} diff --git a/pkg/apigateway/handler/auth.go b/pkg/apigateway/handler/auth.go index bc4563903b..41c6f38321 100644 --- a/pkg/apigateway/handler/auth.go +++ b/pkg/apigateway/handler/auth.go @@ -615,7 +615,7 @@ func (this *projectRoles) add(roleId, roleName string) { this.roles = append(this.roles, role{id: roleId, name: roleName}) } -func (this *projectRoles) getToken(scope rbacutils.TRbacScope, user, userId, domain, domainId string, ip string) mcclient.TokenCredential { +func (this *projectRoles) getToken(user, userId, domain, domainId string, ip string) mcclient.TokenCredential { return &mcclient.SSimpleToken{ Token: "faketoken", Domain: domain, @@ -627,11 +627,11 @@ func (this *projectRoles) getToken(scope rbacutils.TRbacScope, user, userId, dom ProjectDomain: this.domain, ProjectDomainId: this.domainId, Roles: strings.Join(this.getRoles(), ","), + RoleIds: strings.Join(this.getRoleIds(), ","), Context: mcclient.SAuthContext{ Ip: ip, }, } - // return policy.PolicyManager.IsScopeCapable(&t, scope) } func (this *projectRoles) getRoles() []string { @@ -642,43 +642,55 @@ func (this *projectRoles) getRoles() []string { return roles } -func (this *projectRoles) json(user, userId, domain, domainId string, ip string) jsonutils.JSONObject { +func (this *projectRoles) getRoleIds() []string { + roles := make([]string, 0) + for _, r := range this.roles { + roles = append(roles, r.id) + } + return roles +} + +func (this *projectRoles) json(s *mcclient.ClientSession, user, userId, domain, domainId string, ip string) (jsonutils.JSONObject, map[string][]string) { obj := jsonutils.NewDict() obj.Add(jsonutils.NewString(this.id), "id") obj.Add(jsonutils.NewString(this.name), "name") obj.Add(jsonutils.NewString(this.domain), "domain") obj.Add(jsonutils.NewString(this.domainId), "domain_id") + roleIds := make([]string, 0) roles := jsonutils.NewArray() for _, r := range this.roles { role := jsonutils.NewDict() role.Add(jsonutils.NewString(r.id), "id") role.Add(jsonutils.NewString(r.name), "name") roles.Add(role) + roleIds = append(roleIds, r.id) } obj.Add(roles, "roles") + + policies, _ := modules.RolePolicies.FetchMatchedPolicies(s, roleIds, this.id, ip) for _, scope := range []rbacutils.TRbacScope{ rbacutils.ScopeProject, rbacutils.ScopeDomain, rbacutils.ScopeSystem, } { - token := this.getToken(scope, user, userId, domain, domainId, ip) - matches := policy.PolicyManager.MatchedPolicyNames(scope, token) - obj.Add(jsonutils.NewStringArray(matches), fmt.Sprintf("%s_policies", scope)) - if len(matches) > 0 { - obj.Add(jsonutils.JSONTrue, fmt.Sprintf("%s_capable", scope)) - } else { - obj.Add(jsonutils.JSONFalse, fmt.Sprintf("%s_capable", scope)) - } - // backward compatible - if scope == rbacutils.ScopeSystem { + if matches, ok := policies[string(scope)]; ok { + obj.Add(jsonutils.NewStringArray(matches), fmt.Sprintf("%s_policies", scope)) if len(matches) > 0 { - obj.Add(jsonutils.JSONTrue, "admin_capable") + obj.Add(jsonutils.JSONTrue, fmt.Sprintf("%s_capable", scope)) } else { - obj.Add(jsonutils.JSONFalse, "admin_capable") + obj.Add(jsonutils.JSONFalse, fmt.Sprintf("%s_capable", scope)) + } + // backward compatible + if scope == rbacutils.ScopeSystem { + if len(matches) > 0 { + obj.Add(jsonutils.JSONTrue, "admin_capable") + } else { + obj.Add(jsonutils.JSONFalse, "admin_capable") + } } } } - return obj + return obj, policies } func isLBAgentExists(s *mcclient.ClientSession) (bool, error) { @@ -850,39 +862,41 @@ func getUserInfo2(s *mcclient.ClientSession, uid string, pid string, loginIp str } data.Add(jsonutils.NewStringArray(currentRoles), "roles") - + var policies map[string][]string projJson := jsonutils.NewArray() for _, proj := range projects { - projJson.Add(proj.json( + j, p := proj.json( + s, usrName, usrId, usrDomainName, usrDomainId, loginIp, - )) + ) + projJson.Add(j) + if proj.id == pid { + policies = p + } } data.Add(projJson, "projects") if len(pid) > 0 { - ident := rbacutils.NewRbacIdentity2(projDomainId, projName, currentRoles, loginIp) for _, scope := range []rbacutils.TRbacScope{ rbacutils.ScopeSystem, rbacutils.ScopeDomain, rbacutils.ScopeProject, } { - p := policy.PolicyManager.MatchedPolicyNames(scope, ident) - data.Add(jsonutils.NewStringArray(p), fmt.Sprintf("%s_policies", scope)) - if scope == rbacutils.ScopeSystem { - data.Add(jsonutils.NewStringArray(p), "admin_policies") - } else if scope == rbacutils.ScopeProject { - data.Add(jsonutils.NewStringArray(p), "policies") + if p, ok := policies[string(scope)]; ok { + data.Add(jsonutils.NewStringArray(p), fmt.Sprintf("%s_policies", scope)) + if scope == rbacutils.ScopeSystem { + data.Add(jsonutils.NewStringArray(p), "admin_policies") + } else if scope == rbacutils.ScopeProject { + data.Add(jsonutils.NewStringArray(p), "policies") + } } } } - allPolicies := policy.PolicyManager.AllPolicies() - data.Add(jsonutils.Marshal(allPolicies), "all_policies") - services := jsonutils.NewArray() menus := jsonutils.NewArray() k8s := jsonutils.NewArray() diff --git a/pkg/apigateway/policy/policy.go b/pkg/apigateway/policy/policy.go index db11e4c458..26b03a5371 100644 --- a/pkg/apigateway/policy/policy.go +++ b/pkg/apigateway/policy/policy.go @@ -17,44 +17,27 @@ package policy import ( "yunion.io/x/jsonutils" - "yunion.io/x/onecloud/pkg/cloudcommon/policy" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/modules" ) func PolicyCreate(s *mcclient.ClientSession, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { - /*params := jsonutils.NewDict() - params.Add(jsonutils.NewString(args.TYPE), "type") - params.Add(jsonutils.NewString(policy.String()), "policy") - */ result, err := modules.Policies.Create(s, params) if err != nil { return nil, err } - policy.PolicyManager.SyncOnce() return result, nil } func PolicyPatch(s *mcclient.ClientSession, idstr string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { - /*params := jsonutils.NewDict() - if len(name) > 0 { - params.Add(jsonutils.NewString(name), "type") - } - if policy != nil { - params.Add(jsonutils.NewString(policy.String()), "policy") - }*/ result, err := modules.Policies.Patch(s, idstr, params) if err != nil { return nil, err } - policy.PolicyManager.SyncOnce() return result, nil } func PolicyDelete(s *mcclient.ClientSession, idstr string) error { _, err := modules.Policies.Delete(s, idstr, nil) - if err != nil { - policy.PolicyManager.SyncOnce() - } return err } diff --git a/pkg/apis/identity/assignments.go b/pkg/apis/identity/assignments.go index b75b209199..f2c61aa5ee 100644 --- a/pkg/apis/identity/assignments.go +++ b/pkg/apis/identity/assignments.go @@ -14,6 +14,8 @@ package identity +import "yunion.io/x/onecloud/pkg/util/rbacutils" + type SIdentityObject struct { Id string `json:"id"` Name string `json:"name"` @@ -47,16 +49,13 @@ type SRoleAssignment struct { } // rbacutils.IRbacIdentity interfaces -func (ra *SRoleAssignment) GetProjectDomainId() string { - return ra.Scope.Project.Domain.Id + +func (ra *SRoleAssignment) GetProjectId() string { + return ra.Scope.Project.Id } -func (ra *SRoleAssignment) GetProjectName() string { - return ra.Scope.Project.Name -} - -func (ra *SRoleAssignment) GetRoles() []string { - return []string{ra.Role.Name} +func (ra *SRoleAssignment) GetRoleIds() []string { + return []string{ra.Role.Id} } func (ra *SRoleAssignment) GetLoginIp() string { @@ -64,7 +63,7 @@ func (ra *SRoleAssignment) GetLoginIp() string { } func (ra *SRoleAssignment) GetTokenString() string { - return "faketoken" + return rbacutils.FAKE_TOKEN } type RAInputObject struct { diff --git a/pkg/apis/identity/input.go b/pkg/apis/identity/input.go index 4a9ca72caa..3fe9528091 100644 --- a/pkg/apis/identity/input.go +++ b/pkg/apis/identity/input.go @@ -19,6 +19,7 @@ import ( "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/apis" + "yunion.io/x/onecloud/pkg/util/rbacutils" ) type IdentityBaseResourceCreateInput struct { @@ -336,6 +337,9 @@ type PolicyListInput struct { // 以类型查询 Type []string `json:"type"` + + // 是否显示系统权限 + IsSystem *bool `json:"is_system"` } type RegionFilterListInput struct { @@ -397,9 +401,18 @@ type IdentityProviderUpdateInput struct { type PolicyUpdateInput struct { EnabledIdentityBaseUpdateInput + // Deprecated + // swagger:ignore Type string `json:"type"` + // Policy内容 Blob jsonutils.JSONObject `json:"blob"` + + // 生效范围,project|domain|system + Scope rbacutils.TRbacScope `json:"scope"` + + // 是否为系统权限 + IsSystem *bool `json:"is_system"` } type ProjectUpdateInput struct { @@ -475,9 +488,18 @@ type PolicyCreateInput struct { EnabledIdentityBaseResourceCreateInput apis.SharableResourceBaseCreateInput + // Deprecated + // swagger:ignore Type string `json:"type"` + // policy Blob jsonutils.JSONObject `json:"blob"` + + // 生效范围,project|domain|system + Scope rbacutils.TRbacScope `json:"scope"` + + // 是否为系统权限 + IsSystem *bool `json:"is_system"` } type RoleCreateInput struct { diff --git a/pkg/apis/identity/role.go b/pkg/apis/identity/role.go index 69f2fa4894..c920d357a0 100644 --- a/pkg/apis/identity/role.go +++ b/pkg/apis/identity/role.go @@ -14,7 +14,10 @@ package identity -import "yunion.io/x/onecloud/pkg/apis" +import ( + "yunion.io/x/onecloud/pkg/apis" + "yunion.io/x/onecloud/pkg/util/rbacutils" +) type RoleDetails struct { IdentityBaseResourceDetails @@ -22,8 +25,11 @@ type RoleDetails struct { SRole - UserCount int `json:"user_count"` - GroupCount int `json:"group_count"` - ProjectCount int `json:"project_count"` + UserCount int `json:"user_count"` + GroupCount int `json:"group_count"` + ProjectCount int `json:"project_count"` + MatchPolicies []string `json:"match_policies"` + + Policies map[rbacutils.TRbacScope][]string `json:"policies"` } diff --git a/pkg/apis/identity/rolepolicy.go b/pkg/apis/identity/rolepolicy.go new file mode 100644 index 0000000000..ae87a28a6f --- /dev/null +++ b/pkg/apis/identity/rolepolicy.go @@ -0,0 +1,65 @@ +// 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 identity + +import ( + "yunion.io/x/onecloud/pkg/apis" + "yunion.io/x/onecloud/pkg/util/rbacutils" +) + +type RolePolicyListInput struct { + apis.ResourceBaseListInput + + RoleIds []string `json:"role_ids"` + + ProjectId string `json:"project_id"` + + PolicyId string `json:"policy_id"` + + Auth *bool `json:"auth"` +} + +type RolePolicyDetails struct { + apis.ResourceBaseDetails + + Id string `json:"id"` + + Name string `json:"name"` + + Role string `json:"role"` + + Project string `json:"project"` + + Policy string `json:"policy"` + + Scope rbacutils.TRbacScope `json:"scope"` + + Description string `json:"description"` +} + +type RolePerformSetPoliciesInput struct { + Policies []RolePerformAddPolicyInput `json:"policies"` +} + +type RolePerformAddPolicyInput struct { + PolicyId string `json:"policy_id"` + ProjectId string `json:"project_id"` + Ips []string `json:"ips"` +} + +type RolePerformRemovePolicyInput struct { + PolicyId string `json:"policy_id"` + ProjectId string `json:"project_id"` +} diff --git a/pkg/cloudcommon/app/auth.go b/pkg/cloudcommon/app/auth.go index 53caef1319..de17d7dcc8 100644 --- a/pkg/cloudcommon/app/auth.go +++ b/pkg/cloudcommon/app/auth.go @@ -95,7 +95,6 @@ func InitBaseAuth(options *common_options.BaseOptions) { if options.EnableRbac { policy.EnableGlobalRbac( time.Second*time.Duration(options.RbacPolicySyncPeriodSeconds), - time.Second*time.Duration(options.RbacPolicySyncFailedRetrySeconds), options.RbacDebug, ) } diff --git a/pkg/cloudcommon/options/manager.go b/pkg/cloudcommon/options/manager.go index 9a745b826c..bd691b7b88 100644 --- a/pkg/cloudcommon/options/manager.go +++ b/pkg/cloudcommon/options/manager.go @@ -116,7 +116,7 @@ func optionsEquals(newOpts interface{}, oldOpts interface{}) bool { func (manager *SOptionManager) DoSync(first bool) (time.Duration, error) { newOpts := manager.newOptions() copyOptions(newOpts, manager.options) - merged := manager.session.Merge(newOpts, manager.serviceType, manager.serviceVersion) + merged := manager.session.Merge(newOpts, manager.serviceType, manager.serviceVersion, first) if merged && !optionsEquals(newOpts, manager.options) { if manager.onOptionsChange != nil && manager.onOptionsChange(manager.options, newOpts) && !first { @@ -124,7 +124,7 @@ func (manager *SOptionManager) DoSync(first bool) (time.Duration, error) { appsrv.SetExitFlag() } copyOptions(manager.options, newOpts) - manager.session.Upload() + manager.session.Upload(first) } return manager.refreshInterval, nil } diff --git a/pkg/cloudcommon/options/mergeconf.go b/pkg/cloudcommon/options/mergeconf.go index befe02e72c..fb3fbe44c3 100644 --- a/pkg/cloudcommon/options/mergeconf.go +++ b/pkg/cloudcommon/options/mergeconf.go @@ -57,8 +57,8 @@ func getServiceConfig(s *mcclient.ClientSession, serviceId string) (jsonutils.JS } type IServiceConfigSession interface { - Merge(opts interface{}, serviceType string, serviceVersion string) bool - Upload() + Merge(opts interface{}, serviceType string, serviceVersion string, isFirst bool) bool + Upload(isFirst bool) IsRemote() bool } @@ -72,7 +72,7 @@ func newServiceConfigSession() IServiceConfigSession { return &mcclientServiceConfigSession{} } -func (s *mcclientServiceConfigSession) Merge(opts interface{}, serviceType string, serviceVersion string) bool { +func (s *mcclientServiceConfigSession) Merge(opts interface{}, serviceType string, serviceVersion string, isFirst bool) bool { merged := false s.config = jsonutils.Marshal(opts).(*jsonutils.JSONDict) region, _ := s.config.GetString("region") @@ -88,7 +88,7 @@ func (s *mcclientServiceConfigSession) Merge(opts interface{}, serviceType strin merged = true } else { // not initialized - s.Upload() + s.Upload(isFirst) } } commonServiceId, _ := getServiceIdByType(s.session, consts.COMMON_SERVICE, "") @@ -113,7 +113,7 @@ func (s *mcclientServiceConfigSession) Merge(opts interface{}, serviceType strin return false } -func (s *mcclientServiceConfigSession) Upload() { +func (s *mcclientServiceConfigSession) Upload(isFirst bool) { // upload service config if len(s.serviceId) > 0 { nconf := jsonutils.NewDict() diff --git a/pkg/cloudcommon/options/options.go b/pkg/cloudcommon/options/options.go index 782b538150..1bb876eb64 100644 --- a/pkg/cloudcommon/options/options.go +++ b/pkg/cloudcommon/options/options.go @@ -68,10 +68,10 @@ type BaseOptions struct { NotifyAdminUsers []string `default:"sysadmin" help:"System administrator user ID or name to notify system events, if domain is not default, specify domain as prefix ending with double backslash, e.g. domain\\\\user"` NotifyAdminGroups []string `help:"System administrator group ID or name to notify system events, if domain is not default, specify domain as prefix ending with double backslash, e.g. domain\\\\group"` - EnableRbac bool `help:"Switch on Role-based Access Control" default:"true"` - RbacDebug bool `help:"turn on rbac debug log" default:"false"` - RbacPolicySyncPeriodSeconds int `help:"policy sync interval in seconds, default 30 minutes" default:"1800"` - RbacPolicySyncFailedRetrySeconds int `help:"seconds to wait after a failed sync, default 30 seconds" default:"30"` + EnableRbac bool `help:"Switch on Role-based Access Control" default:"true"` + RbacDebug bool `help:"turn on rbac debug log" default:"false"` + RbacPolicySyncPeriodSeconds int `help:"policy sync interval in seconds, default 30 minutes" default:"1800"` + // RbacPolicySyncFailedRetrySeconds int `help:"seconds to wait after a failed sync, default 30 seconds" default:"30"` ConfigSyncPeriodSeconds int `help:"service config sync interval in seconds, default 30 minutes" default:"1800"` diff --git a/pkg/cloudcommon/policy/global.go b/pkg/cloudcommon/policy/global.go index 93bc1f28f4..a8b7fb0408 100644 --- a/pkg/cloudcommon/policy/global.go +++ b/pkg/cloudcommon/policy/global.go @@ -20,12 +20,12 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/consts" ) -func EnableGlobalRbac(refreshInterval time.Duration, retryInterval time.Duration, debug bool) { +func EnableGlobalRbac(refreshInterval time.Duration, debug bool) { if !consts.IsRbacEnabled() { consts.EnableRbac() if debug { consts.EnableRbacDebug() } - PolicyManager.start(refreshInterval, retryInterval) + PolicyManager.init(refreshInterval) } } diff --git a/pkg/cloudcommon/policy/policy.go b/pkg/cloudcommon/policy/policy.go index 79894ddba0..46c0354bb5 100644 --- a/pkg/cloudcommon/policy/policy.go +++ b/pkg/cloudcommon/policy/policy.go @@ -25,16 +25,17 @@ import ( "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/onecloud/pkg/apis" + "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/cloudcommon/consts" - "yunion.io/x/onecloud/pkg/cloudcommon/syncman/watcher" "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" "yunion.io/x/onecloud/pkg/util/hashcache" - "yunion.io/x/onecloud/pkg/util/nopanic" "yunion.io/x/onecloud/pkg/util/rbacutils" ) @@ -50,7 +51,7 @@ const ( PolicyActionPerform = rbacutils.ActionPerform ) -type PolicyFetchFunc func(ctx context.Context) (map[rbacutils.TRbacScope][]rbacutils.SPolicyInfo, error) +type PolicyFetchFunc func(ctx context.Context, token mcclient.TokenCredential) (*mcclient.SFetchMatchPoliciesOutput, error) var ( PolicyManager *SPolicyManager @@ -61,35 +62,39 @@ func init() { PolicyManager = &SPolicyManager{ lock: &sync.Mutex{}, } - DefaultPolicyFetcher = remotePolicyFetcher + DefaultPolicyFetcher = auth.FetchMatchPolicies } type SPolicyManager struct { - watcher.SInformerSyncManager - - // policies map[rbacutils.TRbacScope]map[string]*rbacutils.SRbacPolicy - policies map[rbacutils.TRbacScope][]rbacutils.SPolicyInfo defaultPolicies map[rbacutils.TRbacScope][]*rbacutils.SRbacPolicy - failedRetryInterval time.Duration - refreshInterval time.Duration + refreshInterval time.Duration - cache *hashcache.Cache // policy cache + policyCache *hashcache.Cache // policy cache + permissionCache *hashcache.Cache // permission cache + + fetchWorker *appsrv.SWorkerManager lock *sync.Mutex } type sPolicyData struct { Id string `json:"id"` - Type string `json:"type"` + Name string `json:"name"` Enabled bool `json:"enabled"` DomainId string `json:"domain_id"` IsPublic bool `json:"is_public"` - PublicScope string `json:"public_scope"` + PublicScope rbacutils.TRbacScope `json:"public_scope"` SharedDomains []apis.SharedDomain `json:"shared_domain"` + Scope rbacutils.TRbacScope `json:"scope"` Policy jsonutils.JSONObject `json:"policy"` } +func (data sPolicyData) getPolicy() (rbacutils.TPolicy, error) { + return rbacutils.DecodePolicy(data.Policy) +} + +/* func parseJsonPolicy(obj jsonutils.JSONObject, enabled bool) (rbacutils.SPolicyInfo, error) { sp := rbacutils.SPolicyInfo{} pData := sPolicyData{} @@ -100,35 +105,33 @@ func parseJsonPolicy(obj jsonutils.JSONObject, enabled bool) (rbacutils.SPolicyI if enabled && !pData.Enabled { return sp, errors.Wrap(httperrors.ErrInvalidFormat, "not enabled") } - if len(pData.Type) == 0 { - return sp, errors.Wrap(httperrors.ErrInvalidFormat, "missing type") + if len(pData.Name) == 0 { + return sp, errors.Wrap(httperrors.ErrInvalidFormat, "missing name") } if pData.Policy == nil { return sp, errors.Wrap(httperrors.ErrInvalidFormat, "missing policy") } - policy := rbacutils.SRbacPolicy{} + policy := rbacutils.SRbacPolicyCore{} err = policy.Decode(pData.Policy) if err != nil { log.Errorf("policy decode error %s", err) return sp, errors.Wrap(err, "policy.Decode") } - policy.DomainId = pData.DomainId - policy.IsPublic = pData.IsPublic - policy.PublicScope = rbacutils.String2ScopeDefault(pData.PublicScope, rbacutils.ScopeSystem) - policy.SharedDomainIds = make([]string, len(pData.SharedDomains)) + sp.SharedDomainIds = make([]string, len(pData.SharedDomains)) for i := range pData.SharedDomains { - policy.SharedDomainIds[i] = pData.SharedDomains[i].Id + sp.SharedDomainIds[i] = pData.SharedDomains[i].Id } sp.Id = pData.Id - sp.Name = pData.Type + sp.Name = pData.Name sp.Policy = &policy return sp, nil -} +}*/ +/* func remotePolicyFetcher(ctx context.Context) (map[rbacutils.TRbacScope][]rbacutils.SPolicyInfo, error) { s := auth.GetAdminSession(ctx, consts.GetRegion(), "v1") @@ -153,10 +156,10 @@ func remotePolicyFetcher(ctx context.Context) (map[rbacutils.TRbacScope][]rbacut continue } - if _, ok := policies[sp.Policy.Scope]; !ok { - policies[sp.Policy.Scope] = make([]rbacutils.SPolicyInfo, 0) + if _, ok := policies[sp.Scope]; !ok { + policies[sp.Scope] = make([]rbacutils.SPolicyInfo, 0) } - policies[sp.Policy.Scope] = append(policies[sp.Policy.Scope], sp) + policies[sp.Scope] = append(policies[sp.Scope], sp) } offset += len(result.Data) @@ -166,12 +169,11 @@ func remotePolicyFetcher(ctx context.Context) (map[rbacutils.TRbacScope][]rbacut } return policies, nil } +*/ -func (manager *SPolicyManager) start(refreshInterval time.Duration, retryInterval time.Duration) { - log.Infof("PolicyManager start to fetch policies ...") - manager.failedRetryInterval = retryInterval +func (manager *SPolicyManager) init(refreshInterval time.Duration) { manager.refreshInterval = refreshInterval - manager.InitSync(manager) + // manager.InitSync(manager) if len(predefinedDefaultPolicies) > 0 { policiesMap := make(map[rbacutils.TRbacScope][]*rbacutils.SRbacPolicy) for i := range predefinedDefaultPolicies { @@ -184,25 +186,26 @@ func (manager *SPolicyManager) start(refreshInterval time.Duration, retryInterva policiesMap[policy.Scope] = policies } manager.defaultPolicies = policiesMap - // log.Debugf("%#v", manager.defaultPolicies) } - manager.cache = hashcache.NewCache(2048, refreshInterval/2) + manager.policyCache = hashcache.NewCache(2048, refreshInterval) + manager.permissionCache = hashcache.NewCache(2048, refreshInterval) defaultFetcherFuncAddr := reflect.ValueOf(DefaultPolicyFetcher).Pointer() - remoteFetcherFuncAddr := reflect.ValueOf(remotePolicyFetcher).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 - manager.StartWatching(&modules.Policies) + isDB = false + } else { + isDB = true } - err := manager.FirstSync() - if err != nil { - log.Errorf("PolicyManager first sync fail %s", err) - } + manager.fetchWorker = appsrv.NewWorkerManager("policyFetchWorker", 1, 2048, isDB) } +/* func (manager *SPolicyManager) DoSync(first bool) (time.Duration, error) { var err error @@ -236,15 +239,33 @@ func (manager *SPolicyManager) NeedSync(dat *jsonutils.JSONDict) bool { func (manager *SPolicyManager) Name() string { return "PolicyManager" } +*/ -func queryKey(scope rbacutils.TRbacScope, userCred mcclient.TokenCredential, service string, resource string, action string, extra ...string) string { +func getMaskedLoginIp(userCred mcclient.TokenCredential) string { + loginIp, _ := netutils.NewIPV4Addr(userCred.GetLoginIp()) + return loginIp.NetAddr(16).String() +} + +func policyKey(userCred mcclient.TokenCredential) string { + 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 rbacutils.TRbacScope, userCred mcclient.TokenCredential, service string, resource string, action string, extra ...string) string { queryKeys := []string{string(scope)} - queryKeys = append(queryKeys, userCred.GetProjectId(), userCred.GetDomainId(), userCred.GetUserId()) - roles := userCred.GetRoles() + 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 } @@ -311,25 +332,63 @@ func (manager *SPolicyManager) Allow(targetScope rbacutils.TRbacScope, userCred return rbacutils.Deny } -func (manager *SPolicyManager) allow(scope rbacutils.TRbacScope, userCred mcclient.TokenCredential, service string, resource string, action string, extra ...string) rbacutils.TRbacResult { - if manager.cache != nil && userCred != nil { - key := queryKey(scope, userCred, service, resource, action, extra...) - val := manager.cache.Get(key) - if val != nil { - if consts.IsRbacDebug() { - log.Debugf("query %s:%s:%s:%s from cache %s", service, resource, action, extra, val) - } - return val.(rbacutils.TRbacResult) - } - result := manager.allowWithoutCache(scope, userCred, service, resource, action, extra...) - manager.cache.Set(key, result) - return result - } else { - return manager.allowWithoutCache(scope, userCred, service, resource, action, extra...) +func (manager *SPolicyManager) fetchMatchedPolicies(userCred mcclient.TokenCredential) (*mcclient.SFetchMatchPoliciesOutput, error) { + key := policyKey(userCred) + + type fetchResult struct { + output *mcclient.SFetchMatchPoliciesOutput + err error } + resChan := make(chan fetchResult) + manager.fetchWorker.Run(func() { + val := manager.policyCache.Get(key) + result := fetchResult{} + if gotypes.IsNil(val) { + pg, err := DefaultPolicyFetcher(context.Background(), userCred) + if err != nil { + result.err = errors.Wrap(err, "DefaultPolicyFetcher") + } else { + manager.policyCache.Set(key, pg) + result.output = pg + } + } else { + result.output = val.(*mcclient.SFetchMatchPoliciesOutput) + } + resChan <- result + }, nil, nil) + + res := <-resChan + return res.output, res.err } -func (manager *SPolicyManager) findPolicyByName(scope rbacutils.TRbacScope, name string) *rbacutils.SRbacPolicy { +func (manager *SPolicyManager) allow(scope rbacutils.TRbacScope, userCred mcclient.TokenCredential, service string, resource string, action string, extra ...string) rbacutils.TRbacResult { + // first download userCred policy + policies, err := manager.fetchMatchedPolicies(userCred) + if err != nil { + log.Errorf("fetchMatchedPolicyGroup fail %s", err) + return rbacutils.Deny + } + // 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.TRbacResult) + } + + policySet, ok := policies.Policies[scope] + if !ok { + policySet = rbacutils.TPolicySet{} + } + result := manager.allowWithoutCache(policySet, scope, userCred, service, resource, action, extra...) + manager.permissionCache.Set(key, result) + return result +} + +/* +func (manager *SPolicyManager) findPolicyByName(scope rbacutils.TRbacScope, name string) *rbacutils.SRbacPolicyCore { if policies, ok := manager.policies[scope]; ok { for i := range policies { if policies[i].Id == name || policies[i].Name == name { @@ -352,15 +411,15 @@ func getMatchedPolicyRules(policies []rbacutils.SPolicyInfo, userCred rbacutils. } return matchPolicies.GetMatchRules(service, resource, action, extra...), true } +*/ -func (manager *SPolicyManager) allowWithoutCache(scope rbacutils.TRbacScope, userCred mcclient.TokenCredential, service string, resource string, action string, extra ...string) rbacutils.TRbacResult { +func (manager *SPolicyManager) allowWithoutCache(policies rbacutils.TPolicySet, scope rbacutils.TRbacScope, userCred mcclient.TokenCredential, service string, resource string, action string, extra ...string) rbacutils.TRbacResult { matchRules := make([]rbacutils.SRbacRule, 0) findMatchPolicy := false - policies, ok := manager.policies[scope] - if !ok { + if len(policies) == 0 { log.Warningf("no policies fetched for scope %s", scope) } else { - matchRules, findMatchPolicy = getMatchedPolicyRules(policies, userCred, service, resource, action, extra...) + matchRules = policies.GetMatchRules(service, resource, action, extra...) } scopedDeny := false @@ -397,7 +456,7 @@ func (manager *SPolicyManager) allowWithoutCache(scope rbacutils.TRbacScope, use if !isMatched { continue } - rule := defaultPolicies[i].GetMatchRule(service, resource, action, extra...) + rule := defaultPolicies[i].Rules.GetMatchRule(service, resource, action, extra...) if rule != nil { matchRules = append(matchRules, *rule) } @@ -432,21 +491,26 @@ func (manager *SPolicyManager) allowWithoutCache(scope rbacutils.TRbacScope, use // result: allow/deny for the named policy // userResult: allow/deny for the matched policies of userCred // -func explainPolicy(ctx context.Context, userCred mcclient.TokenCredential, policyReq jsonutils.JSONObject, name string) ([]string, rbacutils.TRbacResult, rbacutils.TRbacResult, error) { - _, request, result, userResult, err := explainPolicyInternal(ctx, userCred, policyReq, name) +func explainPolicy(userCred mcclient.TokenCredential, policyReq jsonutils.JSONObject, policyData *sPolicyData) ([]string, rbacutils.TRbacResult, rbacutils.TRbacResult, error) { + _, request, result, userResult, err := explainPolicyInternal(userCred, policyReq, policyData) return request, result, userResult, err } -func fetchPolicyByIdOrName(ctx context.Context, id string) (rbacutils.SPolicyInfo, error) { +func fetchPolicyDataByIdOrName(ctx context.Context, id string) (*sPolicyData, error) { s := auth.GetAdminSession(ctx, consts.GetRegion(), "v1") data, err := modules.Policies.Get(s, id, nil) if err != nil { - return rbacutils.SPolicyInfo{}, errors.Wrap(err, "modules.Policies.Get") + return nil, errors.Wrap(err, "modules.Policies.Get") } - return parseJsonPolicy(data, false) + pdata := &sPolicyData{} + err = data.Unmarshal(&pdata) + if err != nil { + return nil, errors.Wrap(err, "Unmarshal Policy Data") + } + return pdata, nil } -func explainPolicyInternal(ctx context.Context, userCred mcclient.TokenCredential, policyReq jsonutils.JSONObject, name string) (rbacutils.TRbacScope, []string, rbacutils.TRbacResult, rbacutils.TRbacResult, error) { +func explainPolicyInternal(userCred mcclient.TokenCredential, policyReq jsonutils.JSONObject, policyData *sPolicyData) (rbacutils.TRbacScope, []string, rbacutils.TRbacResult, rbacutils.TRbacResult, error) { policySeq, err := policyReq.GetArray() if err != nil { return rbacutils.ScopeSystem, nil, rbacutils.Deny, rbacutils.Deny, httperrors.NewInputParameterError("invalid format") @@ -489,21 +553,19 @@ func explainPolicyInternal(ctx context.Context, userCred mcclient.TokenCredentia userResult := PolicyManager.Allow(scope, userCred, service, resource, action, extra...) result := userResult - if len(name) > 0 { - policy := PolicyManager.findPolicyByName(scope, name) - if policy == nil { - // policy not found locally, remote fetch - sp, err := fetchPolicyByIdOrName(ctx, name) + if policyData != nil { + if scope.HigherThan(policyData.Scope) { + result = rbacutils.Deny + } else { + policy, err := policyData.getPolicy() if err != nil { - return scope, reqStrs, rbacutils.Deny, rbacutils.Deny, httperrors.NewNotFoundError("policy %s not found: %s", name, err) + return scope, reqStrs, rbacutils.Deny, userResult, errors.Wrap(err, "getPolicy") + } + rule := policy.GetMatchRule(service, resource, action, extra...) + result = rbacutils.Deny + if rule != nil { + result = rule.Result } - policy = sp.Policy - } - - rule := policy.GetMatchRule(service, resource, action, extra...) - result = rbacutils.Deny - if rule != nil { - result = rule.Result } } @@ -515,9 +577,16 @@ func ExplainRpc(ctx context.Context, userCred mcclient.TokenCredential, params j 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(ctx, userCred, policyReq, name) + reqStrs, result, userResult, err := explainPolicy(userCred, policyReq, policyData) if err != nil { return nil, err } @@ -541,24 +610,36 @@ func (manager *SPolicyManager) IsScopeCapable(userCred mcclient.TokenCredential, return false } - if policies, ok := manager.policies[scope]; ok { - pnames := getMatchedPolicyNames(policies, userCred) - if len(pnames) > 0 { - return true - } + 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(scope rbacutils.TRbacScope, userCred rbacutils.IRbacIdentity) []string { +/* +func (manager *SPolicyManager) MatchedPolicyNames(ctx context.Context, scope rbacutils.TRbacScope, ident rbacutils.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 { @@ -597,3 +678,4 @@ func (manager *SPolicyManager) GetMatchedPolicySet(userCred rbacutils.IRbacIdent } return rbacutils.ScopeNone, nil } +*/ diff --git a/pkg/keystone/models/assignments.go b/pkg/keystone/models/assignments.go index dc0ed5cf8e..23a0e3e4d2 100644 --- a/pkg/keystone/models/assignments.go +++ b/pkg/keystone/models/assignments.go @@ -28,7 +28,6 @@ import ( api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/cloudcommon/db" - "yunion.io/x/onecloud/pkg/cloudcommon/policy" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" @@ -589,9 +588,10 @@ func (manager *SAssignmentManager) queryAll( } func fetchRoleAssignmentPolicies(ra *api.SRoleAssignment) { - ra.Policies.Project = policy.PolicyManager.MatchedPolicyNames(rbacutils.ScopeProject, ra) - ra.Policies.Domain = policy.PolicyManager.MatchedPolicyNames(rbacutils.ScopeDomain, ra) - ra.Policies.System = policy.PolicyManager.MatchedPolicyNames(rbacutils.ScopeSystem, ra) + policyNames, _, _ := RolePolicyManager.GetMatchPolicyGroup(ra, true) + ra.Policies.Project, _ = policyNames[rbacutils.ScopeProject] + ra.Policies.Domain, _ = policyNames[rbacutils.ScopeDomain] + ra.Policies.System, _ = policyNames[rbacutils.ScopeSystem] } type sAssignmentInternal struct { diff --git a/pkg/keystone/models/configs.go b/pkg/keystone/models/configs.go index 0b1b5c21bc..521dabd96d 100644 --- a/pkg/keystone/models/configs.go +++ b/pkg/keystone/models/configs.go @@ -367,7 +367,7 @@ func GetConfigs(model db.IModel, sensitive bool, whiteList, blackList map[string return config2map(opts), nil } -func saveConfigs(userCred mcclient.TokenCredential, action string, model db.IModel, opts api.TConfigs, whiteList map[string][]string, blackList map[string][]string, sensitiveConfs map[string][]string) error { +func saveConfigs(userCred mcclient.TokenCredential, action string, model db.IModel, opts api.TConfigs, whiteList map[string][]string, blackList map[string][]string, sensitiveConfs map[string][]string, skipLog bool) error { var err error changed := make([]sChangeOption, 0) changedSensitive := make([]sChangeOption, 0) @@ -417,7 +417,9 @@ func saveConfigs(userCred mcclient.TokenCredential, action string, model db.IMod if len(changed) > 0 { notes := jsonutils.Marshal(changed) db.OpsLog.LogEvent(model, db.ACT_CHANGE_CONFIG, notes, userCred) - logclient.AddSimpleActionLog(model, logclient.ACT_CHANGE_CONFIG, notes, userCred, true) + if !skipLog { + logclient.AddSimpleActionLog(model, logclient.ACT_CHANGE_CONFIG, notes, userCred, true) + } } return nil } @@ -431,7 +433,7 @@ func NewServiceConfigSession() common_options.IServiceConfigSession { return &dbServiceConfigSession{} } -func (s *dbServiceConfigSession) Merge(opts interface{}, serviceType string, serviceVersion string) bool { +func (s *dbServiceConfigSession) Merge(opts interface{}, serviceType string, serviceVersion string, isFirst bool) bool { merged := false s.config = jsonutils.Marshal(opts).(*jsonutils.JSONDict) s.service, _ = ServiceManager.fetchServiceByType(serviceType) @@ -445,7 +447,7 @@ func (s *dbServiceConfigSession) Merge(opts interface{}, serviceType string, ser merged = true } else { // not initialized - uploadConfig(s.service, s.config) + uploadConfig(s.service, s.config, isFirst) } } commonService, _ := ServiceManager.fetchServiceByType(consts.COMMON_SERVICE) @@ -459,7 +461,7 @@ func (s *dbServiceConfigSession) Merge(opts interface{}, serviceType string, ser merged = true } else { // common not initialized - uploadConfig(commonService, s.config) + uploadConfig(commonService, s.config, isFirst) } } if merged { @@ -472,18 +474,18 @@ func (s *dbServiceConfigSession) Merge(opts interface{}, serviceType string, ser return false } -func (s *dbServiceConfigSession) Upload() { +func (s *dbServiceConfigSession) Upload(isFirst bool) { if s.service == nil { return } - uploadConfig(s.service, s.config) + uploadConfig(s.service, s.config, isFirst) } func (s *dbServiceConfigSession) IsRemote() bool { return false } -func uploadConfig(service *SService, config jsonutils.JSONObject) { +func uploadConfig(service *SService, config jsonutils.JSONObject, isFirst bool) { nconf := jsonutils.NewDict() nconf.Add(config, "default") tconf := api.TConfigs{} @@ -493,9 +495,9 @@ func uploadConfig(service *SService, config jsonutils.JSONObject) { return } if service.isCommonService() { - err = saveConfigs(nil, "", service, tconf, api.CommonWhitelistOptionMap, nil, nil) + err = saveConfigs(nil, "", service, tconf, api.CommonWhitelistOptionMap, nil, nil, isFirst) } else { - err = saveConfigs(nil, "", service, tconf, nil, api.ServiceBlacklistOptionMap, nil) + err = saveConfigs(nil, "", service, tconf, nil, api.ServiceBlacklistOptionMap, nil, isFirst) } if err != nil { log.Errorf("saveConfigs fail %s", err) diff --git a/pkg/keystone/models/default_admin.go b/pkg/keystone/models/default_admin.go index 33cb3e8656..f632122c25 100644 --- a/pkg/keystone/models/default_admin.go +++ b/pkg/keystone/models/default_admin.go @@ -65,7 +65,9 @@ func getDefaultAdminCred() mcclient.TokenCredential { token.Project = prj.Name token.ProjectDomainId = prj.DomainId token.ProjectDomain = prj.GetDomain().Name - token.Roles = api.SystemAdminRole + rol, _ := RoleManager.FetchRole("", api.SystemAdminRole, api.DEFAULT_DOMAIN_ID, "") + token.Roles = rol.Name + token.RoleIds = rol.Id return &token } diff --git a/pkg/keystone/models/identity_provider.go b/pkg/keystone/models/identity_provider.go index 40c3f30371..1e99b50c71 100644 --- a/pkg/keystone/models/identity_provider.go +++ b/pkg/keystone/models/identity_provider.go @@ -340,7 +340,7 @@ func (ident *SIdentityProvider) PerformConfig(ctx context.Context, userCred mccl opts := input.Config action := input.Action - err = saveConfigs(userCred, action, ident, opts, nil, nil, api.SensitiveDomainConfigMap) + err = saveConfigs(userCred, action, ident, opts, nil, nil, api.SensitiveDomainConfigMap, false) if err != nil { return nil, httperrors.NewInternalServerError("saveConfig fail %s", err) } @@ -492,7 +492,7 @@ func (ident *SIdentityProvider) PostCreate(ctx context.Context, userCred mcclien log.Errorf("parse config error %s", err) return } - err = saveConfigs(userCred, "", ident, opts, nil, nil, api.SensitiveDomainConfigMap) + err = saveConfigs(userCred, "", ident, opts, nil, nil, api.SensitiveDomainConfigMap, false) if err != nil { log.Errorf("saveConfig fail %s", err) return diff --git a/pkg/keystone/models/policies.go b/pkg/keystone/models/policies.go index 5e07d4a957..aaab43e6d4 100644 --- a/pkg/keystone/models/policies.go +++ b/pkg/keystone/models/policies.go @@ -22,6 +22,7 @@ import ( "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/gotypes" + "yunion.io/x/pkg/tristate" "yunion.io/x/sqlchemy" "yunion.io/x/onecloud/pkg/apis" @@ -69,8 +70,18 @@ type SPolicy struct { SEnabledIdentityBaseResource db.SSharableBaseResource `"is_public=>create":"domain_optional" "public_scope=>create":"domain_optional"` - Type string `width:"255" charset:"utf8" nullable:"false" list:"user" create:"domain_required" update:"domain"` + // swagger:ignore + // Deprecated + Type string `width:"255" charset:"utf8" nullable:"false" list:"user" create:"domain_required" update:"domain"` + + // 权限定义 Blob jsonutils.JSONObject `nullable:"false" list:"user" create:"domain_required" update:"domain"` + + // 权限范围 + Scope rbacutils.TRbacScope `nullable:"true" list:"user" create:"domain_required" update:"domain"` + + // 是否为系统权限 + IsSystem tristate.TriState `nullable:"false" default:"false" list:"domain" update:"admin" create:"admin_optional"` } func (manager *SPolicyManager) InitializeData() error { @@ -91,6 +102,113 @@ func (manager *SPolicyManager) InitializeData() error { return nil }) } + + err = manager.initializeRolePolicyGroup() + if err != nil { + return err + } + + return nil +} + +func (manager *SPolicyManager) initializeRolePolicyGroup() error { + ctx := context.Background() + q := manager.Query() + q = q.IsNullOrEmpty("scope") + policies := make([]SPolicy, 0) + err := db.FetchModelObjects(manager, q, &policies) + if err != nil { + return err + } + + for i := range policies { + var policy rbacutils.SRbacPolicy + err := policy.Decode(policies[i].Blob) + if err != nil { + log.Errorf("Decode policy %s failed %s", policies[i].Name, err) + continue + } + failed := false + if len(policy.Roles) == 0 && len(policy.Projects) == 0 { + // match any + roles, err := policies[i].fetchMatchableRoles() + if err != nil { + log.Errorf("policy fetchMatchableRoles fail %s", err) + failed = true + } else { + for _, r := range roles { + err = RolePolicyManager.newRecord(ctx, r.Id, "", policies[i].Id, tristate.NewFromBool(policy.Auth), policy.Ips) + if err != nil { + log.Errorf("insert role policy fail %s", err) + failed = true + } + } + } + } else if len(policy.Roles) > 0 && len(policy.Projects) == 0 { + for _, r := range policy.Roles { + role, err := RoleManager.FetchRoleByName(r, policies[i].DomainId, "") + if err != nil { + log.Errorf("fetch role %s fail %s", r, err) + continue + } + err = RolePolicyManager.newRecord(ctx, role.Id, "", policies[i].Id, tristate.True, policy.Ips) + if err != nil { + log.Errorf("insert role policy fail %s", err) + failed = true + } + } + } else if len(policy.Roles) == 0 && len(policy.Projects) > 0 { + for _, p := range policy.Projects { + project, err := ProjectManager.FetchProjectByName(p, policies[i].DomainId, "") + if err != nil { + log.Errorf("fetch porject %s fail %s", p, err) + continue + } + roles, err := policies[i].fetchMatchableRoles() + if err != nil { + log.Errorf("policy fetchMatchableRoles fail %s", err) + failed = true + } else { + for _, r := range roles { + err = RolePolicyManager.newRecord(ctx, r.Id, project.Id, policies[i].Id, tristate.True, policy.Ips) + if err != nil { + log.Errorf("insert role policy fail %s", err) + failed = true + } + } + } + } + } else if len(policy.Roles) > 0 && len(policy.Projects) > 0 { + for _, r := range policy.Roles { + role, err := RoleManager.FetchRoleByName(r, policies[i].DomainId, "") + if err != nil { + log.Errorf("fetch role %s fail %s", r, err) + continue + } + for _, p := range policy.Projects { + project, err := ProjectManager.FetchProjectByName(p, policies[i].DomainId, "") + if err != nil { + log.Errorf("fetch project %s fail %s", p, err) + continue + } + err = RolePolicyManager.newRecord(ctx, role.Id, project.Id, policies[i].Id, tristate.True, policy.Ips) + if err != nil { + log.Errorf("insert role policy fail %s", err) + failed = true + } + } + } + } + if !failed { + db.Update(&policies[i], func() error { + policies[i].Scope = policy.Scope + // do not rewrite blob, make backward compatible + // policies[i].Blob = policy.Rules.Encode() + return nil + }) + } + } + return nil } @@ -106,16 +224,34 @@ func (manager *SPolicyManager) FetchEnabledPolicies() ([]SPolicy, error) { return policies, nil } -func validatePolicyVioldatePrivilege(userCred mcclient.TokenCredential, policy *rbacutils.SRbacPolicy) error { +func validatePolicyVioldatePrivilege(userCred mcclient.TokenCredential, policyScope rbacutils.TRbacScope, policy rbacutils.TPolicy) error { if userCred.GetUserName() == api.SystemAdminUser && userCred.GetDomainId() == api.DEFAULT_DOMAIN_ID { return nil } - opsScope, opsPolicySet := policyman.PolicyManager.GetMatchedPolicySet(userCred) - if opsScope != rbacutils.ScopeSystem && policy.Scope.HigherThan(opsScope) { - return errors.Wrapf(httperrors.ErrNotSufficientPrivilege, "cannot create policy scope higher than %s", opsScope) + _, policyGroup, err := RolePolicyManager.GetMatchPolicyGroup(userCred, false) + if err != nil { + return errors.Wrap(err, "GetMatchPolicyGroup") } + noViolate := false assignPolicySet := rbacutils.TPolicySet{policy} - if !opsScope.HigherThan(policy.Scope) && opsPolicySet.ViolatedBy(assignPolicySet) { + for _, scope := range []rbacutils.TRbacScope{ + rbacutils.ScopeSystem, + rbacutils.ScopeDomain, + rbacutils.ScopeProject, + } { + isViolate := false + policySet, ok := policyGroup[scope] + if !ok || len(policySet) == 0 || policySet.ViolatedBy(assignPolicySet) { + isViolate = true + } + if !isViolate { + noViolate = true + } + if scope == policyScope { + break + } + } + if !noViolate { return errors.Wrap(httperrors.ErrNotSufficientPrivilege, "policy violates operator's policy") } return nil @@ -129,17 +265,21 @@ func (manager *SPolicyManager) ValidateCreateData( input api.PolicyCreateInput, ) (api.PolicyCreateInput, error) { var err error - if len(input.Type) == 0 { + if len(input.Type) == 0 && len(input.Name) == 0 { return input, httperrors.NewInputParameterError("missing input field type") } - input.Name = input.Type - policy := rbacutils.SRbacPolicy{} - err = policy.Decode(input.Blob) + if len(input.Name) == 0 { + input.Name = input.Type + } + + policy, err := rbacutils.DecodePolicyData(input.Blob) if err != nil { return input, httperrors.NewInputParameterError("fail to decode policy data") } - err = validatePolicyVioldatePrivilege(userCred, &policy) + input.Scope = rbacutils.String2ScopeDefault(string(input.Scope), rbacutils.ScopeProject) + + err = validatePolicyVioldatePrivilege(userCred, input.Scope, policy) if err != nil { return input, errors.Wrap(err, "validatePolicyVioldatePrivilege") } @@ -166,20 +306,49 @@ func (manager *SPolicyManager) ValidateCreateData( return input, errors.Wrap(err, "CheckSetPendingQuota") } + requireScope := input.Scope + if input.IsSystem != nil && *input.IsSystem { + requireScope = rbacutils.ScopeSystem + } + allowScope := policyman.PolicyManager.AllowScope(userCred, api.SERVICE_TYPE, manager.KeywordPlural(), policyman.PolicyActionCreate) + if requireScope.HigherThan(allowScope) { + return input, errors.Wrapf(httperrors.ErrNotSufficientPrivilege, "require %s allow %s", requireScope, allowScope) + } + return input, nil } func (policy *SPolicy) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.PolicyUpdateInput) (api.PolicyUpdateInput, error) { + var requireScope rbacutils.TRbacScope + + if len(input.Scope) > 0 { + input.Scope = rbacutils.String2ScopeDefault(string(input.Scope), rbacutils.ScopeProject) + requireScope = input.Scope + } + if input.IsSystem != nil && *input.IsSystem { + requireScope = rbacutils.ScopeSystem + } + + if len(requireScope) > 0 { + allowScope := policyman.PolicyManager.AllowScope(userCred, api.SERVICE_TYPE, policy.KeywordPlural(), policyman.PolicyActionUpdate) + if requireScope.HigherThan(allowScope) { + return input, errors.Wrapf(httperrors.ErrNotSufficientPrivilege, "require %s allow %s", requireScope, allowScope) + } + } + if input.Blob != nil { - p := rbacutils.SRbacPolicy{} - err := p.Decode(input.Blob) + p, err := rbacutils.DecodePolicyData(input.Blob) if err != nil { return input, httperrors.NewInputParameterError("fail to decode policy data") } /* if p.IsSystemWidePolicy() && policyman.PolicyManager.Allow(rbacutils.ScopeSystem, userCred, consts.GetServiceType(), policy.GetModelManager().KeywordPlural(), policyman.PolicyActionUpdate) == rbacutils.Deny { return nil, httperrors.NewNotSufficientPrivilegeError("not allow to update system-wide policy") } */ - err = validatePolicyVioldatePrivilege(userCred, &p) + scope := input.Scope + if len(scope) == 0 { + scope = policy.Scope + } + err = validatePolicyVioldatePrivilege(userCred, scope, p) if err != nil { return input, errors.Wrap(err, "validatePolicyVioldatePrivilege") } @@ -192,6 +361,7 @@ func (policy *SPolicy) ValidateUpdateData(ctx context.Context, userCred mcclient if err != nil { return input, errors.Wrap(err, "SEnabledIdentityBaseResource.ValidateUpdateData") } + return input, nil } @@ -207,17 +377,17 @@ func (policy *SPolicy) PostCreate(ctx context.Context, userCred mcclient.TokenCr log.Errorf("CancelPendingUsage fail %s", err) } - policyman.PolicyManager.SyncOnce() + // policyman.PolicyManager.SyncOnce() } func (policy *SPolicy) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) { policy.SEnabledIdentityBaseResource.PostUpdate(ctx, userCred, query, data) - policyman.PolicyManager.SyncOnce() + // policyman.PolicyManager.SyncOnce() } func (policy *SPolicy) PostDelete(ctx context.Context, userCred mcclient.TokenCredential) { policy.SEnabledIdentityBaseResource.PostDelete(ctx, userCred) - policyman.PolicyManager.SyncOnce() + // policyman.PolicyManager.SyncOnce() } func (policy *SPolicy) IsSharable(reqUsrId mcclient.IIdentityProvider) bool { @@ -238,7 +408,7 @@ func (policy *SPolicy) PerformPublic(ctx context.Context, userCred mcclient.Toke if err != nil { return nil, errors.Wrap(err, "SharablePerformPublic") } - policyman.PolicyManager.SyncOnce() + // policyman.PolicyManager.SyncOnce() return nil, nil } @@ -252,7 +422,7 @@ func (policy *SPolicy) PerformPrivate(ctx context.Context, userCred mcclient.Tok if err != nil { return nil, errors.Wrap(err, "SharablePerformPrivate") } - policyman.PolicyManager.SyncOnce() + // policyman.PolicyManager.SyncOnce() return nil, nil } @@ -270,6 +440,9 @@ func (policy *SPolicy) ValidateDeleteCondition(ctx context.Context) error { // if policy.IsShared() { // return httperrors.NewInvalidStatusError("cannot delete shared policy") // } + if policy.IsSystem.IsTrue() { + return httperrors.NewForbiddenError("cannot delete system policy") + } if policy.Enabled.IsTrue() { return httperrors.NewInvalidStatusError("cannot delete enabled policy") } @@ -294,6 +467,13 @@ func (manager *SPolicyManager) ListItemFilter( if len(query.Type) > 0 { q = q.In("type", query.Type) } + if query.IsSystem != nil { + if *query.IsSystem { + q = q.IsTrue("is_system") + } else { + q = q.IsFalse("is_system") + } + } return q, nil } @@ -379,3 +559,29 @@ func (policy *SPolicy) GetRequiredSharedDomainIds() []string { func (policy *SPolicy) GetSharedDomains() []string { return db.SharableGetSharedProjects(policy, db.SharedTargetDomain) } + +func (policy *SPolicy) getPolicy() (rbacutils.TPolicy, error) { + pc, err := rbacutils.DecodePolicyData(policy.Blob) + if err != nil { + return nil, errors.Wrap(err, "Decode") + } + return pc, nil +} + +func (policy *SPolicy) GetChangeOwnerCandidateDomainIds() []string { + return db.ISharableChangeOwnerCandidateDomainIds(policy) +} + +func (policy *SPolicy) fetchMatchableRoles() ([]SRole, error) { + q := RoleManager.Query() + candDomains := policy.GetChangeOwnerCandidateDomainIds() + if len(candDomains) > 0 { + q = q.In("domain_id", candDomains) + } + roles := make([]SRole, 0) + err := db.FetchModelObjects(RoleManager, q, &roles) + if err != nil && errors.Cause(err) != sql.ErrNoRows { + return nil, errors.Wrap(err, "FetchRoles") + } + return roles, nil +} diff --git a/pkg/keystone/models/projects.go b/pkg/keystone/models/projects.go index c9d0f40aa8..83316dcc65 100644 --- a/pkg/keystone/models/projects.go +++ b/pkg/keystone/models/projects.go @@ -31,7 +31,6 @@ import ( "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" - "yunion.io/x/onecloud/pkg/cloudcommon/policy" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/keystone/options" "yunion.io/x/onecloud/pkg/mcclient" @@ -480,15 +479,14 @@ func (self *SProject) PostCreate( } } -func validateJoinProject(userCred mcclient.TokenCredential, project *SProject, roleNames []string) error { - opsScope, opsPolicySet := policy.PolicyManager.GetMatchedPolicySet(userCred) - rbacCred := rbacutils.NewRbacIdentity(project.DomainId, project.Name, roleNames) - assignScope, assignPolicySet := policy.PolicyManager.GetMatchedPolicySet(rbacCred) - log.Debugf("opsScope: %s assignScope: %s", opsScope, assignScope) +func validateJoinProject(userCred mcclient.TokenCredential, project *SProject, roleIds []string) error { + _, opsPolicies, _ := RolePolicyManager.GetMatchPolicyGroup(userCred, false) + _, assignPolicies, _ := RolePolicyManager.GetMatchPolicyGroup2(false, roleIds, project.Id, "", false) + opsScope := opsPolicies.HighestScope() + assignScope := assignPolicies.HighestScope() if assignScope.HigherThan(opsScope) { return errors.Wrap(httperrors.ErrNotSufficientPrivilege, "assigning roles requires higher privilege scope") - } - if !opsScope.HigherThan(assignScope) && opsPolicySet.ViolatedBy(assignPolicySet) { + } else if assignScope == opsScope && opsPolicies[opsScope].ViolatedBy(assignPolicies[assignScope]) { return errors.Wrap(httperrors.ErrNotSufficientPrivilege, "assigning roles violates operator's policy") } return nil @@ -514,7 +512,7 @@ func (project *SProject) PerformJoin( return nil, httperrors.NewInputParameterError("%v", err) } - roleNames := make([]string, 0) + roleIds := make([]string, 0) roles := make([]*SRole, 0) for i := range input.Roles { obj, err := RoleManager.FetchByIdOrName(userCred, input.Roles[i]) @@ -527,10 +525,10 @@ func (project *SProject) PerformJoin( } role := obj.(*SRole) roles = append(roles, role) - roleNames = append(roleNames, role.Name) + roleIds = append(roleIds, role.Id) } - err = validateJoinProject(userCred, project, roleNames) + err = validateJoinProject(userCred, project, roleIds) if err != nil { return nil, errors.Wrap(err, "validateJoinProject") } diff --git a/pkg/keystone/models/rolepolicies.go b/pkg/keystone/models/rolepolicies.go new file mode 100644 index 0000000000..12b3c840d7 --- /dev/null +++ b/pkg/keystone/models/rolepolicies.go @@ -0,0 +1,451 @@ +// 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 models + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/tristate" + "yunion.io/x/pkg/util/netutils" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/identity" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/util/rbacutils" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SRolePolicyManager struct { + db.SResourceBaseManager +} + +var RolePolicyManager *SRolePolicyManager + +func init() { + RolePolicyManager = &SRolePolicyManager{ + SResourceBaseManager: db.NewResourceBaseManager( + SRolePolicy{}, + "rolepolicy_tbl", + "rolepolicy", + "rolepolicies", + ), + } + RolePolicyManager.SetVirtualObject(RolePolicyManager) +} + +type SRolePolicy struct { + db.SResourceBase + + // 角色ID, 主键 + RoleId string `width:"128" charset:"ascii" primary:"true" list:"domain" create:"domain_optional"` + // 项目ID,主键 + ProjectId string `width:"128" charset:"ascii" primary:"true" list:"domain" create:"domain_optional"` + // 权限ID, 主键 + PolicyId string `width:"128" charset:"ascii" primary:"true" list:"domain" create:"domain_required"` + // 是否需要认证 + Auth tristate.TriState `nullable:"false" default:"true" list:"domain" create:"domain_optional"` + // 匹配的IP白名单 + Ips string `list:"domain" create:"domain_optional" update:"domain"` +} + +func (manager *SRolePolicyManager) newRecord(ctx context.Context, roleId, projectId, policyId string, auth tristate.TriState, ips []netutils.IPV4Prefix) error { + if len(roleId) == 0 { + return errors.Wrap(httperrors.ErrNotEmpty, "roleId") + } + if len(policyId) == 0 { + return errors.Wrap(httperrors.ErrNotEmpty, "policyId") + } + rpg := SRolePolicy{} + rpg.RoleId = roleId + rpg.ProjectId = projectId + rpg.PolicyId = policyId + rpg.Auth = auth + ipStrs := make([]string, len(ips)) + for i, ipprefix := range ips { + ipStrs[i] = ipprefix.String() + } + rpg.Ips = strings.Join(ipStrs, rbacutils.IP_PREFIX_SEP) + rpg.SetModelManager(manager, &rpg) + err := RolePolicyManager.TableSpec().InsertOrUpdate(ctx, &rpg) + if err != nil { + log.Errorf("insert role policy fail %s", err) + return errors.Wrap(err, "insert role policy") + } + return nil +} + +func (manager *SRolePolicyManager) deleteRecord(ctx context.Context, roleId, projectId, policyId string) error { + rpg := SRolePolicy{} + rpg.RoleId = roleId + rpg.ProjectId = projectId + rpg.PolicyId = policyId + rpg.SetModelManager(manager, &rpg) + _, err := db.Update(&rpg, func() error { + return rpg.MarkDelete() + }) + if err != nil && errors.Cause(err) != sql.ErrNoRows { + return errors.Wrap(err, "Update") + } + return nil +} + +func (rp *SRolePolicy) GetId() string { + return fmt.Sprintf("%s:%s:%s", rp.RoleId, rp.ProjectId, rp.PolicyId) +} + +func (rp *SRolePolicy) GetName() string { + return getRolePolicyName(rp.GetRole(), rp.GetProject(), rp.GetPolicy()) +} + +func getRolePolicyName(role *SRole, project *SProject, policy *SPolicy) string { + names := make([]string, 0) + if role != nil { + names = append(names, role.GetName()) + } + if project != nil { + names = append(names, project.GetName()) + } + if policy != nil { + names = append(names, policy.GetName()) + } + return strings.Join(names, "/") +} + +func (rp *SRolePolicy) GetRole() *SRole { + role, err := RoleManager.FetchById(rp.RoleId) + if err != nil { + log.Errorf("RoleManaget.FetchById %s fail %s", rp.RoleId, err) + return nil + } + return role.(*SRole) +} + +func (rp *SRolePolicy) GetProject() *SProject { + if len(rp.ProjectId) == 0 { + return nil + } + project, err := ProjectManager.FetchById(rp.ProjectId) + if err != nil { + log.Errorf("ProjectManager.FetchById %s fail %s", rp.ProjectId, err) + return nil + } + return project.(*SProject) +} + +func (rp *SRolePolicy) GetPolicy() *SPolicy { + policy, err := PolicyManager.FetchById(rp.PolicyId) + if err != nil { + log.Errorf("PolicyManaget.FetchById %s fail %s", rp.PolicyId, err) + return nil + } + return policy.(*SPolicy) +} + +func (manager *SRolePolicyManager) NamespaceScope() rbacutils.TRbacScope { + return PolicyManager.NamespaceScope() +} + +func (manager *SRolePolicyManager) FilterByOwner(q *sqlchemy.SQuery, owner mcclient.IIdentityProvider, scope rbacutils.TRbacScope) *sqlchemy.SQuery { + return PolicyManager.FilterByOwner(q, owner, scope) +} + +func (manager *SRolePolicyManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.RolePolicyListInput, +) (*sqlchemy.SQuery, error) { + var err error + q, err = manager.SResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ResourceBaseListInput) + if err != nil { + return nil, errors.Wrap(err, "SResourceBaseManager.ListItemFilter") + } + if len(query.RoleIds) > 0 { + for i := range query.RoleIds { + role, err := RoleManager.FetchByIdOrName(userCred, query.RoleIds[i]) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, errors.Wrapf(httperrors.ErrResourceNotFound, "%s %s", RoleManager.Keyword(), query.RoleIds[i]) + } else { + return nil, errors.Wrap(err, "RoleManager.FetchByIdOrName") + } + } + query.RoleIds[i] = role.GetId() + } + q = q.Filter(sqlchemy.OR( + sqlchemy.IsNullOrEmpty(q.Field("role_id")), + sqlchemy.In(q.Field("role_id"), query.RoleIds), + )) + } + if len(query.ProjectId) > 0 { + project, err := ProjectManager.FetchByIdOrName(userCred, query.ProjectId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, errors.Wrapf(httperrors.ErrResourceNotFound, "%s %s", ProjectManager.Keyword(), query.ProjectId) + } else { + return nil, errors.Wrap(err, "ProjectManager.FetchByIdOrName") + } + } + q = q.Filter(sqlchemy.OR( + sqlchemy.IsNullOrEmpty(q.Field("project_id")), + sqlchemy.Equals(q.Field("project_id"), project.GetId()), + )) + } + if len(query.PolicyId) > 0 { + policy, err := PolicyManager.FetchByIdOrName(userCred, query.PolicyId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, errors.Wrapf(httperrors.ErrResourceNotFound, "%s %s", PolicyManager.Keyword(), query.PolicyId) + } else { + return nil, errors.Wrap(err, "PolicyManager.FetchByIdOrName") + } + } + q = q.Filter(sqlchemy.OR( + sqlchemy.IsNullOrEmpty(q.Field("policy_id")), + sqlchemy.Equals(q.Field("policy_id"), policy.GetId()), + )) + } + if query.Auth != nil { + if *query.Auth { + q = q.IsTrue("auth") + } else { + q = q.IsFalse("auth") + } + } + return q, nil +} + +func (manager *SRolePolicyManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.RolePolicyListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.ResourceBaseListInput) + if err != nil { + return nil, errors.Wrap(err, "SResourceBaseManager.OrderByExtraFields") + } + + return q, nil +} + +func (manager *SRolePolicyManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + + return q, httperrors.ErrNotFound +} + +func (policy *SRolePolicy) GetExtraDetails( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + isList bool, +) (api.RolePolicyDetails, error) { + return api.RolePolicyDetails{}, nil +} + +func (manager *SRolePolicyManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.RolePolicyDetails { + rows := make([]api.RolePolicyDetails, len(objs)) + resRows := manager.SResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + roleIds := make([]string, 0) + projectIds := make([]string, 0) + policyIds := make([]string, 0) + for i := range rows { + rows[i] = api.RolePolicyDetails{ + ResourceBaseDetails: resRows[i], + } + rp := objs[i].(*SRolePolicy) + roleIds = append(roleIds, rp.RoleId) + projectIds = append(projectIds, rp.ProjectId) + policyIds = append(policyIds, rp.PolicyId) + } + + roleMap := make(map[string]SRole) + err := db.FetchModelObjectsByIds(RoleManager, "id", roleIds, &roleMap) + if err != nil { + log.Errorf("db.FetchModelObjectsByIds RoleManager fail %s", err) + return rows + } + projectMap := make(map[string]SProject) + err = db.FetchModelObjectsByIds(ProjectManager, "id", projectIds, &projectMap) + if err != nil { + log.Errorf("db.FetchModelObjectsByIds ProjectManager fail %s", err) + return rows + } + policyMap := make(map[string]SPolicy) + err = db.FetchModelObjectsByIds(PolicyManager, "id", policyIds, &policyMap) + if err != nil { + log.Errorf("db.FetchModelObjectsByIds PolicyManager fail %s", err) + return rows + } + + for i := range rows { + rp := objs[i].(*SRolePolicy) + var role *SRole + if obj, ok := roleMap[rp.RoleId]; ok { + role = &obj + } + var project *SProject + if obj, ok := projectMap[rp.ProjectId]; ok { + project = &obj + } + var policy *SPolicy + if obj, ok := policyMap[rp.PolicyId]; ok { + policy = &obj + } + rows[i].Id = rp.GetId() + rows[i].Name = getRolePolicyName(role, project, policy) + if role != nil { + rows[i].Role = role.GetName() + } + if project != nil { + rows[i].Project = project.GetName() + } + if policy != nil { + rows[i].Policy = policy.GetName() + rows[i].Scope = policy.Scope + rows[i].Description = policy.Description + } + } + + return rows +} + +func (manager *SRolePolicyManager) getMatchPolicyIds(userCred rbacutils.IRbacIdentity) ([]string, error) { + isGuest := true + if userCred != nil && !auth.IsGuestToken(userCred) { + isGuest = false + } + return manager.getMatchPolicyIds2(isGuest, userCred.GetRoleIds(), userCred.GetProjectId(), userCred.GetLoginIp()) +} + +func (manager *SRolePolicyManager) getMatchPolicyIds2(isGuest bool, roleIds []string, pid string, loginIp string) ([]string, error) { + q := manager.Query() + if !isGuest { + if len(roleIds) > 0 { + q = q.Filter(sqlchemy.OR( + sqlchemy.IsNullOrEmpty(q.Field("role_id")), + sqlchemy.In(q.Field("role_id"), roleIds), + )) + } + if len(pid) > 0 { + q = q.Filter(sqlchemy.OR( + sqlchemy.IsNullOrEmpty(q.Field("project_id")), + sqlchemy.Equals(q.Field("project_id"), pid), + )) + } + } else { + q = q.IsFalse("auth") + } + rps := make([]SRolePolicy, 0) + err := db.FetchModelObjects(manager, q, &rps) + if err != nil && errors.Cause(err) != sql.ErrNoRows { + return nil, errors.Wrap(err, "FetchPolicies") + } + policyIds := stringutils2.NewSortedStrings(nil) + // filter by login IP + for _, rp := range rps { + if len(loginIp) > 0 && !rp.MatchIP(loginIp) { + continue + } + policyIds = stringutils2.Append(policyIds, rp.PolicyId) + } + return policyIds, nil +} + +func (manager *SRolePolicyManager) GetMatchPolicyGroup(userCred rbacutils.IRbacIdentity, nameOnly bool) (map[rbacutils.TRbacScope][]string, rbacutils.TPolicyGroup, error) { + policyIds, err := manager.getMatchPolicyIds(userCred) + if err != nil { + return nil, nil, errors.Wrap(err, "getMatchPolicyIds") + } + return manager.GetPolicyGroupByIds(policyIds, nameOnly) +} + +func (manager *SRolePolicyManager) GetMatchPolicyGroup2(isGuest bool, roleIds []string, pid string, loginIp string, nameOnly bool) (map[rbacutils.TRbacScope][]string, rbacutils.TPolicyGroup, error) { + policyIds, err := manager.getMatchPolicyIds2(isGuest, roleIds, pid, loginIp) + if err != nil { + return nil, nil, errors.Wrap(err, "getMatchPolicyIds") + } + return manager.GetPolicyGroupByIds(policyIds, nameOnly) +} + +func (manager *SRolePolicyManager) GetPolicyGroupByIds(policyIds []string, nameOnly bool) (map[rbacutils.TRbacScope][]string, rbacutils.TPolicyGroup, error) { + names := make(map[rbacutils.TRbacScope][]string) + var group rbacutils.TPolicyGroup + if !nameOnly { + group = rbacutils.TPolicyGroup{} + } + for _, id := range policyIds { + policyObj, err := PolicyManager.FetchById(id) + if err != nil { + return nil, nil, errors.Wrapf(err, "FetchPolicy %s", id) + } + policy := policyObj.(*SPolicy) + if scopeName, ok := names[policy.Scope]; !ok { + names[policy.Scope] = []string{policy.Name} + } else { + names[policy.Scope] = append(scopeName, policy.Name) + } + if !nameOnly { + data, err := policy.getPolicy() + if err != nil { + return nil, nil, errors.Wrap(err, "getPolicy") + } + if set, ok := group[policy.Scope]; !ok { + group[policy.Scope] = rbacutils.TPolicySet{data} + } else { + group[policy.Scope] = append(set, data) + } + } + } + return names, group, nil +} + +func (rp *SRolePolicy) MatchIP(ipstr string) bool { + return rbacutils.MatchIPStrings(rp.Ips, ipstr) +} + +func (manager *SRolePolicyManager) fetchByRoleId(roleId string) ([]SRolePolicy, error) { + q := manager.Query().Equals("role_id", roleId) + rps := make([]SRolePolicy, 0) + err := db.FetchModelObjects(manager, q, &rps) + if err != nil && errors.Cause(err) == sql.ErrNoRows { + return nil, errors.Wrap(err, "FetchModelObjects") + } + return rps, nil +} diff --git a/pkg/keystone/models/roles.go b/pkg/keystone/models/roles.go index 6ea4cc3372..dc03037a82 100644 --- a/pkg/keystone/models/roles.go +++ b/pkg/keystone/models/roles.go @@ -23,13 +23,14 @@ import ( "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/gotypes" + "yunion.io/x/pkg/tristate" + "yunion.io/x/pkg/util/netutils" "yunion.io/x/sqlchemy" "yunion.io/x/onecloud/pkg/apis" api "yunion.io/x/onecloud/pkg/apis/identity" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" - "yunion.io/x/onecloud/pkg/cloudcommon/policy" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/rbacutils" @@ -240,7 +241,13 @@ func (manager *SRoleManager) FetchCustomizeColumns( rows[i].UserCount, _ = role.GetUserCount() rows[i].GroupCount, _ = role.GetGroupCount() rows[i].ProjectCount, _ = role.GetProjectCount() - rows[i].MatchPolicies = policy.PolicyManager.RoleMatchPolicies(role.Name) + names, _, _ := RolePolicyManager.GetMatchPolicyGroup2(false, []string{role.Id}, "", "", true) + rows[i].Policies = names + mp := make([]string, 0) + for _, v := range names { + mp = append(mp, v...) + } + rows[i].MatchPolicies = mp } return rows @@ -347,7 +354,7 @@ func (role *SRole) UpdateInContext(ctx context.Context, userCred mcclient.TokenC if project.DomainId != role.DomainId && !role.GetIsPublic() { return nil, httperrors.NewInputParameterError("inconsistent domain for project and roles") } - err := validateJoinProject(userCred, project, []string{role.Name}) + err := validateJoinProject(userCred, project, []string{role.Id}) if err != nil { return nil, errors.Wrap(err, "validateJoinProject") } @@ -436,7 +443,6 @@ func (role *SRole) PerformPublic(ctx context.Context, userCred mcclient.TokenCre if err != nil { return nil, errors.Wrap(err, "SharablePerformPublic") } - policy.PolicyManager.SyncOnce() return nil, nil } @@ -449,7 +455,6 @@ func (role *SRole) PerformPrivate(ctx context.Context, userCred mcclient.TokenCr if err != nil { return nil, errors.Wrap(err, "SharablePerformPrivate") } - policy.PolicyManager.SyncOnce() return nil, nil } @@ -541,3 +546,171 @@ func (role *SRole) GetRequiredSharedDomainIds() []string { func (role *SRole) GetSharedDomains() []string { return db.SharableGetSharedProjects(role, db.SharedTargetDomain) } + +func (role *SRole) AllowPerformSetPolicies(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.RolePerformSetPoliciesInput) bool { + return true +} + +func (role *SRole) PerformSetPolicies(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.RolePerformSetPoliciesInput) (jsonutils.JSONObject, error) { + normalInputIds := stringutils2.NewSortedStrings(nil) + normalInputs := make(map[string]sRolePerformAddPolicyInput, len(input.Policies)) + for i := range input.Policies { + normalInput, err := role.normalizeRoleAddPolicyInput(userCred, input.Policies[i]) + if err != nil { + return nil, errors.Wrapf(err, "normalizeRoleAddPolicyInput at %d", i) + } + idstr := normalInput.getId() + if _, ok := normalInputs[idstr]; !ok { + normalInputs[idstr] = normalInput + normalInputIds = stringutils2.Append(normalInputIds, idstr) + } else { + log.Warningf("duplicate input key id %s", idstr) + } + } + + existRpList, err := RolePolicyManager.fetchByRoleId(role.Id) + if err != nil { + return nil, errors.Wrap(err, "RolePolicyManager.fetchByRoleId") + } + + existRpIds := stringutils2.NewSortedStrings(nil) + existRpMap := make(map[string]*SRolePolicy) + for i := range existRpList { + idstr := existRpList[i].GetId() + if _, ok := existRpMap[idstr]; !ok { + existRpMap[idstr] = &existRpList[i] + existRpIds = stringutils2.Append(existRpIds, idstr) + } + } + + addedIds, updatedIds, deletedIds := stringutils2.Split(normalInputIds, existRpIds) + + for _, idstr := range deletedIds { + toDel := existRpMap[idstr] + err := RolePolicyManager.deleteRecord(ctx, toDel.RoleId, toDel.ProjectId, toDel.PolicyId) + if err != nil { + return nil, errors.Wrap(err, "RolePolicyManager.deleteRecord") + } + } + + for _, idstr := range updatedIds { + toUpdate := normalInputs[idstr] + err := RolePolicyManager.newRecord(ctx, toUpdate.roleId, toUpdate.projectId, toUpdate.policyId, tristate.True, toUpdate.prefixes) + if err != nil { + return nil, errors.Wrap(err, "RolePolicyManager.updateRecord") + } + } + + for _, idstr := range addedIds { + toAdd := normalInputs[idstr] + err := RolePolicyManager.newRecord(ctx, toAdd.roleId, toAdd.projectId, toAdd.policyId, tristate.True, toAdd.prefixes) + if err != nil { + return nil, errors.Wrap(err, "RolePolicyManager.newRecord") + } + } + + return nil, nil +} + +func (role *SRole) AllowPerformAddPolicy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.RolePerformAddPolicyInput) bool { + return true +} + +type sRolePerformAddPolicyInput struct { + prefixes []netutils.IPV4Prefix + roleId string + projectId string + policyId string +} + +func (s sRolePerformAddPolicyInput) getId() string { + return fmt.Sprintf("%s:%s:%s", s.roleId, s.projectId, s.policyId) +} + +func (role *SRole) normalizeRoleAddPolicyInput(userCred mcclient.TokenCredential, input api.RolePerformAddPolicyInput) (sRolePerformAddPolicyInput, error) { + output := sRolePerformAddPolicyInput{} + prefList := make([]netutils.IPV4Prefix, 0) + for _, ipStr := range input.Ips { + pref, err := netutils.NewIPV4Prefix(ipStr) + if err != nil { + return output, errors.Wrapf(httperrors.ErrInputParameter, "invalid prefix %s", ipStr) + } + prefList = append(prefList, pref) + } + if len(input.ProjectId) > 0 { + proj, err := ProjectManager.FetchByIdOrName(userCred, input.ProjectId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return output, errors.Wrapf(httperrors.ErrNotFound, "%s %s", ProjectManager.Keyword(), input.ProjectId) + } else { + return output, errors.Wrap(err, "ProjectManager.FetchByIdOrName") + } + } + output.projectId = proj.GetId() + } + if len(input.PolicyId) == 0 { + return output, errors.Wrap(httperrors.ErrInputParameter, "missing policy_id") + } + policy, err := PolicyManager.FetchByIdOrName(userCred, input.PolicyId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return output, errors.Wrapf(httperrors.ErrNotFound, "%s %s", PolicyManager.Keyword(), input.PolicyId) + } else { + return output, errors.Wrap(err, "PolicyManager.FetchByIdOrName") + } + } + output.roleId = role.Id + output.prefixes = prefList + output.policyId = policy.GetId() + return output, nil +} + +func (role *SRole) PerformAddPolicy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.RolePerformAddPolicyInput) (jsonutils.JSONObject, error) { + normalInput, err := role.normalizeRoleAddPolicyInput(userCred, input) + if err != nil { + return nil, errors.Wrap(err, "normalizeRoleAddPolicyInput") + } + err = RolePolicyManager.newRecord(ctx, normalInput.roleId, normalInput.projectId, normalInput.policyId, tristate.True, normalInput.prefixes) + if err != nil { + return nil, errors.Wrap(err, "newRecord") + } + return nil, nil +} + +func (role *SRole) AllowPerformRemovePolicy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.RolePerformRemovePolicyInput) bool { + return true +} + +func (role *SRole) PerformRemovePolicy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.RolePerformRemovePolicyInput) (jsonutils.JSONObject, error) { + if len(input.ProjectId) > 0 { + proj, err := ProjectManager.FetchByIdOrName(userCred, input.ProjectId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, errors.Wrapf(httperrors.ErrNotFound, "%s %s", ProjectManager.Keyword(), input.ProjectId) + } else { + return nil, errors.Wrap(err, "ProjectManager.FetchByIdOrName") + } + } + input.ProjectId = proj.GetId() + } + if len(input.PolicyId) == 0 { + return nil, errors.Wrap(httperrors.ErrInputParameter, "missing policy_id") + } + policy, err := PolicyManager.FetchByIdOrName(userCred, input.PolicyId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, errors.Wrapf(httperrors.ErrNotFound, "%s %s", PolicyManager.Keyword(), input.PolicyId) + } else { + return nil, errors.Wrap(err, "PolicyManager.FetchByIdOrName") + } + } + err = RolePolicyManager.deleteRecord(ctx, role.Id, input.ProjectId, policy.GetId()) + if err != nil { + return nil, errors.Wrap(err, "deleteRecord") + } + return nil, nil +} + +func (role *SRole) GetChangeOwnerCandidateDomainIds() []string { + return db.ISharableChangeOwnerCandidateDomainIds(role) +} diff --git a/pkg/keystone/models/services.go b/pkg/keystone/models/services.go index c6b732a3ee..50a743b6fa 100644 --- a/pkg/keystone/models/services.go +++ b/pkg/keystone/models/services.go @@ -201,9 +201,9 @@ func (service *SService) PerformConfig(ctx context.Context, userCred mcclient.To action := input.Action opts := input.Config if service.isCommonService() { - err = saveConfigs(userCred, action, service, opts, api.CommonWhitelistOptionMap, nil, nil) + err = saveConfigs(userCred, action, service, opts, api.CommonWhitelistOptionMap, nil, nil, false) } else { - err = saveConfigs(userCred, action, service, opts, nil, api.ServiceBlacklistOptionMap, nil) + err = saveConfigs(userCred, action, service, opts, nil, api.ServiceBlacklistOptionMap, nil, false) } if err != nil { return nil, httperrors.NewInternalServerError("saveConfig fail %s", err) diff --git a/pkg/keystone/models/users.go b/pkg/keystone/models/users.go index 39b081d87b..ead4eb60e9 100644 --- a/pkg/keystone/models/users.go +++ b/pkg/keystone/models/users.go @@ -86,7 +86,7 @@ type SUser struct { LastLoginIp string `nullable:"true" list:"domain"` LastLoginSource string `nullable:"true" list:"domain"` - IsSystemAccount tristate.TriState `nullable:"false" default:"false" list:"domain" update:"domain" create:"domain_optional"` + IsSystemAccount tristate.TriState `nullable:"false" default:"false" list:"domain" update:"admin" create:"admin_optional"` // deprecated DefaultProjectId string `width:"64" charset:"ascii" nullable:"true"` @@ -957,7 +957,7 @@ func joinProjects(ident db.IModel, isUser bool, ctx context.Context, userCred mc projects := make([]*SProject, 0) roles := make([]*SRole, 0) - roleNames := make([]string, 0) + roleIds := make([]string, 0) for i := range input.Roles { obj, err := RoleManager.FetchByIdOrName(userCred, input.Roles[i]) @@ -970,7 +970,7 @@ func joinProjects(ident db.IModel, isUser bool, ctx context.Context, userCred mc } role := obj.(*SRole) roles = append(roles, role) - roleNames = append(roleNames, role.Name) + roleIds = append(roleIds, role.Id) } for i := range input.Projects { @@ -983,7 +983,7 @@ func joinProjects(ident db.IModel, isUser bool, ctx context.Context, userCred mc } } project := obj.(*SProject) - err = validateJoinProject(userCred, project, roleNames) + err = validateJoinProject(userCred, project, roleIds) if err != nil { return errors.Wrapf(err, "validateJoinProject %s(%s)", project.Id, project.Name) } diff --git a/pkg/keystone/service/handlers.go b/pkg/keystone/service/handlers.go index 19d7652525..b45263dbe2 100644 --- a/pkg/keystone/service/handlers.go +++ b/pkg/keystone/service/handlers.go @@ -89,6 +89,7 @@ func InitHandlers(app *appsrv.Application) { models.CredentialManager, models.IdentityProviderManager, models.ServiceCertificateManager, + models.RolePolicyManager, } { db.RegisterModelManager(manager) handler := db.NewModelHandler(manager) diff --git a/pkg/keystone/service/localpolicy.go b/pkg/keystone/service/localpolicy.go index 06eaeb1f11..a2f7b963d9 100644 --- a/pkg/keystone/service/localpolicy.go +++ b/pkg/keystone/service/localpolicy.go @@ -17,58 +17,21 @@ package service import ( "context" - "yunion.io/x/jsonutils" - "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/keystone/models" - "yunion.io/x/onecloud/pkg/util/rbacutils" + "yunion.io/x/onecloud/pkg/mcclient" ) -func localPolicyFetcher(ctx context.Context) (map[rbacutils.TRbacScope][]rbacutils.SPolicyInfo, error) { - policyList, err := models.PolicyManager.FetchEnabledPolicies() +func localPolicyFetcher(ctx context.Context, token mcclient.TokenCredential) (*mcclient.SFetchMatchPoliciesOutput, error) { + names, groups, err := models.RolePolicyManager.GetMatchPolicyGroup(token, false) if err != nil { - return nil, errors.Wrap(err, "models.PolicyManager.FetchEnabledPolicies") + return nil, errors.Wrap(err, "GetMatchPolicyGroup") } - policies := make(map[rbacutils.TRbacScope][]rbacutils.SPolicyInfo) + output := mcclient.SFetchMatchPoliciesOutput{} + output.Names = names + output.Policies = groups - for i := range policyList { - typeStr := policyList[i].Name - policy := rbacutils.SRbacPolicy{} - policyStr, err := policyList[i].Blob.GetString() - if err != nil { - log.Errorf("fail to get string of blob %s", err) - continue - } - policyJson, err := jsonutils.ParseString(policyStr) - if err != nil { - log.Errorf("fail to deocde policy blob into JSON %s", err) - continue - } - err = policy.Decode(policyJson) - if err != nil { - log.Errorf("fail to decode policy %s %s %s", typeStr, policyList[i].Blob, err) - continue - } - - policy.DomainId = policyList[i].DomainId - policy.IsPublic = policyList[i].IsPublic - policy.PublicScope = rbacutils.String2ScopeDefault(policyList[i].PublicScope, rbacutils.ScopeSystem) - policy.SharedDomainIds = policyList[i].GetSharedDomains() - - if _, ok := policies[policy.Scope]; !ok { - policies[policy.Scope] = make([]rbacutils.SPolicyInfo, 0) - } - - sp := rbacutils.SPolicyInfo{ - Id: policyList[i].Id, - Name: policyList[i].Name, - Policy: &policy, - } - - policies[policy.Scope] = append(policies[policy.Scope], sp) - } - - return policies, nil + return &output, nil } diff --git a/pkg/keystone/tokens/handlers.go b/pkg/keystone/tokens/handlers.go index ef448b30c4..9137820307 100644 --- a/pkg/keystone/tokens/handlers.go +++ b/pkg/keystone/tokens/handlers.go @@ -39,6 +39,7 @@ func AddHandler(app *appsrv.Application) { app.AddHandler2("POST", "/v3/auth/tokens", authenticateTokensV3, nil, "auth_tokens_v3", nil) app.AddHandler2("GET", "/v2.0/tokens/", authenticateToken(verifyTokensV2), nil, "verify_tokens_v2", nil) app.AddHandler2("GET", "/v3/auth/tokens", authenticateToken(verifyTokensV3), nil, "verify_tokens_v3", nil) + app.AddHandler2("GET", "/v3/auth/policies", authenticateToken(fetchTokenPolicies), nil, "fetch_token_policies", nil) } func FetchAuthContext(authCtx mcclient.SAuthContext, r *http.Request) mcclient.SAuthContext { diff --git a/pkg/keystone/tokens/policy.go b/pkg/keystone/tokens/policy.go new file mode 100644 index 0000000000..812568c004 --- /dev/null +++ b/pkg/keystone/tokens/policy.go @@ -0,0 +1,39 @@ +// 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 tokens + +import ( + "context" + "net/http" + + "yunion.io/x/onecloud/pkg/appsrv" + "yunion.io/x/onecloud/pkg/cloudcommon/policy" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/keystone/models" + "yunion.io/x/onecloud/pkg/mcclient" +) + +func fetchTokenPolicies(ctx context.Context, w http.ResponseWriter, r *http.Request) { + token := policy.FetchUserCredential(ctx) + names, group, err := models.RolePolicyManager.GetMatchPolicyGroup(token, false) + if err != nil { + httperrors.GeneralServerError(ctx, w, err) + return + } + output := mcclient.SFetchMatchPoliciesOutput{} + output.Names = names + output.Policies = group + appsrv.SendJSON(w, output.Encode()) +} diff --git a/pkg/keystone/tokens/token.go b/pkg/keystone/tokens/token.go index 72379b202b..f31db2b8c2 100644 --- a/pkg/keystone/tokens/token.go +++ b/pkg/keystone/tokens/token.go @@ -23,7 +23,6 @@ import ( "yunion.io/x/pkg/utils" api "yunion.io/x/onecloud/pkg/apis/identity" - "yunion.io/x/onecloud/pkg/cloudcommon/policy" "yunion.io/x/onecloud/pkg/keystone/keys" "yunion.io/x/onecloud/pkg/keystone/models" "yunion.io/x/onecloud/pkg/keystone/options" @@ -219,10 +218,13 @@ func (t *SAuthToken) GetSimpleUserCred(token string) (mcclient.TokenCredential, roles, err = models.AssignmentManager.FetchUserProjectRoles(t.UserId, t.DomainId) } roleStrs := make([]string, len(roles)) + roleIdStrs := make([]string, len(roles)) for i := range roles { roleStrs[i] = roles[i].Name + roleIdStrs[i] = roles[i].Id } ret.Roles = strings.Join(roleStrs, ",") + ret.RoleIds = strings.Join(roleIdStrs, ",") return &ret, nil } @@ -334,9 +336,10 @@ func (t *SAuthToken) getTokenV3( token.Token.Roles[i].Name = roles[i].Name } - token.Token.Policies.Project = policy.PolicyManager.MatchedPolicyNames(rbacutils.ScopeProject, &token) - token.Token.Policies.Domain = policy.PolicyManager.MatchedPolicyNames(rbacutils.ScopeDomain, &token) - token.Token.Policies.System = policy.PolicyManager.MatchedPolicyNames(rbacutils.ScopeSystem, &token) + policyNames, _, _ := models.RolePolicyManager.GetMatchPolicyGroup(&token, true) + token.Token.Policies.Project, _ = policyNames[rbacutils.ScopeProject] + token.Token.Policies.Domain, _ = policyNames[rbacutils.ScopeDomain] + token.Token.Policies.System, _ = policyNames[rbacutils.ScopeSystem] endpoints, err := models.EndpointManager.FetchAll() if err != nil { @@ -400,6 +403,7 @@ func (t *SAuthToken) getTokenV2( token.Metadata.Roles = make([]string, len(roles)) for i := range roles { token.User.Roles[i].Name = roles[i].Name + token.User.Roles[i].Id = roles[i].Id token.Metadata.Roles[i] = roles[i].Name } endpoints, err := models.EndpointManager.FetchAll() diff --git a/pkg/mcclient/auth/middleware.go b/pkg/mcclient/auth/middleware.go index 63b09f56a8..e51e8fd092 100644 --- a/pkg/mcclient/auth/middleware.go +++ b/pkg/mcclient/auth/middleware.go @@ -25,11 +25,15 @@ import ( "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/rbacutils" ) var ( - GuestToken = mcclient.SSimpleToken{ - User: "guest", + GUEST_USER = "guest" + GUEST_TOKEN = "guest_token" + GuestToken = mcclient.SSimpleToken{ + User: GUEST_USER, + Token: GUEST_TOKEN, } DefaultTokenVerifier = Verify @@ -91,3 +95,7 @@ func FetchUserCredential(ctx context.Context, filter func(mcclient.TokenCredenti } return nil } + +func IsGuestToken(userCred rbacutils.IRbacIdentity) bool { + return userCred.GetTokenString() == GUEST_TOKEN +} diff --git a/pkg/mcclient/auth/policy.go b/pkg/mcclient/auth/policy.go new file mode 100644 index 0000000000..869709caeb --- /dev/null +++ b/pkg/mcclient/auth/policy.go @@ -0,0 +1,29 @@ +// 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 auth + +import ( + "context" + + "yunion.io/x/onecloud/pkg/mcclient" +) + +func (a *authManager) fetchMatchPolicies(ctx context.Context, token mcclient.TokenCredential) (*mcclient.SFetchMatchPoliciesOutput, error) { + return a.client.FetchMatchPolicies(ctx, token) +} + +func FetchMatchPolicies(ctx context.Context, token mcclient.TokenCredential) (*mcclient.SFetchMatchPoliciesOutput, error) { + return manager.fetchMatchPolicies(ctx, token) +} diff --git a/pkg/mcclient/mcclient.go b/pkg/mcclient/mcclient.go index 21dffd717f..3fb2a2bff7 100644 --- a/pkg/mcclient/mcclient.go +++ b/pkg/mcclient/mcclient.go @@ -33,6 +33,7 @@ import ( "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/util/httputils" + "yunion.io/x/onecloud/pkg/util/rbacutils" "yunion.io/x/onecloud/pkg/util/seclib2" ) @@ -412,3 +413,48 @@ func (this *Client) NewSession(ctx context.Context, region, zone, endpointType s customizeServiceUrl: map[string]string{}, } } + +type SFetchMatchPoliciesOutput struct { + Names map[rbacutils.TRbacScope][]string `json:"names"` + Policies rbacutils.TPolicyGroup `json:"policies"` +} + +func (o *SFetchMatchPoliciesOutput) Decode(object jsonutils.JSONObject) error { + err := object.Unmarshal(&o.Names, "names") + if err != nil { + return errors.Wrap(err, "unmarshal names") + } + pData, err := object.Get("policies") + if err != nil { + return errors.Wrap(err, "Get policies") + } + o.Policies, err = rbacutils.DecodePolicyGroup(pData) + if err != nil { + return errors.Wrap(err, "DecodePolicyGroup") + } + return nil +} + +func (o SFetchMatchPoliciesOutput) Encode() jsonutils.JSONObject { + output := jsonutils.NewDict() + output.Set("names", jsonutils.Marshal(o.Names)) + output.Set("policies", o.Policies.Encode()) + return output +} + +func (client *Client) FetchMatchPolicies(ctx context.Context, token TokenCredential) (*SFetchMatchPoliciesOutput, error) { + header := http.Header{} + if token.GetTokenString() != "" { + header.Add(api.AUTH_TOKEN_HEADER, token.GetTokenString()) + } + _, rbody, err := client.jsonRequest(ctx, client.authUrl, "", "GET", "/auth/policies", header, nil) + if err != nil { + return nil, errors.Wrap(err, "jsonRequest") + } + output := &SFetchMatchPoliciesOutput{} + err = output.Decode(rbody) + if err != nil { + return nil, errors.Wrap(err, "SFetchMatchPoliciesOutput.Decode") + } + return output, nil +} diff --git a/pkg/mcclient/modules/mod_policies.go b/pkg/mcclient/modules/mod_policies.go index 434336df9c..936b792e1f 100644 --- a/pkg/mcclient/modules/mod_policies.go +++ b/pkg/mcclient/modules/mod_policies.go @@ -16,6 +16,7 @@ package modules import ( "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/modulebase" @@ -30,22 +31,18 @@ var Policies SPolicyManager func policyReadFilter(session *mcclient.ClientSession, s jsonutils.JSONObject, query jsonutils.JSONObject) (jsonutils.JSONObject, error) { ss := s.(*jsonutils.JSONDict) - ret := ss.CopyIncludes("id", "type", "enabled", "domain_id", "domain", "project_domain", "can_update", "can_delete", "is_public", "description", "delete_fail_reason", "update_fail_reason", "public_scope", "shared_domains", "created_at", "updated_at") + ret := ss.CopyExcludes("blob", "type") blobJson, _ := ss.Get("blob") if blobJson != nil { - policy := rbacutils.SRbacPolicy{} blobStr, _ := blobJson.GetString() if len(blobStr) > 0 { blobJson, _ = jsonutils.ParseString(blobStr) } - err := policy.Decode(blobJson) + policy, err := rbacutils.DecodePolicyData(blobJson) if err != nil { - return nil, err - } - blobJson, err = policy.Encode() - if err != nil { - return nil, err + return nil, errors.Wrap(err, "rbacutils.DecodePolicyData") } + blobJson = policy.EncodeData() var format string if query != nil { format, _ = query.GetString("format") @@ -82,7 +79,7 @@ func policyWriteFilter(session *mcclient.ClientSession, s jsonutils.JSONObject, ret.Add(blobJson, "blob") } for _, k := range []string{ - "type", "enabled", "domain", "domain_id", "project_domain", "description", "is_public", "public_scope", "shared_domains", + "name", "type", "enabled", "domain", "domain_id", "project_domain", "description", "is_public", "public_scope", "shared_domains", "scope", "is_system", } { if s.Contains(k) { val, err := s.Get(k) @@ -97,14 +94,15 @@ func policyWriteFilter(session *mcclient.ClientSession, s jsonutils.JSONObject, func init() { Policies = SPolicyManager{NewIdentityV3Manager( - "policy", "policies", - []string{"id", "type", "policy", "enabled", - "domain_id", "domain", "project_domain", - "is_public", "description", + "policy", + "policies", + []string{"id", "name", "policy", "scope", "enabled", + "domain_id", "domain", "project_domain", "public_scope", + "is_public", "description", "is_system", }, []string{})} - Policies.SetReadFilter(policyReadFilter).SetWriteFilter(policyWriteFilter).SetNameField("type") + Policies.SetReadFilter(policyReadFilter).SetWriteFilter(policyWriteFilter) // .SetNameField("type") register(&Policies) } diff --git a/pkg/mcclient/modules/mod_rolepolicies.go b/pkg/mcclient/modules/mod_rolepolicies.go new file mode 100644 index 0000000000..82358cc4d0 --- /dev/null +++ b/pkg/mcclient/modules/mod_rolepolicies.go @@ -0,0 +1,67 @@ +// 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 modules + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/identity" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modulebase" +) + +type SRolePolicyManager struct { + modulebase.ResourceManager +} + +var RolePolicies SRolePolicyManager + +func (manager *SRolePolicyManager) FetchMatchedPolicies(s *mcclient.ClientSession, roleIds []string, projectId string, loginIp string) (map[string][]string, error) { + input := api.RolePolicyListInput{} + input.RoleIds = roleIds + input.ProjectId = projectId + limit := 2048 + input.Limit = &limit + details := true + input.Details = &details + results, err := manager.List(s, jsonutils.Marshal(input)) + if err != nil { + return nil, errors.Wrap(err, "List RolePolicyManager") + } + ret := make(map[string][]string) + for i := range results.Data { + policy, _ := results.Data[i].GetString("policy") + scope, _ := results.Data[i].GetString("scope") + if policies, ok := ret[scope]; !ok { + ret[scope] = []string{policy} + } else { + ret[scope] = append(policies, policy) + } + } + + return ret, nil +} + +func init() { + RolePolicies = SRolePolicyManager{NewIdentityV3Manager( + "rolepolicy", + "rolepolicies", + []string{"role", "role_id", "project", "project_id", "policy", "policy_id", "ips", "scope"}, + []string{}, + )} + + register(&RolePolicies) +} diff --git a/pkg/mcclient/token.go b/pkg/mcclient/token.go index 8557c0203f..2194fe5227 100644 --- a/pkg/mcclient/token.go +++ b/pkg/mcclient/token.go @@ -74,6 +74,7 @@ type TokenCredential interface { GetTokenString() string GetRoles() []string + GetRoleIds() []string GetExpires() time.Time IsValid() bool ValidDuration() time.Duration diff --git a/pkg/mcclient/token2.go b/pkg/mcclient/token2.go index 044fc76c49..bb94ef1a78 100644 --- a/pkg/mcclient/token2.go +++ b/pkg/mcclient/token2.go @@ -51,6 +51,8 @@ type KeystoneServiceV2 struct { type KeystoneRoleV2 struct { // 角色名称 Name string `json:"name"` + // 角色ID + Id string `json:"id"` } type KeystoneUserV2 struct { @@ -168,6 +170,14 @@ func (token *TokenCredentialV2) GetRoles() []string { return roles } +func (token *TokenCredentialV2) GetRoleIds() []string { + roles := make([]string, 0) + for i := 0; i < len(token.User.Roles); i++ { + roles = append(roles, token.User.Roles[i].Id) + } + return roles +} + func (this *TokenCredentialV2) GetExpires() time.Time { return this.Token.Expires } diff --git a/pkg/mcclient/token3.go b/pkg/mcclient/token3.go index 0daf26823a..4d698623d4 100644 --- a/pkg/mcclient/token3.go +++ b/pkg/mcclient/token3.go @@ -202,6 +202,14 @@ func (token *TokenCredentialV3) GetRoles() []string { return roles } +func (token *TokenCredentialV3) GetRoleIds() []string { + roles := make([]string, 0) + for i := 0; i < len(token.Token.Roles); i++ { + roles = append(roles, token.Token.Roles[i].Id) + } + return roles +} + func (this *TokenCredentialV3) GetExpires() time.Time { return this.Token.ExpiresAt } diff --git a/pkg/mcclient/tokensimple.go b/pkg/mcclient/tokensimple.go index 1ae26f6f87..d92e69f390 100644 --- a/pkg/mcclient/tokensimple.go +++ b/pkg/mcclient/tokensimple.go @@ -40,6 +40,7 @@ type SSimpleToken struct { ProjectDomainId string Roles string + RoleIds string Expires time.Time Context SAuthContext @@ -99,6 +100,10 @@ func (self *SSimpleToken) GetRoles() []string { return strings.Split(self.Roles, ",") } +func (self *SSimpleToken) GetRoleIds() []string { + return strings.Split(self.RoleIds, ",") +} + func (self *SSimpleToken) GetExpires() time.Time { return self.Expires } @@ -194,6 +199,7 @@ func SimplifyToken(token TokenCredential) TokenCredential { ProjectDomainId: token.GetProjectDomainId(), Roles: strings.Join(token.GetRoles(), ","), + RoleIds: strings.Join(token.GetRoleIds(), ","), Expires: token.GetExpires(), Context: SAuthContext{ Source: token.GetLoginSource(), diff --git a/pkg/util/rbacutils/ipmatch.go b/pkg/util/rbacutils/ipmatch.go new file mode 100644 index 0000000000..4920762f0d --- /dev/null +++ b/pkg/util/rbacutils/ipmatch.go @@ -0,0 +1,62 @@ +// 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 rbacutils + +import ( + "strings" + + "yunion.io/x/pkg/util/netutils" +) + +const ( + IP_PREFIX_SEP = "," +) + +func getPrefixes(prefstr string) []netutils.IPV4Prefix { + if len(prefstr) == 0 { + return nil + } + prefs := strings.Split(prefstr, IP_PREFIX_SEP) + ret := make([]netutils.IPV4Prefix, 0) + for _, pref := range prefs { + p, err := netutils.NewIPV4Prefix(pref) + if err != nil { + continue + } + ret = append(ret, p) + } + return ret +} + +func MatchIPStrings(prefstr string, ipstr string) bool { + prefs := getPrefixes(prefstr) + return matchIP(prefs, ipstr) +} + +func matchIP(prefs []netutils.IPV4Prefix, ipstr string) bool { + if len(prefs) == 0 { + return true + } + ip, err := netutils.NewIPV4Addr(ipstr) + if err != nil { + return false + } + for _, pref := range prefs { + if pref.Contains(ip) { + return true + } + } + return false +} diff --git a/pkg/util/rbacutils/ipmatch_test.go b/pkg/util/rbacutils/ipmatch_test.go new file mode 100644 index 0000000000..dc339412f2 --- /dev/null +++ b/pkg/util/rbacutils/ipmatch_test.go @@ -0,0 +1,57 @@ +// 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 rbacutils + +import "testing" + +func TestMatchIPStrings(t *testing.T) { + cases := []struct { + prefixes string + ip string + want bool + }{ + { + prefixes: "", + ip: "127.0.0.1", + want: true, + }, + { + prefixes: "10.0.0.0/8", + ip: "10.8.0.1", + want: true, + }, + { + prefixes: "10.0.0.0/8,192.168.0.0/16", + ip: "172.16.0.23", + want: false, + }, + { + prefixes: "10.0.0.0/8,192.168.0.0/16", + ip: "10.16.0.23", + want: true, + }, + { + prefixes: "10.0.0.0/8,192.168.0.0/16", + ip: "192.168.0.23", + want: true, + }, + } + for _, c := range cases { + got := MatchIPStrings(c.prefixes, c.ip) + if got != c.want { + t.Errorf("prefix %s ip %s got %v want %v", c.prefixes, c.ip, got, c.want) + } + } +} diff --git a/pkg/util/rbacutils/policy.go b/pkg/util/rbacutils/policy.go new file mode 100644 index 0000000000..d5461e5488 --- /dev/null +++ b/pkg/util/rbacutils/policy.go @@ -0,0 +1,257 @@ +// 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 rbacutils + +import ( + "regexp" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/netutils" +) + +type SRbacPolicy struct { + // condition, when the policy takes effects + // Deprecated + Condition string + + DomainId string + + IsPublic bool + + PublicScope TRbacScope + + SharedDomainIds []string + + Projects []string + + Roles []string + + Ips []netutils.IPV4Prefix + + Auth bool // whether needs authentication + + // scope, the scope of the policy, system/domain/project + Scope TRbacScope + // Deprecated + // is_admin=true means scope=system, is_admin=false means scope=project + IsAdmin bool + + Rules TPolicy +} + +var ( + tenantEqualsPattern = regexp.MustCompile(`tenant\s*==\s*['"]?(\w+)['"]?`) + roleContainsPattern = regexp.MustCompile(`roles.contains\(['"]?(\w+)['"]?\)`) +) + +func searchMatchStrings(pattern *regexp.Regexp, condstr string) []string { + ret := make([]string, 0) + matches := pattern.FindAllStringSubmatch(condstr, -1) + for _, match := range matches { + ret = append(ret, match[1]) + } + return ret +} + +func searchMatchTenants(condstr string) []string { + return searchMatchStrings(tenantEqualsPattern, condstr) +} + +func searchMatchRoles(condstr string) []string { + return searchMatchStrings(roleContainsPattern, condstr) +} + +func (policy *SRbacPolicy) Decode(policyJson jsonutils.JSONObject) error { + policy.Condition, _ = policyJson.GetString("condition") + if policyJson.Contains("projects") { + projectJson, _ := policyJson.GetArray("projects") + policy.Projects = jsonutils.JSONArray2StringArray(projectJson) + } + if policyJson.Contains("roles") { + roleJson, _ := policyJson.GetArray("roles") + policy.Roles = jsonutils.JSONArray2StringArray(roleJson) + } + + if len(policy.Projects) == 0 && len(policy.Roles) == 0 && len(policy.Condition) > 0 { + // XXX hack + // for smooth transtion from condition to projects&roles + policy.Projects = searchMatchTenants(policy.Condition) + policy.Roles = searchMatchRoles(policy.Condition) + } + // empty condition, no longer use this field + policy.Condition = "" + + if policyJson.Contains("ips") { + ipsJson, _ := policyJson.GetArray("ips") + ipStrs := jsonutils.JSONArray2StringArray(ipsJson) + policy.Ips = make([]netutils.IPV4Prefix, 0) + for _, ipStr := range ipStrs { + if len(ipStr) == 0 || ipStr == "0.0.0.0" { + continue + } + prefix, err := netutils.NewIPV4Prefix(ipStr) + if err != nil { + continue + } + policy.Ips = append(policy.Ips, prefix) + } + } + + policy.Auth = jsonutils.QueryBoolean(policyJson, "auth", true) + if len(policy.Ips) > 0 || len(policy.Roles) > 0 || len(policy.Projects) > 0 { + policy.Auth = true + } + + scopeStr, _ := policyJson.GetString("scope") + if len(scopeStr) > 0 { + policy.Scope = TRbacScope(scopeStr) + } else { + policy.IsAdmin = jsonutils.QueryBoolean(policyJson, "is_admin", false) + if len(policy.Scope) == 0 { + if policy.IsAdmin { + policy.Scope = ScopeSystem + } else { + policy.Scope = ScopeProject + } + } + } + + policyBody, err := policyJson.Get("policy") + if err != nil { + return errors.Wrap(err, "Get policy") + } + policy.Rules, err = DecodePolicy(policyBody) + if err != nil { + return errors.Wrap(err, "DecodePolicy") + } + + return nil +} + +func (policy *SRbacPolicy) Encode() jsonutils.JSONObject { + ret := jsonutils.NewDict() + + if !policy.Auth && len(policy.Projects) == 0 && len(policy.Roles) == 0 && len(policy.Ips) == 0 { + ret.Add(jsonutils.JSONFalse, "auth") + } else { + ret.Add(jsonutils.JSONTrue, "auth") + } + + if len(policy.Projects) > 0 { + ret.Add(jsonutils.NewStringArray(policy.Projects), "projects") + } + if len(policy.Roles) > 0 { + ret.Add(jsonutils.NewStringArray(policy.Roles), "roles") + } + if len(policy.Ips) > 0 { + ipStrs := make([]string, len(policy.Ips)) + for i := range policy.Ips { + ipStrs[i] = policy.Ips[i].String() + } + ret.Add(jsonutils.NewStringArray(ipStrs), "ips") + } + + ret.Add(jsonutils.NewString(string(policy.Scope)), "scope") + + ret.Add(policy.Rules.Encode(), "policy") + + return ret +} + +func (policy *SRbacPolicy) IsSystemWidePolicy() bool { + return (len(policy.DomainId) == 0 || (policy.IsPublic && policy.PublicScope == ScopeSystem)) && len(policy.Roles) == 0 && len(policy.Projects) == 0 +} + +func (policy *SRbacPolicy) MatchDomain(domainId string) bool { + if len(policy.DomainId) == 0 || len(domainId) == 0 { + return true + } + if policy.DomainId == domainId { + return true + } + if policy.IsPublic { + if policy.PublicScope == ScopeSystem { + return true + } + if contains(policy.SharedDomainIds, domainId) { + return true + } + } + return false +} + +func (policy *SRbacPolicy) MatchProject(projectName string) bool { + if len(policy.Projects) == 0 || len(projectName) == 0 { + return true + } + if contains(policy.Projects, projectName) { + return true + } + return false +} + +func (policy *SRbacPolicy) MatchRoles(roleNames []string) bool { + if len(policy.Roles) == 0 { + return true + } + if intersect(policy.Roles, roleNames) { + return true + } + return false +} + +// check whether policy maches a userCred +// return value +// bool isMatched +// int match weight, the higher the value, the more exact the match +// the more exact match wins +func (policy *SRbacPolicy) Match(userCred IRbacIdentity2) (bool, int) { + if !policy.Auth && len(policy.Roles) == 0 && len(policy.Projects) == 0 && len(policy.Ips) == 0 { + return true, 1 + } + if userCred == nil || len(userCred.GetTokenString()) == 0 { + return false, 0 + } + weight := 0 + if policy.MatchDomain(userCred.GetProjectDomainId()) { + if len(policy.DomainId) > 0 { + if policy.DomainId == userCred.GetProjectDomainId() { + weight += 30 // exact domain match + } else if len(policy.SharedDomainIds) > 0 { + weight += 20 // shared domain match + } else { + weight += 10 // else, system scope match + } + } + if policy.MatchRoles(userCred.GetRoles()) { + if len(policy.Roles) != 0 { + weight += 100 + } + if policy.MatchProject(userCred.GetProjectName()) { + if len(policy.Projects) > 0 { + weight += 1000 + } + if len(policy.Ips) == 0 || containsIp(policy.Ips, userCred.GetLoginIp()) { + if len(policy.Ips) > 0 { + weight += 10000 + } + return true, weight + } + } + } + } + return false, 0 +} diff --git a/pkg/util/rbacutils/policycore.go b/pkg/util/rbacutils/policycore.go new file mode 100644 index 0000000000..c60cee5a30 --- /dev/null +++ b/pkg/util/rbacutils/policycore.go @@ -0,0 +1,93 @@ +// 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 rbacutils + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/httperrors" +) + +type TPolicy []SRbacRule + +func (policy TPolicy) getMatchRule(req []string) *SRbacRule { + service := WILD_MATCH + if len(req) > levelService { + service = req[levelService] + } + resource := WILD_MATCH + if len(req) > levelResource { + resource = req[levelResource] + } + action := WILD_MATCH + if len(req) > levelAction { + action = req[levelAction] + } + var extra []string + if len(req) > levelExtra { + extra = req[levelExtra:] + } else { + extra = make([]string, 0) + } + + return policy.GetMatchRule(service, resource, action, extra...) +} + +func (policy TPolicy) GetMatchRule(service string, resource string, action string, extra ...string) *SRbacRule { + return GetMatchRule(policy, service, resource, action, extra...) +} + +func DecodePolicy(policyJson jsonutils.JSONObject) (TPolicy, error) { + rules, err := json2Rules(policyJson) + if err != nil { + return nil, errors.Wrap(err, "json2Rules") + } + if len(rules) == 0 { + return nil, ErrEmptyPolicy + } + return rules, nil +} + +func DecodePolicyData(input jsonutils.JSONObject) (TPolicy, error) { + policyData, err := input.Get("policy") + if err != nil || policyData == nil { + return nil, errors.Wrap(httperrors.ErrInvalidFormat, "invalid policy data") + } + return DecodePolicy(policyData) +} + +func (policy TPolicy) Encode() jsonutils.JSONObject { + return rules2Json(policy) +} + +func (policy TPolicy) EncodeData() jsonutils.JSONObject { + ret := jsonutils.NewDict() + ret.Add(policy.Encode(), "policy") + return ret +} + +func (policy TPolicy) Explain(request [][]string) [][]string { + output := make([][]string, len(request)) + for i := 0; i < len(request); i += 1 { + rule := policy.getMatchRule(request[i]) + if rule == nil { + output[i] = append(request[i], string(Deny)) + } else { + output[i] = append(request[i], string(rule.Result)) + } + } + return output +} diff --git a/pkg/util/rbacutils/policygroup.go b/pkg/util/rbacutils/policygroup.go new file mode 100644 index 0000000000..342ee71a6a --- /dev/null +++ b/pkg/util/rbacutils/policygroup.go @@ -0,0 +1,88 @@ +// 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 rbacutils + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/httperrors" +) + +/* +type SPolicyInfo struct { + Id string `json:"id"` + Name string `json:"name"` + Enabled bool `json:"enabled"` + DomainId string `json:"domain_id"` + IsPublic bool `json:"is_public"` + PublicScope string `json:"public_scope"` + SharedDomainIds []string `json:"shared_domain_ids"` + Scope TRbacScope `json:"scope"` + Policy *SRbacPolicyCore `json:"policy"` +} + +func GetMatchedPolicies(policies []SPolicyInfo, userCred IRbacIdentity) (TPolicySet, []string) { + matchedPolicies := make([]*SRbacPolicyCore, 0) + matchedNames := make([]string, 0) + for i := range policies { + isMatched, _ := policies[i].Policy.Match(userCred) + if !isMatched { + continue + } + matchedPolicies = append(matchedPolicies, policies[i].Policy) + matchedNames = append(matchedNames, policies[i].Name) + } + return matchedPolicies, matchedNames +}*/ + +type TPolicyGroup map[TRbacScope]TPolicySet + +func DecodePolicyGroup(json jsonutils.JSONObject) (TPolicyGroup, error) { + jmap, err := json.GetMap() + if err != nil { + return nil, errors.Wrap(httperrors.ErrInvalidFormat, "invalid json: not a map") + } + group := TPolicyGroup{} + for k := range jmap { + group[TRbacScope(k)], err = DecodePolicySet(jmap[k]) + if err != nil { + return nil, errors.Wrapf(err, "decode %s", k) + } + } + return group, nil +} + +func (sets TPolicyGroup) HighestScope() TRbacScope { + for _, s := range []TRbacScope{ + ScopeSystem, + ScopeDomain, + ScopeProject, + ScopeUser, + } { + if _, ok := sets[s]; ok { + return s + } + } + return ScopeNone +} + +func (sets TPolicyGroup) Encode() jsonutils.JSONObject { + j := jsonutils.NewDict() + for k := range sets { + j.Set(string(k), sets[k].Encode()) + } + return j +} diff --git a/pkg/util/rbacutils/policyset.go b/pkg/util/rbacutils/policyset.go index d3e072752d..8bb6805243 100644 --- a/pkg/util/rbacutils/policyset.go +++ b/pkg/util/rbacutils/policyset.go @@ -14,27 +14,14 @@ package rbacutils -type SPolicyInfo struct { - Id string - Name string - Policy *SRbacPolicy -} +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" -type TPolicySet []*SRbacPolicy + "yunion.io/x/onecloud/pkg/httperrors" +) -func GetMatchedPolicies(policies []SPolicyInfo, userCred IRbacIdentity) (TPolicySet, []string) { - matchedPolicies := make([]*SRbacPolicy, 0) - matchedNames := make([]string, 0) - for i := range policies { - isMatched, _ := policies[i].Policy.Match(userCred) - if !isMatched { - continue - } - matchedPolicies = append(matchedPolicies, policies[i].Policy) - matchedNames = append(matchedNames, policies[i].Name) - } - return matchedPolicies, matchedNames -} +type TPolicySet []TPolicy func (policies TPolicySet) GetMatchRules(service string, resource string, action string, extra ...string) []SRbacRule { matchRules := make([]SRbacRule, 0) @@ -47,6 +34,30 @@ func (policies TPolicySet) GetMatchRules(service string, resource string, action return matchRules } +func DecodePolicySet(jsonObj jsonutils.JSONObject) (TPolicySet, error) { + jsonArr, err := jsonObj.GetArray() + if err != nil { + return nil, errors.Wrap(httperrors.ErrInvalidFormat, "invalid json: not an array") + } + set := TPolicySet{} + for i := range jsonArr { + policy, err := DecodePolicy(jsonArr[i]) + if err != nil { + return nil, errors.Wrapf(err, "decode %d", i) + } + set = append(set, policy) + } + return set, nil +} + +func (policies TPolicySet) Encode() jsonutils.JSONObject { + obj := make([]jsonutils.JSONObject, len(policies)) + for i := range policies { + obj[i] = policies[i].Encode() + } + return jsonutils.NewArray(obj...) +} + // ViolatedBy: policies中deny的权限,但是assign中却是allow // if any assign allow, but policies deny // OR @@ -70,9 +81,9 @@ func (policies TPolicySet) violatedBySet(assign TPolicySet, expect TRbacResult) return false } -func (policies TPolicySet) violatedByPolicy(policy *SRbacPolicy, expect TRbacResult) bool { - for i := range policy.Rules { - rule := policy.Rules[i] +func (policies TPolicySet) violatedByPolicy(policy TPolicy, expect TRbacResult) bool { + for i := range policy { + rule := policy[i] if rule.Result != expect { continue } diff --git a/pkg/util/rbacutils/policyset_test.go b/pkg/util/rbacutils/policyset_test.go index e9fbab8321..83e654849f 100644 --- a/pkg/util/rbacutils/policyset_test.go +++ b/pkg/util/rbacutils/policyset_test.go @@ -26,32 +26,28 @@ func TestTPolicySet_Violate(t *testing.T) { { name: "case1", p1: TPolicySet{ - &SRbacPolicy{ - Rules: []SRbacRule{ - { - Service: "compute", - Resource: "servers", - Action: "list", - Result: Deny, - }, - { - Service: "compute", - Resource: "servers", - Action: WILD_MATCH, - Result: Allow, - }, + { + { + Service: "compute", + Resource: "servers", + Action: "list", + Result: Deny, + }, + { + Service: "compute", + Resource: "servers", + Action: WILD_MATCH, + Result: Allow, }, }, }, p2: TPolicySet{ - &SRbacPolicy{ - Rules: []SRbacRule{ - { - Service: "compute", - Resource: "servers", - Action: WILD_MATCH, - Result: Allow, - }, + { + { + Service: "compute", + Resource: "servers", + Action: WILD_MATCH, + Result: Allow, }, }, }, @@ -60,40 +56,34 @@ func TestTPolicySet_Violate(t *testing.T) { { name: "case2", p1: TPolicySet{ - &SRbacPolicy{ - Rules: []SRbacRule{ - { - Service: "comptue", - Resource: "servers", - Action: "list", - Result: Deny, - }, - { - Service: "compute", - Resource: "servers", - Action: WILD_MATCH, - Result: Allow, - }, + { + { + Service: "comptue", + Resource: "servers", + Action: "list", + Result: Deny, + }, + { + Service: "compute", + Resource: "servers", + Action: WILD_MATCH, + Result: Allow, }, }, }, p2: TPolicySet{ - &SRbacPolicy{ - Rules: []SRbacRule{ - { - Service: WILD_MATCH, - Result: Allow, - }, + { + { + Service: WILD_MATCH, + Result: Allow, }, }, - &SRbacPolicy{ - Rules: []SRbacRule{ - { - Service: "compute", - Resource: "servers", - Action: "list", - Result: Deny, - }, + { + { + Service: "compute", + Resource: "servers", + Action: "list", + Result: Deny, }, }, }, @@ -102,60 +92,52 @@ func TestTPolicySet_Violate(t *testing.T) { { name: "case3", p1: TPolicySet{ - &SRbacPolicy{ - Rules: []SRbacRule{ - { - Service: WILD_MATCH, - Result: Allow, - }, - { - Service: "compute", - Resource: "servers", - Action: "create", - Result: Deny, - }, + { + { + Service: WILD_MATCH, + Result: Allow, + }, + { + Service: "compute", + Resource: "servers", + Action: "create", + Result: Deny, }, }, - &SRbacPolicy{ - Rules: []SRbacRule{ - { - Service: "comptue", - Resource: "servers", - Action: "list", - Result: Deny, - }, - { - Service: "compute", - Resource: "servers", - Action: WILD_MATCH, - Result: Allow, - }, + { + { + Service: "comptue", + Resource: "servers", + Action: "list", + Result: Deny, + }, + { + Service: "compute", + Resource: "servers", + Action: WILD_MATCH, + Result: Allow, }, }, }, p2: TPolicySet{ - &SRbacPolicy{ - Rules: []SRbacRule{ - { - Service: WILD_MATCH, - Result: Deny, - }, + { + { + Service: WILD_MATCH, + Result: Deny, }, }, - &SRbacPolicy{ - Rules: []SRbacRule{ - { - Service: "comptue", - Resource: "servers", - Action: WILD_MATCH, - Result: Deny, - }, - { - Service: "compute", - Resource: "servers", - Action: "get", - Result: Allow, - }, + { + { + Service: "comptue", + Resource: "servers", + Action: WILD_MATCH, + Result: Deny, + }, + { + Service: "compute", + Resource: "servers", + Action: "get", + Result: Allow, }, }, }, @@ -164,30 +146,24 @@ func TestTPolicySet_Violate(t *testing.T) { { name: "case4", p2: TPolicySet{ - &SRbacPolicy{ - Scope: ScopeDomain, - Rules: []SRbacRule{ - { - Service: WILD_MATCH, - Result: Allow, - }, + { + { + Service: WILD_MATCH, + Result: Allow, }, }, }, p1: TPolicySet{ - &SRbacPolicy{ - Scope: ScopeDomain, - Rules: []SRbacRule{ - { - Service: WILD_MATCH, - Result: Allow, - }, - { - Service: "compute", - Resource: "servers", - Action: "list", - Result: Deny, - }, + { + { + Service: WILD_MATCH, + Result: Allow, + }, + { + Service: "compute", + Resource: "servers", + Action: "list", + Result: Deny, }, }, }, diff --git a/pkg/util/rbacutils/rbac.go b/pkg/util/rbacutils/rbac.go index 506d144c94..4220360403 100644 --- a/pkg/util/rbacutils/rbac.go +++ b/pkg/util/rbacutils/rbac.go @@ -15,12 +15,9 @@ package rbacutils import ( - "regexp" "strings" - "yunion.io/x/jsonutils" "yunion.io/x/log" - "yunion.io/x/pkg/errors" "yunion.io/x/pkg/util/netutils" ) @@ -87,32 +84,6 @@ func (s1 TRbacScope) HigherThan(s2 TRbacScope) bool { return scopeScore[s1] > scopeScore[s2] } -type SRbacPolicy struct { - // condition, when the policy takes effects - // Deprecated - Condition string - - DomainId string - - IsPublic bool - PublicScope TRbacScope - SharedDomainIds []string - - Projects []string - Roles []string - Ips []netutils.IPV4Prefix - Auth bool // whether needs authentication - - // scope, the scope of the policy, system/domain/project - Scope TRbacScope - // Deprecated - // is_admin=true means scope=system, is_admin=false means scope=project - IsAdmin bool - - // rules, the exact rules - Rules []SRbacRule -} - type SRbacRule struct { Service string Resource string @@ -201,33 +172,6 @@ func (rule *SRbacRule) match(service string, resource string, action string, ext return true, matched, weight } -func (policy *SRbacPolicy) getMatchRule(req []string) *SRbacRule { - service := WILD_MATCH - if len(req) > levelService { - service = req[levelService] - } - resource := WILD_MATCH - if len(req) > levelResource { - resource = req[levelResource] - } - action := WILD_MATCH - if len(req) > levelAction { - action = req[levelAction] - } - var extra []string - if len(req) > levelExtra { - extra = req[levelExtra:] - } else { - extra = make([]string, 0) - } - - return policy.GetMatchRule(service, resource, action, extra...) -} - -func (policy *SRbacPolicy) GetMatchRule(service string, resource string, action string, extra ...string) *SRbacRule { - return GetMatchRule(policy.Rules, service, resource, action, extra...) -} - var ( ShowMatchRuleDebug = false ) @@ -252,103 +196,6 @@ func GetMatchRule(rules []SRbacRule, service string, resource string, action str return matchRule } -var ( - tenantEqualsPattern = regexp.MustCompile(`tenant\s*==\s*['"]?(\w+)['"]?`) - roleContainsPattern = regexp.MustCompile(`roles.contains\(['"]?(\w+)['"]?\)`) -) - -func searchMatchStrings(pattern *regexp.Regexp, condstr string) []string { - ret := make([]string, 0) - matches := pattern.FindAllStringSubmatch(condstr, -1) - for _, match := range matches { - ret = append(ret, match[1]) - } - return ret -} - -func searchMatchTenants(condstr string) []string { - return searchMatchStrings(tenantEqualsPattern, condstr) -} - -func searchMatchRoles(condstr string) []string { - return searchMatchStrings(roleContainsPattern, condstr) -} - -func (policy *SRbacPolicy) Decode(policyJson jsonutils.JSONObject) error { - policy.Condition, _ = policyJson.GetString("condition") - if policyJson.Contains("projects") { - projectJson, _ := policyJson.GetArray("projects") - policy.Projects = jsonutils.JSONArray2StringArray(projectJson) - } - if policyJson.Contains("roles") { - roleJson, _ := policyJson.GetArray("roles") - policy.Roles = jsonutils.JSONArray2StringArray(roleJson) - } - - if len(policy.Projects) == 0 && len(policy.Roles) == 0 && len(policy.Condition) > 0 { - // XXX hack - // for smooth transtion from condition to projects&roles - policy.Projects = searchMatchTenants(policy.Condition) - policy.Roles = searchMatchRoles(policy.Condition) - } - // empty condition, no longer use this field - policy.Condition = "" - - scopeStr, _ := policyJson.GetString("scope") - if len(scopeStr) > 0 { - policy.Scope = TRbacScope(scopeStr) - } else { - policy.IsAdmin = jsonutils.QueryBoolean(policyJson, "is_admin", false) - if len(policy.Scope) == 0 { - if policy.IsAdmin { - policy.Scope = ScopeSystem - } else { - policy.Scope = ScopeProject - } - } - } - - if policyJson.Contains("ips") { - ipsJson, _ := policyJson.GetArray("ips") - ipStrs := jsonutils.JSONArray2StringArray(ipsJson) - policy.Ips = make([]netutils.IPV4Prefix, 0) - for _, ipStr := range ipStrs { - if len(ipStr) == 0 || ipStr == "0.0.0.0" { - continue - } - prefix, err := netutils.NewIPV4Prefix(ipStr) - if err != nil { - continue - } - policy.Ips = append(policy.Ips, prefix) - } - } - - policy.Auth = jsonutils.QueryBoolean(policyJson, "auth", true) - if len(policy.Ips) > 0 || len(policy.Roles) > 0 || len(policy.Projects) > 0 { - policy.Auth = true - } - - ruleJson, err := policyJson.Get("policy") - if err != nil { - return err - } - - /*rules, err := decode(ruleJson, SRbacRule{}, levelService)*/ - rules, err := json2Rules(ruleJson) - if err != nil { - return errors.Wrap(err, "json2Rules") - } - - if len(rules) == 0 { - return ErrEmptyPolicy - } - - policy.Rules = rules - - return nil -} - const ( levelService = 0 levelResource = 1 @@ -356,55 +203,6 @@ const ( levelExtra = 3 ) -func decode(rules jsonutils.JSONObject, decodeRule SRbacRule, level int) ([]SRbacRule, error) { - switch rules.(type) { - case *jsonutils.JSONString: - ruleJsonStr := rules.(*jsonutils.JSONString) - ruleStr, _ := ruleJsonStr.GetString() - switch ruleStr { - case string(Allow), string(AdminAllow), string(OwnerAllow), string(UserAllow), string(GuestAllow): - decodeRule.Result = Allow - default: - decodeRule.Result = Deny - // default: - // return nil, fmt.Errorf("unsupported rule string %s", ruleStr) - } - return []SRbacRule{decodeRule}, nil - case *jsonutils.JSONDict: - ruleJsonDict, err := rules.GetMap() - if err != nil { - return nil, errors.Wrap(err, "get rule map fail") - } - rules := make([]SRbacRule, 0) - for key, ruleJson := range ruleJsonDict { - rule := decodeRule - switch { - case level == levelService: - rule.Service = key - case level == levelResource: - rule.Resource = key - case level == levelAction: - rule.Action = key - case level >= levelExtra: - if rule.Extra == nil { - rule.Extra = make([]string, 1) - rule.Extra[0] = key - } else { - rule.Extra = append(rule.Extra, key) - } - } - decoded, err := decode(ruleJson, rule, level+1) - if err != nil { - return nil, errors.Wrap(err, "decode") - } - rules = append(rules, decoded...) - } - return rules, nil - default: - return nil, errors.Wrap(ErrUnsuportRuleData, rules.String()) - } -} - func (rule *SRbacRule) toStringArray() []string { strArr := make([]string, 0) strArr = append(strArr, rule.Service) @@ -420,106 +218,6 @@ func (rule *SRbacRule) toStringArray() []string { return strArr[0 : i+1] } -func addRule2Json(nodeJson *jsonutils.JSONDict, keys []string, result TRbacResult) error { - if len(keys) == 1 { - if nodeJson.Contains(keys[0]) { - nextJson, _ := nodeJson.Get(keys[0]) - switch nextJson.(type) { - case *jsonutils.JSONString: // conflict?? - return ErrConflict // fmt.Errorf("conflict?") - case *jsonutils.JSONDict: - nextJsonDict := nextJson.(*jsonutils.JSONDict) - addRule2Json(nextJsonDict, []string{WILD_MATCH}, result) - return nil - default: - return ErrInvalidRules // fmt.Errorf("invalid rules") - } - } else { - nodeJson.Add(jsonutils.NewString(string(result)), keys[0]) - return nil - } - } - // len(keys) > 1 - exist, _ := nodeJson.Get(keys[0]) - if exist != nil { - switch exist.(type) { - case *jsonutils.JSONString: // need restruct - newDict := jsonutils.NewDict() - newDict.Add(exist, "*") - nodeJson.Set(keys[0], newDict) - return addRule2Json(newDict, keys[1:], result) - case *jsonutils.JSONDict: - existDict := exist.(*jsonutils.JSONDict) - return addRule2Json(existDict, keys[1:], result) - default: - return ErrInvalidRules // fmt.Errorf("invalid rules") - } - } else { - next := jsonutils.NewDict() - nodeJson.Add(next, keys[0]) - return addRule2Json(next, keys[1:], result) - } -} - -func (policy *SRbacPolicy) Encode() (jsonutils.JSONObject, error) { - /*rules := jsonutils.NewDict() - for i := 0; i < len(policy.Rules); i += 1 { - keys := policy.Rules[i].toStringArray() - err := addRule2Json(rules, keys, policy.Rules[i].Result) - if err != nil { - return nil, errors.Wrap(err, "addRule2Json") - } - }*/ - - rules := rules2Json(policy.Rules) - - ret := jsonutils.NewDict() - // ret.Add(jsonutils.NewString(policy.Condition), "condition") - // if policy.IsAdmin { - // ret.Add(jsonutils.JSONTrue, "is_admin") - // } else { - // ret.Add(jsonutils.JSONFalse, "is_admin") - // } - - ret.Add(jsonutils.NewString(string(policy.Scope)), "scope") - - if !policy.Auth && len(policy.Projects) == 0 && len(policy.Roles) == 0 && len(policy.Ips) == 0 { - ret.Add(jsonutils.JSONFalse, "auth") - } else { - ret.Add(jsonutils.JSONTrue, "auth") - } - - if len(policy.Projects) > 0 { - ret.Add(jsonutils.NewStringArray(policy.Projects), "projects") - } - if len(policy.Roles) > 0 { - ret.Add(jsonutils.NewStringArray(policy.Roles), "roles") - } - if len(policy.Ips) > 0 { - ipStrs := make([]string, len(policy.Ips)) - for i := range policy.Ips { - ipStrs[i] = policy.Ips[i].String() - } - ret.Add(jsonutils.NewStringArray(ipStrs), "ips") - } - - ret.Add(rules, "policy") - return ret, nil -} - -func (policy *SRbacPolicy) Explain(request [][]string) [][]string { - output := make([][]string, len(request)) - for i := 0; i < len(request); i += 1 { - rule := policy.getMatchRule(request[i]) - if rule == nil { - output[i] = append(request[i], string(Deny)) - } else { - output[i] = append(request[i], string(rule.Result)) - } - } - return output -} - func contains(s1 []string, s string) bool { for i := range s1 { if s1[i] == s { @@ -558,7 +256,18 @@ func containsIp(ips []netutils.IPV4Prefix, ipStr string) bool { return false } +const ( + FAKE_TOKEN = "fake_token" +) + type IRbacIdentity interface { + GetProjectId() string + GetRoleIds() []string + GetLoginIp() string + GetTokenString() string +} + +type IRbacIdentity2 interface { GetProjectDomainId() string GetProjectName() string GetRoles() []string @@ -566,128 +275,57 @@ type IRbacIdentity interface { GetTokenString() string } -func (policy *SRbacPolicy) IsSystemWidePolicy() bool { - return (len(policy.DomainId) == 0 || (policy.IsPublic && policy.PublicScope == ScopeSystem)) && len(policy.Roles) == 0 && len(policy.Projects) == 0 -} - -func (policy *SRbacPolicy) MatchDomain(domainId string) bool { - if len(policy.DomainId) == 0 || len(domainId) == 0 { - return true - } - if policy.DomainId == domainId { - return true - } - if policy.IsPublic { - if policy.PublicScope == ScopeSystem { - return true - } - if contains(policy.SharedDomainIds, domainId) { - return true - } - } - return false -} - -func (policy *SRbacPolicy) MatchProject(projectName string) bool { - if len(policy.Projects) == 0 || len(projectName) == 0 { - return true - } - if contains(policy.Projects, projectName) { - return true - } - return false -} - -func (policy *SRbacPolicy) MatchRoles(roleNames []string) bool { - if len(policy.Roles) == 0 { - return true - } - if intersect(policy.Roles, roleNames) { - return true - } - return false -} - -// check whether policy maches a userCred -// return value -// bool isMatched -// int match weight, the higher the value, the more exact the match -// the more exact match wins -func (policy *SRbacPolicy) Match(userCred IRbacIdentity) (bool, int) { - if !policy.Auth && len(policy.Roles) == 0 && len(policy.Projects) == 0 && len(policy.Ips) == 0 { - return true, 1 - } - if userCred == nil || len(userCred.GetTokenString()) == 0 { - return false, 0 - } - weight := 0 - if policy.MatchDomain(userCred.GetProjectDomainId()) { - if len(policy.DomainId) > 0 { - if policy.DomainId == userCred.GetProjectDomainId() { - weight += 30 // exact domain match - } else if len(policy.SharedDomainIds) > 0 { - weight += 20 // shared domain match - } else { - weight += 10 // else, system scope match - } - } - if policy.MatchRoles(userCred.GetRoles()) { - if len(policy.Roles) != 0 { - weight += 100 - } - if policy.MatchProject(userCred.GetProjectName()) { - if len(policy.Projects) > 0 { - weight += 1000 - } - if len(policy.Ips) == 0 || containsIp(policy.Ips, userCred.GetLoginIp()) { - if len(policy.Ips) > 0 { - weight += 10000 - } - return true, weight - } - } - } - } - return false, 0 -} - type sSimpleRbacIdentity struct { - domainId string - projectName string - roleNames []string - loginIp string -} - -func (id sSimpleRbacIdentity) GetProjectDomainId() string { - return id.domainId + projectDomainId string + projectId string + projectName string + roleIds []string + roles []string + ip string } func (id sSimpleRbacIdentity) GetRoles() []string { - return id.roleNames + return id.roles +} + +func (id sSimpleRbacIdentity) GetRoleIds() []string { + return id.roleIds } func (id sSimpleRbacIdentity) GetProjectName() string { return id.projectName } +func (id sSimpleRbacIdentity) GetProjectId() string { + return id.projectId +} + +func (id sSimpleRbacIdentity) GetProjectDomainId() string { + return id.projectDomainId +} + func (id sSimpleRbacIdentity) GetLoginIp() string { - return id.loginIp + return id.ip } func (id sSimpleRbacIdentity) GetTokenString() string { - return "faketoken" + return FAKE_TOKEN } -func NewRbacIdentity(domainId, projectName string, roleNames []string) IRbacIdentity { - return NewRbacIdentity2(domainId, projectName, roleNames, "") -} - -func NewRbacIdentity2(domainId, projectName string, roleNames []string, loginIp string) IRbacIdentity { +func newRbacIdentity2(projectDomainId, projectName string, roles []string, ip string) IRbacIdentity2 { return sSimpleRbacIdentity{ - domainId: domainId, - projectName: projectName, - roleNames: roleNames, - loginIp: loginIp, + projectDomainId: projectDomainId, + projectName: projectName, + roles: roles, + ip: ip, + } +} + +func NewRbacIdentity(projectId string, roleIds []string, ip string) IRbacIdentity { + return sSimpleRbacIdentity{ + projectId: projectId, + roleIds: roleIds, + ip: ip, } } diff --git a/pkg/util/rbacutils/rbac_test.go b/pkg/util/rbacutils/rbac_test.go index 0455a7d048..d1b5b087cc 100644 --- a/pkg/util/rbacutils/rbac_test.go +++ b/pkg/util/rbacutils/rbac_test.go @@ -203,11 +203,7 @@ func TestSRabcPolicy_Encode(t *testing.T) { return } - policyJson1, err := policy.Encode() - if err != nil { - t.Errorf("encode error %s", err) - return - } + policyJson1 := policy.Encode() policy2 := SRbacPolicy{} @@ -217,11 +213,7 @@ func TestSRabcPolicy_Encode(t *testing.T) { return } - policyJson2, err := policy2.Encode() - if err != nil { - t.Errorf("encode error 2 %s", err) - return - } + policyJson2 := policy2.Encode() policyStr1 := policyJson1.PrettyString() policyStr2 := policyJson2.PrettyString() @@ -235,6 +227,7 @@ func TestSRabcPolicy_Encode(t *testing.T) { } } +/* func TestSRabcPolicy_Explain(t *testing.T) { policyStr := `{ "condition": "usercred.project != \"system\" && usercred.roles==\"projectowner\"", @@ -282,6 +275,7 @@ func TestSRabcPolicy_Explain(t *testing.T) { t.Logf("%#v", output) } +*/ func TestConditionParser(t *testing.T) { condition := `tenant=="system" && roles.contains("admin")` @@ -291,44 +285,16 @@ func TestConditionParser(t *testing.T) { t.Logf("%s", roles) } -type sRbacIdentity struct { - DomainId string - Project string - Roles []string - Ip string - Token string -} - -func (ri *sRbacIdentity) GetProjectDomainId() string { - return ri.DomainId -} - -func (ri *sRbacIdentity) GetProjectName() string { - return ri.Project -} - -func (ri *sRbacIdentity) GetRoles() []string { - return ri.Roles -} - -func (ri *sRbacIdentity) GetLoginIp() string { - return ri.Ip -} - -func (ri *sRbacIdentity) GetTokenString() string { - return ri.Token -} - func TestSRbacPolicyMatch(t *testing.T) { prefix, _ := netutils.NewIPV4Prefix("10.168.22.0/24") cases := []struct { policy SRbacPolicy - userCred IRbacIdentity + userCred IRbacIdentity2 want bool }{ { SRbacPolicy{}, - &sRbacIdentity{}, + newRbacIdentity2("", "", nil, ""), true, }, { @@ -340,20 +306,14 @@ func TestSRbacPolicyMatch(t *testing.T) { SRbacPolicy{ Projects: []string{"system"}, }, - &sRbacIdentity{ - Project: "system", - Token: "faketoken", - }, + newRbacIdentity2("", "system", nil, ""), true, }, { SRbacPolicy{ Projects: []string{"system"}, }, - &sRbacIdentity{ - Project: "demo", - Token: "faketoken", - }, + newRbacIdentity2("", "demo", nil, ""), false, }, { @@ -361,11 +321,7 @@ func TestSRbacPolicyMatch(t *testing.T) { Projects: []string{"system"}, Roles: []string{"admin"}, }, - &sRbacIdentity{ - Project: "system", - Roles: []string{"admin"}, - Token: "faketoken", - }, + newRbacIdentity2("", "system", []string{"admin"}, ""), true, }, { @@ -373,11 +329,7 @@ func TestSRbacPolicyMatch(t *testing.T) { Projects: []string{"system"}, Roles: []string{"admin"}, }, - &sRbacIdentity{ - Project: "system", - Roles: []string{"admin", "_member_"}, - Token: "faketoken", - }, + newRbacIdentity2("", "system", []string{"admin", "_member_"}, ""), true, }, { @@ -385,11 +337,7 @@ func TestSRbacPolicyMatch(t *testing.T) { Projects: []string{"system"}, Roles: []string{"admin"}, }, - &sRbacIdentity{ - Project: "system", - Roles: []string{"_member_"}, - Token: "faketoken", - }, + newRbacIdentity2("", "system", []string{"_member_"}, ""), false, }, { @@ -413,12 +361,7 @@ func TestSRbacPolicyMatch(t *testing.T) { Roles: []string{"admin"}, Ips: []netutils.IPV4Prefix{prefix}, }, - &sRbacIdentity{ - Project: "system", - Roles: []string{"admin"}, - Ip: "10.0.0.23", - Token: "faketoken", - }, + newRbacIdentity2("", "system", []string{"admin"}, "10.0.0.23"), false, }, { @@ -427,12 +370,7 @@ func TestSRbacPolicyMatch(t *testing.T) { Roles: []string{"admin"}, Ips: []netutils.IPV4Prefix{prefix}, }, - &sRbacIdentity{ - Project: "system", - Roles: []string{"admin"}, - Ip: "10.168.22.23", - Token: "faketoken", - }, + newRbacIdentity2("", "system", []string{"admin"}, "10.168.22.23"), true, }, { @@ -441,12 +379,7 @@ func TestSRbacPolicyMatch(t *testing.T) { Roles: []string{"admin"}, Ips: []netutils.IPV4Prefix{prefix}, }, - &sRbacIdentity{ - Project: "system", - Roles: []string{"_member_"}, - Ip: "10.168.22.23", - Token: "faketoken", - }, + newRbacIdentity2("", "system", []string{"_member_"}, "10.168.22.23"), false, }, { @@ -454,12 +387,7 @@ func TestSRbacPolicyMatch(t *testing.T) { Roles: []string{"admin"}, Ips: []netutils.IPV4Prefix{prefix}, }, - &sRbacIdentity{ - Project: "system", - Roles: []string{"_member_", "admin"}, - Ip: "10.168.22.23", - Token: "faketoken", - }, + newRbacIdentity2("", "system", []string{"_member_", "admin"}, "10.168.22.23"), true, }, { @@ -468,12 +396,7 @@ func TestSRbacPolicyMatch(t *testing.T) { Roles: []string{"admin", "_member_"}, Ips: []netutils.IPV4Prefix{prefix}, }, - &sRbacIdentity{ - Project: "system", - Roles: []string{"_member_", "projectowner"}, - Ip: "10.168.22.23", - Token: "faketoken", - }, + newRbacIdentity2("", "system", []string{"_member_", "projectowner"}, "10.168.22.23"), true, }, { @@ -482,11 +405,7 @@ func TestSRbacPolicyMatch(t *testing.T) { Roles: []string{"domain_admin"}, Auth: true, }, - &sRbacIdentity{ - Project: "ldapproj", - Roles: []string{"domain_admin"}, - Token: "faketoken", - }, + newRbacIdentity2("", "ldapproj", []string{"domain_admin"}, ""), true, }, { @@ -495,7 +414,7 @@ func TestSRbacPolicyMatch(t *testing.T) { Roles: []string{"admin"}, Auth: true, }, - NewRbacIdentity("", "", []string{"admin"}), + newRbacIdentity2("", "", []string{"admin"}, ""), true, }, }