feature: policy group support

This commit is contained in:
Qiu Jian
2020-10-24 22:46:46 +08:00
parent 167c672194
commit 2d39cb4cb0
53 changed files with 2306 additions and 972 deletions
+62
View File
@@ -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
}
+57
View File
@@ -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)
}
}
}
+257
View File
@@ -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
}
+93
View File
@@ -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
}
+88
View File
@@ -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
}
+33 -22
View File
@@ -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
}
+92 -116
View File
@@ -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,
},
},
},
+45 -407
View File
@@ -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,
}
}
+18 -99
View File
@@ -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,
},
}