改进:model资源支持rbac认证,需要打开enable_rbac选项,默认关闭。非model资源还不支持

This commit is contained in:
Qiu Jian
2018-10-27 14:34:09 +08:00
parent 93ddd2b26f
commit d0fef591f1
23 changed files with 1097 additions and 74 deletions
+9
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/mcclient/auth"
)
@@ -39,4 +40,12 @@ 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()
}
}
+158 -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
@@ -457,9 +457,19 @@ func listItems(manager IModelManager, ctx context.Context, userCred mcclient.Tok
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)
@@ -541,6 +551,37 @@ func (dispatcher *DBModelDispatcher) tryGetModelProperty(ctx context.Context, pr
}
}
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 (dispatcher *DBModelDispatcher) Get(ctx context.Context, idStr string, query jsonutils.JSONObject) (jsonutils.JSONObject, error) {
// log.Debugf("Get %s", idStr)
userCred := fetchUserCredential(ctx)
@@ -560,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)
@@ -576,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)
}
@@ -633,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)
@@ -748,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")
}
@@ -806,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")
}
@@ -858,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) {
@@ -884,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 {
@@ -892,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
}
}
@@ -931,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)
}
@@ -1038,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"))
}
@@ -1067,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()))
}
+30 -1
View File
@@ -2,8 +2,37 @@ package db
/// 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() {
globalsRbacEnabled = true
PolicyManager.start()
}
+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
}
+146
View File
@@ -0,0 +1,146 @@
package db
import (
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"time"
"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 (
PolicyFailedRetryInterval = 15 * time.Second
PolicyRefreshInterval = 15 * time.Minute
PolicyDelegation = "delegate"
PolicyActionList = "list"
PolicyActionGet = "get"
PolicyActionUpdate = "update"
PolicyActionPatch = "patch"
PolicyActionCreate = "create"
PolicyActionDelete = "delete"
PolicyActionPerform = "perform"
)
var PolicyManager *SPolicyManager
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.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() {
log.Infof("PolicyManager start to fetch policies ...")
manager.sync()
}
func (manager *SPolicyManager) sync() {
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
}
+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)
+2
View File
@@ -40,6 +40,8 @@ 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"`
structarg.BaseOptions
}