Merge pull request #369 in YUNIONIO/onecloud from ~QIUJIAN/onecloud:feature/qj-rbac-support to release/2.3.0

* commit '5a8bd4848509782e3ce953e9faae864ee09a0d8b':
  改进:rpc支持
  make fmt
  remove slice nil check
  fix quota check rbac check logic
  增加 usages get 和 quotas get/update 的RBAC检查
  JointModel support
  改进:model资源支持rbac认证,需要打开enable_rbac选项,默认关闭。非model资源还不支持
This commit is contained in:
邱剑
2018-10-29 22:22:31 +08:00
32 changed files with 1554 additions and 93 deletions
+11
View File
@@ -4,6 +4,8 @@ import (
"fmt"
"os"
"time"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/mcclient/auth"
)
@@ -39,4 +41,13 @@ func InitAuth(options *Options, authComplete auth.AuthCompletedCallback) {
auth.Init(a, false, true, options.SslCertfile, options.SslKeyfile) // , authComplete)
authComplete()
if options.GlobalVirtualResourceNamespace {
db.EnableGlobalVirtualResourceNamespace()
}
if options.EnableRbac {
db.EnableGlobalRbac(time.Duration(options.RbacPolicySyncPeriodSeconds)*time.Second,
time.Duration(options.RbacPolicySyncFailedRetrySeconds)*time.Second)
}
}
+127 -52
View File
@@ -401,7 +401,7 @@ func listItems(manager IModelManager, ctx context.Context, userCred mcclient.Tok
return nil, err
}
totalCnt := int64(q.Count())
log.Debugf("total count %d", totalCnt)
// log.Debugf("total count %d", totalCnt)
if totalCnt == 0 {
emptyList := modules.ListResult{Data: []jsonutils.JSONObject{}}
return &emptyList, nil
@@ -488,9 +488,19 @@ func calculateListResult(data []jsonutils.JSONObject, total, limit, offset int64
func (dispatcher *DBModelDispatcher) List(ctx context.Context, query jsonutils.JSONObject, ctxId string) (*modules.ListResult, error) {
userCred := fetchUserCredential(ctx)
if !dispatcher.modelManager.AllowListItems(ctx, userCred, query) {
var isAllow bool
if globalsRbacEnabled {
isAdmin := jsonutils.QueryBoolean(query, "admin", false)
isAllow = PolicyManager.Allow(isAdmin, userCred, GetGlobalServiceType(),
dispatcher.modelManager.KeywordPlural(), PolicyActionList)
} else {
isAllow = dispatcher.modelManager.AllowListItems(ctx, userCred, query)
}
if !isAllow {
return nil, httperrors.NewForbiddenError("Not allow to list")
}
items, err := listItems(dispatcher.modelManager, ctx, userCred, query, ctxId)
if err != nil {
log.Errorf("Fail to list items: %s", err)
@@ -591,7 +601,13 @@ func (dispatcher *DBModelDispatcher) Get(ctx context.Context, idStr string, quer
return nil, err
}
// log.Debugf("Get found %s", model)
if !model.AllowGetDetails(ctx, userCred, query) {
var isAllow bool
if globalsRbacEnabled {
isAllow = isRbacAllowed(dispatcher.modelManager, model, userCred, PolicyActionGet)
} else {
isAllow = model.AllowGetDetails(ctx, userCred, query)
}
if !isAllow {
return nil, httperrors.NewForbiddenError("Not allow to get details")
}
return getItemDetails(dispatcher.modelManager, model, ctx, userCred, query)
@@ -607,35 +623,44 @@ func (dispatcher *DBModelDispatcher) GetSpecific(ctx context.Context, idStr stri
return nil, err
}
specCamel := utils.Kebab2Camel(spec, "-")
funcName := fmt.Sprintf("AllowGetDetails%s", specCamel)
modelValue := reflect.ValueOf(model)
funcValue := modelValue.MethodByName(funcName)
if !funcValue.IsValid() || funcValue.IsNil() {
return nil, httperrors.NewSpecNotFoundError(fmt.Sprintf("%s %s %s not found", dispatcher.Keyword(), idStr, spec))
}
params := []reflect.Value{
reflect.ValueOf(ctx),
reflect.ValueOf(userCred),
reflect.ValueOf(query),
}
outs := funcValue.Call(params)
if len(outs) != 1 {
return nil, httperrors.NewInternalServerError("Invald %s return value", funcName)
specCamel := utils.Kebab2Camel(spec, "-")
modelValue := reflect.ValueOf(model)
var isAllow bool
if globalsRbacEnabled {
isAllow = isRbacAllowed(dispatcher.modelManager, model, userCred, PolicyActionGet, spec)
} else {
funcName := fmt.Sprintf("AllowGetDetails%s", specCamel)
funcValue := modelValue.MethodByName(funcName)
if !funcValue.IsValid() || funcValue.IsNil() {
return nil, httperrors.NewSpecNotFoundError(fmt.Sprintf("%s %s %s not found", dispatcher.Keyword(), idStr, spec))
}
outs := funcValue.Call(params)
if len(outs) != 1 {
return nil, httperrors.NewInternalServerError("Invald %s return value", funcName)
}
isAllow = outs[0].Bool()
}
if !outs[0].Bool() {
if !isAllow {
return nil, httperrors.NewForbiddenError(fmt.Sprintf("%s not allow to get spec %s", dispatcher.Keyword(), spec))
}
funcName = fmt.Sprintf("GetDetails%s", specCamel)
funcValue = modelValue.MethodByName(funcName)
funcName := fmt.Sprintf("GetDetails%s", specCamel)
funcValue := modelValue.MethodByName(funcName)
if !funcValue.IsValid() || funcValue.IsNil() {
return nil, httperrors.NewSpecNotFoundError(fmt.Sprintf("%s %s %s not found", dispatcher.Keyword(), idStr, spec))
}
outs = funcValue.Call(params)
outs := funcValue.Call(params)
if len(outs) != 2 {
return nil, httperrors.NewInternalServerError("Invald %s return value", funcName)
}
@@ -664,7 +689,13 @@ func fetchOwnerProjectId(ctx context.Context, userCred mcclient.TokenCredential,
if len(projId) == 0 {
return userCred.GetProjectId(), nil
}
if !userCred.IsSystemAdmin() {
var isAllow bool
if globalsRbacEnabled {
isAllow = PolicyManager.Allow(true, userCred, GetGlobalServiceType(), PolicyDelegation, "")
} else {
isAllow = userCred.IsSystemAdmin()
}
if !isAllow {
return "", httperrors.NewForbiddenError("Delegation not allowed")
}
t, _ := TenantCacheManager.FetchTenantByIdOrName(ctx, projId)
@@ -779,7 +810,13 @@ func (dispatcher *DBModelDispatcher) Create(ctx context.Context, query jsonutils
lockman.LockClass(ctx, dispatcher.modelManager, ownerProjId)
defer lockman.ReleaseClass(ctx, dispatcher.modelManager, ownerProjId)
if !dispatcher.modelManager.AllowCreateItem(ctx, userCred, query, data) {
var isAllow bool
if globalsRbacEnabled {
isAllow = isRbacAllowed(dispatcher.modelManager, nil, userCred, PolicyActionCreate)
} else {
isAllow = dispatcher.modelManager.AllowCreateItem(ctx, userCred, query, data)
}
if !isAllow {
return nil, httperrors.NewForbiddenError("Not allow to create item")
}
@@ -837,7 +874,13 @@ func (dispatcher *DBModelDispatcher) BatchCreate(ctx context.Context, query json
lockman.LockClass(ctx, dispatcher.modelManager, ownerProjId)
defer lockman.ReleaseClass(ctx, dispatcher.modelManager, ownerProjId)
if !dispatcher.modelManager.AllowCreateItem(ctx, userCred, query, data) {
var isAllow bool
if globalsRbacEnabled {
isAllow = isRbacAllowed(dispatcher.modelManager, nil, userCred, PolicyActionCreate)
} else {
isAllow = dispatcher.modelManager.AllowCreateItem(ctx, userCred, query, data)
}
if !isAllow {
return nil, httperrors.NewForbiddenError("Not allow to create item")
}
@@ -889,16 +932,30 @@ func (dispatcher *DBModelDispatcher) PerformClassAction(ctx context.Context, act
lockman.LockClass(ctx, dispatcher.modelManager, ownerProjId)
defer lockman.ReleaseClass(ctx, dispatcher.modelManager, ownerProjId)
managerValue := reflect.ValueOf(dispatcher.modelManager)
if action == "check-create-data" {
manager := dispatcher.modelManager
if body, err := data.(*jsonutils.JSONDict).Get(manager.Keyword()); err != nil {
body, err := data.(*jsonutils.JSONDict).Get(manager.Keyword())
if err != nil {
return nil, httperrors.NewGeneralError(err)
} else {
return manager.ValidateCreateData(ctx, userCred, ownerProjId, query, body.(*jsonutils.JSONDict))
}
data := body.(*jsonutils.JSONDict)
var isAllow bool
if globalsRbacEnabled {
isAllow = isRbacAllowed(manager, nil, userCred, PolicyActionPerform, action)
} else {
isAllow = manager.AllowPerformCheckCreateData(ctx, userCred, query, data)
}
if !isAllow {
return nil, httperrors.NewForbiddenError("not allow to perform %s", action)
}
return manager.ValidateCreateData(ctx, userCred, ownerProjId, query, data)
}
return objectPerformAction(dispatcher, managerValue, ctx, userCred, action, query, data)
managerValue := reflect.ValueOf(dispatcher.modelManager)
return objectPerformAction(dispatcher, nil, managerValue, ctx, userCred, action, query, data)
}
func (dispatcher *DBModelDispatcher) PerformAction(ctx context.Context, idStr string, action string, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
@@ -915,7 +972,7 @@ func (dispatcher *DBModelDispatcher) PerformAction(ctx context.Context, idStr st
defer lockman.ReleaseObject(ctx, model)
modelValue := reflect.ValueOf(model)
result, err := objectPerformAction(dispatcher, modelValue, ctx, userCred, action, query, data)
result, err := objectPerformAction(dispatcher, model, modelValue, ctx, userCred, action, query, data)
if err == nil && result == nil {
return getItemDetails(dispatcher.modelManager, model, ctx, userCred, query)
} else {
@@ -923,23 +980,23 @@ func (dispatcher *DBModelDispatcher) PerformAction(ctx context.Context, idStr st
}
}
func objectPerformAction(dispatcher *DBModelDispatcher, modelValue reflect.Value, ctx context.Context, userCred mcclient.TokenCredential, action string, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
isGeneral := false
func objectPerformAction(dispatcher *DBModelDispatcher, model IModel, modelValue reflect.Value, ctx context.Context, userCred mcclient.TokenCredential, action string, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
const generalFuncName = "PerformAction"
const generalAllowFuncName = "AllowPerformAction"
// const generalAllowFuncName = "AllowPerformAction"
isGeneral := false
funcName := fmt.Sprintf("Perform%s", utils.Kebab2Camel(action, "-"))
allowFuncName := "Allow" + funcName
funcValue := modelValue.MethodByName(allowFuncName)
funcValue := modelValue.MethodByName(funcName)
if !funcValue.IsValid() || funcValue.IsNil() {
funcValue = modelValue.MethodByName(generalAllowFuncName)
funcValue = modelValue.MethodByName(generalFuncName)
if !funcValue.IsValid() || funcValue.IsNil() {
msg := fmt.Sprintf("%s allow perform action %s not found", dispatcher.Keyword(), action)
msg := fmt.Sprintf("%s perform action %s not found", dispatcher.Keyword(), action)
log.Errorf(msg)
return nil, httperrors.NewActionNotFoundError(msg)
} else {
isGeneral = true
funcName = generalFuncName
}
}
@@ -962,25 +1019,30 @@ func objectPerformAction(dispatcher *DBModelDispatcher, modelValue reflect.Value
}
}
outs := funcValue.Call(params)
if len(outs) != 1 {
return nil, httperrors.NewInternalServerError("Invald %s return value", allowFuncName)
var isAllow bool
if globalsRbacEnabled {
isAllow = isRbacAllowed(dispatcher.modelManager, model, userCred, PolicyActionPerform, action)
} else {
allowFuncName := "Allow" + funcName
allowFuncValue := modelValue.MethodByName(allowFuncName)
if !allowFuncValue.IsValid() || allowFuncValue.IsNil() {
msg := fmt.Sprintf("%s allow perform action %s not found", dispatcher.Keyword(), action)
log.Errorf(msg)
return nil, httperrors.NewActionNotFoundError(msg)
}
outs := funcValue.Call(params)
if len(outs) != 1 {
return nil, httperrors.NewInternalServerError("Invald %s return value", allowFuncName)
}
isAllow = outs[0].Bool()
}
if !outs[0].Bool() {
if !isAllow {
return nil, httperrors.NewForbiddenError(fmt.Sprintf("%s not allow to perform action %s", dispatcher.Keyword(), action))
}
if isGeneral {
funcValue = modelValue.MethodByName(generalFuncName)
} else {
funcName = fmt.Sprintf("Perform%s", utils.Kebab2Camel(action, "-"))
funcValue = modelValue.MethodByName(funcName)
}
if !funcValue.IsValid() || funcValue.IsNil() {
return nil, httperrors.NewActionNotFoundError(fmt.Sprintf("%s perform action %s not found", dispatcher.Keyword(), action))
}
outs = funcValue.Call(params)
outs := funcValue.Call(params)
if len(outs) != 2 {
return nil, httperrors.NewInternalServerError("Invald %s return value", funcName)
}
@@ -1069,7 +1131,13 @@ func (dispatcher *DBModelDispatcher) Update(ctx context.Context, idStr string, q
return nil, httperrors.NewGeneralError(err)
}
if !model.AllowUpdateItem(ctx, userCred) {
var isAllow bool
if globalsRbacEnabled {
isAllow = isRbacAllowed(dispatcher.modelManager, model, userCred, PolicyActionUpdate)
} else {
isAllow = model.AllowUpdateItem(ctx, userCred)
}
if !isAllow {
return nil, httperrors.NewForbiddenError(fmt.Sprintf("Not allow to update item"))
}
@@ -1098,7 +1166,14 @@ func DeleteModel(ctx context.Context, userCred mcclient.TokenCredential, item IM
func deleteItem(manager IModelManager, model IModel, ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
log.Debugf("deleteItem %s", jsonutils.Marshal(model))
if !model.AllowDeleteItem(ctx, userCred, query, data) {
var isAllow bool
if globalsRbacEnabled {
isAllow = isRbacAllowed(manager, model, userCred, PolicyActionDelete)
} else {
isAllow = model.AllowDeleteItem(ctx, userCred, query, data)
}
if !isAllow {
log.Errorf("not allow to delete")
return nil, httperrors.NewForbiddenError(fmt.Sprintf("%s(%s) not allow to delete", manager.KeywordPlural(), model.GetId()))
}
+25 -6
View File
@@ -44,7 +44,7 @@ func (dispatcher *DBJointModelDispatcher) SlaveKeywordPlural() string {
}
func (dispatcher *DBJointModelDispatcher) ListMasterDescendent(ctx context.Context, idStr string, query jsonutils.JSONObject) (*modules.ListResult, error) {
log.Debugf("ListMasterDescendent %s %s", dispatcher.JointModelManager().GetMasterManager().Keyword(), idStr)
//log.Debugf("ListMasterDescendent %s %s", dispatcher.JointModelManager().GetMasterManager().Keyword(), idStr)
userCred := fetchUserCredential(ctx)
var queryDict *jsonutils.JSONDict
@@ -71,8 +71,7 @@ func (dispatcher *DBJointModelDispatcher) ListMasterDescendent(ctx context.Conte
}
func (dispatcher *DBJointModelDispatcher) ListSlaveDescendent(ctx context.Context, idStr string, query jsonutils.JSONObject) (*modules.ListResult, error) {
log.Debugf("ListSlaveDescendent %s %s", dispatcher.JointModelManager().GetMasterManager().Keyword(), idStr)
//log.Debugf("ListSlaveDescendent %s %s", dispatcher.JointModelManager().GetMasterManager().Keyword(), idStr)
userCred := fetchUserCredential(ctx)
var queryDict *jsonutils.JSONDict
@@ -99,7 +98,15 @@ func (dispatcher *DBJointModelDispatcher) ListSlaveDescendent(ctx context.Contex
}
func (dispatcher *DBJointModelDispatcher) _listJoint(ctx context.Context, userCred mcclient.TokenCredential, ctxModel IModel, queryDict jsonutils.JSONObject) (*modules.ListResult, error) {
if !dispatcher.JointModelManager().AllowListDescendent(ctx, userCred, ctxModel, queryDict) {
var isAllow bool
if IsGlobalRbacEnabled() {
isAdmin := jsonutils.QueryBoolean(queryDict, "admin", false)
isAllow = PolicyManager.Allow(isAdmin, userCred, GetGlobalServiceType(),
dispatcher.JointModelManager().KeywordPlural(), PolicyActionList)
} else {
isAllow = dispatcher.JointModelManager().AllowListDescendent(ctx, userCred, ctxModel, queryDict)
}
if !isAllow {
return nil, httperrors.NewForbiddenError("Not allow to list")
}
@@ -136,7 +143,13 @@ func (dispatcher *DBJointModelDispatcher) Get(ctx context.Context, id1 string, i
} else if err != nil {
return nil, httperrors.NewGeneralError(err)
}
if !item.AllowGetJointDetails(ctx, userCred, query, item) {
var isAllow bool
if IsGlobalRbacEnabled() {
isAllow = isJointRbacAllowed(dispatcher.JointModelManager(), item, userCred, PolicyActionGet)
} else {
isAllow = item.AllowGetJointDetails(ctx, userCred, query, item)
}
if !isAllow {
return nil, httperrors.NewForbiddenError("Not allow to get details")
}
return getItemDetails(dispatcher.JointModelManager(), item, ctx, userCred, query)
@@ -201,7 +214,13 @@ func (dispatcher *DBJointModelDispatcher) Update(ctx context.Context, id1 string
return nil, httperrors.NewGeneralError(err)
}
if !item.AllowUpdateJointItem(ctx, userCred, item) {
var isAllow bool
if IsGlobalRbacEnabled() {
isAllow = isJointRbacAllowed(dispatcher.JointModelManager(), item, userCred, PolicyActionUpdate)
} else {
isAllow = item.AllowUpdateJointItem(ctx, userCred, item)
}
if !isAllow {
return nil, httperrors.NewForbiddenError(fmt.Sprintf("Not allow to update item"))
}
+36 -1
View File
@@ -1,9 +1,44 @@
package db
import "time"
/// Global virtual resource namespace
var globalVirtualResourceNamespace = false
var (
globalVirtualResourceNamespace = false
globalRegion = ""
globalServiceType = ""
globalsRbacEnabled = false
)
func EnableGlobalVirtualResourceNamespace() {
globalVirtualResourceNamespace = true
}
func SetGlobalRegion(region string) {
globalRegion = region
}
func GetGlobalRegion() string {
return globalRegion
}
func SetGlobalServiceType(srvType string) {
globalServiceType = srvType
}
func GetGlobalServiceType() string {
return globalServiceType
}
func EnableGlobalRbac(refreshInterval time.Duration, retryInterval time.Duration) {
globalsRbacEnabled = true
PolicyManager.start(refreshInterval, retryInterval)
}
func IsGlobalRbacEnabled() bool {
return globalsRbacEnabled
}
+1
View File
@@ -54,6 +54,7 @@ type IModelManager interface {
// allow perform action
AllowPerformAction(ctx context.Context, userCred mcclient.TokenCredential, action string, query jsonutils.JSONObject, data jsonutils.JSONObject) bool
AllowPerformCheckCreateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool
PerformAction(ctx context.Context, userCred mcclient.TokenCredential, action string, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error)
DoCreate(ctx context.Context, userCred mcclient.TokenCredential, kwargs jsonutils.JSONObject, data jsonutils.JSONObject, realManager IModelManager) (IModel, error)
+4
View File
@@ -150,6 +150,10 @@ func (manager *SModelBaseManager) PerformAction(ctx context.Context, userCred mc
return nil, nil
}
func (manager *SModelBaseManager) AllowPerformCheckCreateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
return userCred.IsSystemAdmin()
}
func (manager *SModelBaseManager) InitializeData() error {
return nil
}
+207
View File
@@ -0,0 +1,207 @@
package db
import (
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"time"
"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/rbacutils"
)
const (
PolicyDelegation = "delegate"
PolicyActionList = "list"
PolicyActionGet = "get"
PolicyActionUpdate = "update"
PolicyActionPatch = "patch"
PolicyActionCreate = "create"
PolicyActionDelete = "delete"
PolicyActionPerform = "perform"
)
var (
PolicyManager *SPolicyManager
PolicyFailedRetryInterval = 15 * time.Second
PolicyRefreshInterval = 15 * time.Minute
)
func init() {
PolicyManager = &SPolicyManager{}
}
type SPolicyManager struct {
policies map[string]rbacutils.SRbacPolicy
adminPolicies map[string]rbacutils.SRbacPolicy
lastSync time.Time
}
func parseJsonPolicy(obj jsonutils.JSONObject) (string, rbacutils.SRbacPolicy, error) {
policy := rbacutils.SRbacPolicy{}
typeStr, err := obj.GetString("type")
if err != nil {
log.Errorf("get type error %s", err)
return "", policy, err
}
blobStr, err := obj.GetString("blob")
if err != nil {
log.Errorf("get blob error %s", err)
return "", policy, err
}
blob, err := jsonutils.ParseString(blobStr)
if err != nil {
log.Errorf("parse blob json error %s", err)
return "", policy, err
}
err = policy.Decode(blob)
if err != nil {
log.Errorf("policy decode error %s", err)
return "", policy, err
}
return typeStr, policy, nil
}
func fetchPolicies() (map[string]rbacutils.SRbacPolicy, map[string]rbacutils.SRbacPolicy, error) {
s := auth.GetAdminSession(GetGlobalRegion(), "v1")
policies := make(map[string]rbacutils.SRbacPolicy)
adminPolicies := make(map[string]rbacutils.SRbacPolicy)
offset := 0
for {
params := jsonutils.NewDict()
params.Add(jsonutils.NewInt(2048), "limit")
params.Add(jsonutils.NewInt(int64(offset)), "offset")
result, err := modules.Policies.ResourceManager.List(s, params)
if err != nil {
log.Errorf("fetch policy failed")
return nil, nil, err
}
for i := 0; i < len(result.Data); i += 1 {
typeStr, policy, err := parseJsonPolicy(result.Data[i])
if err != nil {
log.Errorf("error parse policty %s", err)
continue
}
if policy.IsAdmin {
adminPolicies[typeStr] = policy
} else {
policies[typeStr] = policy
}
}
offset += len(result.Data)
if offset >= result.Total {
break
}
}
return policies, adminPolicies, nil
}
func (manager *SPolicyManager) start(refreshInterval time.Duration, retryInterval time.Duration) {
log.Infof("PolicyManager start to fetch policies ...")
PolicyRefreshInterval = refreshInterval
PolicyFailedRetryInterval = retryInterval
manager.sync()
}
func (manager *SPolicyManager) sync() {
log.Debugf("start synchronize RBAC policies ...")
policies, adminPolicies, err := fetchPolicies()
if err != nil {
log.Errorf("sync policy fail %s", err)
time.AfterFunc(PolicyFailedRetryInterval, manager.sync)
return
}
manager.policies = policies
manager.adminPolicies = adminPolicies
manager.lastSync = time.Now()
time.AfterFunc(PolicyRefreshInterval, manager.sync)
}
func (manager *SPolicyManager) Allow(isAdmin bool, userCred mcclient.TokenCredential, service string, resource string, action string, extra ...string) bool {
var policies map[string]rbacutils.SRbacPolicy
if isAdmin {
policies = manager.adminPolicies
} else {
policies = manager.policies
}
if policies == nil {
log.Warningf("no policies fetched")
return false
}
userCredJson := userCred.ToJson()
log.Debugf("%s", userCredJson)
for _, p := range policies {
if p.Allow(userCredJson, service, resource, action, extra...) {
return true
}
}
return false
}
func (manager *SPolicyManager) explainPolicy(userCred mcclient.TokenCredential, policyReq jsonutils.JSONObject) (bool, error) {
policySeq, err := policyReq.GetArray()
if err != nil {
return false, httperrors.NewInputParameterError("invalid format")
}
isAdmin, _ := policySeq[0].Bool()
if !IsGlobalRbacEnabled() {
if !isAdmin || (isAdmin && userCred.IsSystemAdmin()) {
return true, nil
} else {
return false, httperrors.NewForbiddenError("operation not allowed")
}
}
service := rbacutils.WILD_MATCH
resource := rbacutils.WILD_MATCH
action := rbacutils.WILD_MATCH
extra := make([]string, 0)
if len(policySeq) > 1 {
service, _ = policySeq[1].GetString()
}
if len(policySeq) > 2 {
resource, _ = policySeq[2].GetString()
}
if len(policySeq) > 3 {
action, _ = policySeq[3].GetString()
}
if len(policySeq) > 4 {
for i := 4; i < len(policySeq); i += 1 {
extra[i-4], _ = policySeq[i].GetString()
}
}
return manager.Allow(isAdmin, userCred, service, resource, action, extra...), nil
}
func (manager *SPolicyManager) ExplainRpc(userCred mcclient.TokenCredential, params jsonutils.JSONObject) (jsonutils.JSONObject, error) {
paramDict, err := params.GetMap()
if err != nil {
return nil, httperrors.NewInputParameterError("invalid input format")
}
ret := jsonutils.NewDict()
for key, policyReq := range paramDict {
allow, err := manager.explainPolicy(userCred, policyReq)
if err != nil {
return nil, err
}
if allow {
ret.Add(jsonutils.JSONTrue, key)
} else {
ret.Add(jsonutils.JSONFalse, key)
}
}
return ret, nil
}
+51 -5
View File
@@ -75,11 +75,33 @@ func getQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Request
projectId := params["<tenantid>"]
if len(projectId) == 0 {
projectId = userCred.GetProjectId()
if db.IsGlobalRbacEnabled() {
if !db.PolicyManager.Allow(false, userCred, db.GetGlobalServiceType(),
"quotas", db.PolicyActionGet) {
httperrors.ForbiddenError(w, "not allow to get quota")
return
}
}
} else {
if !userCred.IsSystemAdmin() {
httperrors.ForbiddenError(w, "not allow to query quota")
isAllow := false
if db.IsGlobalRbacEnabled() {
isAllow = db.PolicyManager.Allow(true, userCred, db.GetGlobalServiceType(),
db.PolicyDelegation, db.PolicyActionGet)
} else {
isAllow = userCred.IsSystemAdmin()
}
if !isAllow {
httperrors.ForbiddenError(w, "not allow to delegate query quota")
return
}
if db.IsGlobalRbacEnabled() {
if !db.PolicyManager.Allow(true, userCred, db.GetGlobalServiceType(),
"quotas", db.PolicyActionGet) {
httperrors.ForbiddenError(w, "not allow to query quota")
return
}
}
tenant, err := db.TenantCacheManager.FetchTenantByIdOrName(ctx, projectId)
if err != nil {
if err == sql.ErrNoRows {
@@ -107,7 +129,15 @@ func getQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Request
func setQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Request) {
userCred := auth.FetchUserCredential(ctx)
if !userCred.IsSystemAdmin() {
var isAllow bool
if db.IsGlobalRbacEnabled() {
isAllow = db.PolicyManager.Allow(true, userCred, db.GetGlobalServiceType(),
"quotas", db.PolicyActionUpdate)
} else {
isAllow = userCred.IsSystemAdmin()
}
if !isAllow {
httperrors.ForbiddenError(w, "not allow to set quota")
return
}
@@ -162,10 +192,26 @@ func setQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Request
func checkQuotaHanlder(ctx context.Context, w http.ResponseWriter, r *http.Request) {
userCred := auth.FetchUserCredential(ctx)
if !userCred.IsSystemAdmin() {
httperrors.ForbiddenError(w, "not allow to set quota")
isAllow := false
if db.IsGlobalRbacEnabled() {
isAllow = db.PolicyManager.Allow(true, userCred, db.GetGlobalServiceType(),
db.PolicyDelegation, db.PolicyActionGet)
} else {
isAllow = userCred.IsSystemAdmin()
}
if !isAllow {
httperrors.ForbiddenError(w, "not allow to delegate check quota")
return
}
if db.IsGlobalRbacEnabled() {
if !db.PolicyManager.Allow(true, userCred, db.GetGlobalServiceType(),
"quotas", db.PolicyActionGet) {
httperrors.ForbiddenError(w, "not allow to query quota")
return
}
}
params := appctx.AppContextParams(ctx)
projectId := params["<tenantid>"]
if len(projectId) == 0 {
+55
View File
@@ -0,0 +1,55 @@
package db
import (
"yunion.io/x/onecloud/pkg/mcclient"
)
func isRbacAllowed(manager IModelManager, model IModel, userCred mcclient.TokenCredential, action string, extra ...string) bool {
var isAllow bool
var isAdmin bool
if model == nil {
_, ok := manager.(IVirtualModelManager)
if ok {
isAdmin = false
}
} else {
virtModel, ok := model.(IVirtualModel)
if ok {
if virtModel.IsOwner(userCred) {
isAdmin = false
} else {
isAdmin = true
}
} else {
isAdmin = true
}
}
if !isAdmin {
isAllow = PolicyManager.Allow(false, userCred, GetGlobalServiceType(),
manager.KeywordPlural(), action, extra...)
}
if !isAllow {
isAllow = PolicyManager.Allow(true, userCred, GetGlobalServiceType(),
manager.KeywordPlural(), action, extra...)
}
return isAllow
}
func isJointRbacAllowed(manager IJointModelManager, item IJointModel, userCred mcclient.TokenCredential, action string, extra ...string) bool {
isAllow := false
isAdmin := true
master := item.Master()
virtualMaster, ok := master.(IVirtualModel)
if ok && virtualMaster.IsOwner(userCred) {
isAdmin = false
}
if !isAdmin {
isAllow = PolicyManager.Allow(false, userCred, GetGlobalServiceType(),
manager.KeywordPlural(), action, extra...)
}
if !isAllow {
isAllow = PolicyManager.Allow(true, userCred, GetGlobalServiceType(),
manager.KeywordPlural(), action, extra...)
}
return isAllow
}
+1 -1
View File
@@ -77,7 +77,7 @@ func (manager *STenantCacheManager) FetchTenantByName(ctx context.Context, idStr
}
func (manager *STenantCacheManager) fetchTenantFromKeystone(ctx context.Context, idStr string) (*STenant, error) {
s := auth.GetAdminSession("", "v1")
s := auth.GetAdminSession(GetGlobalRegion(), "v1")
tenant, err := modules.Projects.Get(s, idStr, nil)
if err != nil {
log.Errorf("fetch project fail %s", err)
+4
View File
@@ -40,6 +40,10 @@ type Options struct {
SslCertfile string `help:"ssl certification file"`
SslKeyfile string `help:"ssl certification key file"`
EnableRbac bool `help:"Switch on Role-based Access Control" default:"false"`
RbacPolicySyncPeriodSeconds int `help:"policy sync interval in seconds, default 15 minutes" default:"900"`
RbacPolicySyncFailedRetrySeconds int `help:"seconds to wait after a failed sync, default 30 seconds" default:"30"`
structarg.BaseOptions
}