From b1fb581ab121102e76ab23305975c750ca74e5b1 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 11 Nov 2024 16:39:21 +0100 Subject: [PATCH] Zanzana: Evaluate access with Check request (server-side) (#96213) * Zanzana: Evaluate access with Check request (server-side) * Pass parent folder for checking access * Review suggestions * remove fixme comment --- .../accesscontrol/acimpl/accesscontrol.go | 35 +++++++++++++++++-- pkg/services/accesscontrol/evaluator.go | 16 +++------ pkg/services/authz/zanzana/common/info.go | 23 +++++++----- pkg/services/authz/zanzana/zanzana.go | 31 ++++++++++++++++ 4 files changed, 83 insertions(+), 22 deletions(-) diff --git a/pkg/services/accesscontrol/acimpl/accesscontrol.go b/pkg/services/accesscontrol/acimpl/accesscontrol.go index cdef563bc17..78697ba9a06 100644 --- a/pkg/services/accesscontrol/acimpl/accesscontrol.go +++ b/pkg/services/accesscontrol/acimpl/accesscontrol.go @@ -8,6 +8,8 @@ import ( "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel" + "github.com/grafana/authlib/claims" + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics" @@ -116,9 +118,36 @@ func (a *AccessControl) evaluateZanzana(ctx context.Context, user identity.Reque eval = evaluator } - return eval.EvaluateCustom(func(action, scope string) (bool, error) { - // FIXME: Implement using new schema / apis - return false, nil + return eval.EvaluateCustom(func(action string, scopes ...string) (bool, error) { + // FIXME: handle action with no scopes + if len(scopes) == 0 { + return false, nil + } + + resourceScope := scopes[0] + kind, _, identifier := accesscontrol.SplitScope(resourceScope) + + // Parent folder always returned by scope resolver as a second value + var parentFolder string + if len(scopes) > 1 { + _, _, parentFolder = accesscontrol.SplitScope(scopes[1]) + } + + namespace := claims.OrgNamespaceFormatter(user.GetOrgID()) + req, ok := zanzana.TranslateToCheckRequest(namespace, action, kind, parentFolder, identifier) + if !ok { + // unsupported translation + return false, errAccessNotImplemented + } + + a.log.Debug("evaluating zanzana", "user", user.GetUID(), "namespace", req.Namespace, "verb", req.Verb, "resource", req.Resource, "name", req.Name) + res, err := a.zclient.Check(ctx, user, *req) + + if err != nil { + return false, err + } + + return res.Allowed, nil }) } diff --git a/pkg/services/accesscontrol/evaluator.go b/pkg/services/accesscontrol/evaluator.go index d07ee295bee..3d4b6a38304 100644 --- a/pkg/services/accesscontrol/evaluator.go +++ b/pkg/services/accesscontrol/evaluator.go @@ -11,7 +11,7 @@ import ( var logger = log.New("accesscontrol.evaluator") -type CheckerFn func(action string, scope string) (bool, error) +type CheckerFn func(action string, scopes ...string) (bool, error) type Evaluator interface { // Evaluate permissions that are grouped by action @@ -89,18 +89,12 @@ func (p permissionEvaluator) EvaluateCustom(fn CheckerFn) (bool, error) { return fn(p.Action, "") } - for _, target := range p.Scopes { - matches, err := fn(p.Action, target) - if err != nil { - return false, err - } - - if matches { - return true, nil - } + matches, err := fn(p.Action, p.Scopes...) + if err != nil { + return false, err } - return false, nil + return matches, nil } func (p permissionEvaluator) MutateScopes(ctx context.Context, mutate ScopeAttributeMutator) (Evaluator, error) { diff --git a/pkg/services/authz/zanzana/common/info.go b/pkg/services/authz/zanzana/common/info.go index d59e19e40d6..3d2521875e8 100644 --- a/pkg/services/authz/zanzana/common/info.go +++ b/pkg/services/authz/zanzana/common/info.go @@ -23,12 +23,19 @@ func GetTypeInfo(group, resource string) (TypeInfo, bool) { } var VerbMapping = map[string]string{ - utils.VerbGet: "read", - utils.VerbList: "read", - utils.VerbWatch: "read", - utils.VerbCreate: "create", - utils.VerbUpdate: "write", - utils.VerbPatch: "write", - utils.VerbDelete: "delete", - utils.VerbDeleteCollection: "delete", + utils.VerbGet: RelationRead, + utils.VerbList: RelationRead, + utils.VerbWatch: RelationRead, + utils.VerbCreate: RelationCreate, + utils.VerbUpdate: RelationWrite, + utils.VerbPatch: RelationWrite, + utils.VerbDelete: RelationDelete, + utils.VerbDeleteCollection: RelationDelete, +} + +var RelationToVerbMapping = map[string]string{ + RelationRead: utils.VerbGet, + RelationCreate: utils.VerbCreate, + RelationWrite: utils.VerbUpdate, + RelationDelete: utils.VerbDelete, } diff --git a/pkg/services/authz/zanzana/zanzana.go b/pkg/services/authz/zanzana/zanzana.go index 455a5635a63..ac951b79ba4 100644 --- a/pkg/services/authz/zanzana/zanzana.go +++ b/pkg/services/authz/zanzana/zanzana.go @@ -6,6 +6,8 @@ import ( openfgav1 "github.com/openfga/api/proto/openfga/v1" + "github.com/grafana/authlib/authz" + "github.com/grafana/grafana/pkg/services/authz/zanzana/common" ) @@ -143,3 +145,32 @@ func MergeFolderResourceTuples(a, b *openfgav1.TupleKey) { vb := b.Condition.Context.Fields["group_resources"] va.GetListValue().Values = append(va.GetListValue().Values, vb.GetListValue().Values...) } + +func TranslateToCheckRequest(namespace, action, kind, folder, name string) (*authz.CheckRequest, bool) { + translation, ok := resourceTranslations[kind] + + if !ok { + return nil, false + } + + m, ok := translation.mapping[action] + if !ok { + return nil, false + } + + verb, ok := common.RelationToVerbMapping[m.relation] + if !ok { + return nil, false + } + + req := &authz.CheckRequest{ + Namespace: namespace, + Verb: verb, + Group: translation.group, + Resource: translation.resource, + Name: name, + Folder: folder, + } + + return req, true +}