diff --git a/go.mod b/go.mod index 7e9c07b61c3..d61d779f5ad 100644 --- a/go.mod +++ b/go.mod @@ -73,8 +73,8 @@ require ( github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.0 // @grafana/grafana-app-platform-squad github.com/grafana/alerting v0.0.0-20241010165806-807ddf183724 // @grafana/alerting-backend - github.com/grafana/authlib v0.0.0-20241014135010-3e1f37f75699 // @grafana/identity-access-team - github.com/grafana/authlib/claims v0.0.0-20240926100702-4aee62663da0 // @grafana/identity-access-team + github.com/grafana/authlib v0.0.0-20241018103850-afc1195d8240 // @grafana/identity-access-team + github.com/grafana/authlib/claims v0.0.0-20241018085709-130ad686d80e // @grafana/identity-access-team github.com/grafana/codejen v0.0.3 // @grafana/dataviz-squad github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics diff --git a/go.sum b/go.sum index 806b1ca8327..89771b573ae 100644 --- a/go.sum +++ b/go.sum @@ -2244,10 +2244,10 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/alerting v0.0.0-20241010165806-807ddf183724 h1:u+ZM5TLkdeEoSWXgYWxc4XRfPHhXpR63MyHXJxbBLrc= github.com/grafana/alerting v0.0.0-20241010165806-807ddf183724/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= -github.com/grafana/authlib v0.0.0-20241014135010-3e1f37f75699 h1:+xSpRpQPhMXAE9z68u0zMzzIa78jy1UqFb4tMJczFNc= -github.com/grafana/authlib v0.0.0-20241014135010-3e1f37f75699/go.mod h1:fhuI+ulquEIVcLsbwPml9JapWQzg8EYBp29HteO62DM= -github.com/grafana/authlib/claims v0.0.0-20240926100702-4aee62663da0 h1:XT/WvQCWVVOvXRJy0SCQHkhxXFHNRJ3+jzhW5PutEk8= -github.com/grafana/authlib/claims v0.0.0-20240926100702-4aee62663da0/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= +github.com/grafana/authlib v0.0.0-20241018103850-afc1195d8240 h1:bBn6sCbBjxjYlvs5JAIGHQSOs8xbDEBWbezxarA/DDo= +github.com/grafana/authlib v0.0.0-20241018103850-afc1195d8240/go.mod h1:RKqhn8E5PY2k5Xo6X8FHFgP45/qt9qqfAY7YYJ2mtB8= +github.com/grafana/authlib/claims v0.0.0-20241018085709-130ad686d80e h1:I0sSXcqdt/ttiOJ/BVhpfa2q/xAyWSweQwaypGmvLss= +github.com/grafana/authlib/claims v0.0.0-20241018085709-130ad686d80e/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw= github.com/grafana/codejen v0.0.3/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s= github.com/grafana/cue v0.0.0-20230926092038-971951014e3f h1:TmYAMnqg3d5KYEAaT6PtTguL2GjLfvr6wnAX8Azw6tQ= diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index e87d0c3fb92..fea229e8c09 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -3,8 +3,8 @@ module github.com/grafana/grafana/pkg/apimachinery go 1.23.1 require ( - github.com/grafana/authlib v0.0.0-20241014135010-3e1f37f75699 // @grafana/identity-access-team - github.com/grafana/authlib/claims v0.0.0-20240926100702-4aee62663da0 // @grafana/identity-access-team + github.com/grafana/authlib v0.0.0-20241018103850-afc1195d8240 // @grafana/identity-access-team + github.com/grafana/authlib/claims v0.0.0-20241018085709-130ad686d80e // @grafana/identity-access-team github.com/stretchr/testify v1.9.0 k8s.io/apimachinery v0.31.1 k8s.io/apiserver v0.31.1 diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index 99ac3491e99..153826fb882 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -28,10 +28,10 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/grafana/authlib v0.0.0-20241014135010-3e1f37f75699 h1:+xSpRpQPhMXAE9z68u0zMzzIa78jy1UqFb4tMJczFNc= -github.com/grafana/authlib v0.0.0-20241014135010-3e1f37f75699/go.mod h1:fhuI+ulquEIVcLsbwPml9JapWQzg8EYBp29HteO62DM= -github.com/grafana/authlib/claims v0.0.0-20240926100702-4aee62663da0 h1:XT/WvQCWVVOvXRJy0SCQHkhxXFHNRJ3+jzhW5PutEk8= -github.com/grafana/authlib/claims v0.0.0-20240926100702-4aee62663da0/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= +github.com/grafana/authlib v0.0.0-20241018103850-afc1195d8240 h1:bBn6sCbBjxjYlvs5JAIGHQSOs8xbDEBWbezxarA/DDo= +github.com/grafana/authlib v0.0.0-20241018103850-afc1195d8240/go.mod h1:RKqhn8E5PY2k5Xo6X8FHFgP45/qt9qqfAY7YYJ2mtB8= +github.com/grafana/authlib/claims v0.0.0-20241018085709-130ad686d80e h1:I0sSXcqdt/ttiOJ/BVhpfa2q/xAyWSweQwaypGmvLss= +github.com/grafana/authlib/claims v0.0.0-20241018085709-130ad686d80e/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 8aede364feb..df2c48e661f 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -4,7 +4,7 @@ go 1.23.1 require ( github.com/google/go-cmp v0.6.0 - github.com/grafana/authlib/claims v0.0.0-20240926100702-4aee62663da0 + github.com/grafana/authlib/claims v0.0.0-20241018085709-130ad686d80e github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1 github.com/prometheus/client_golang v1.20.4 github.com/stretchr/testify v1.9.0 diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index 366d9300873..e4235c77c9d 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -78,8 +78,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/authlib/claims v0.0.0-20240926100702-4aee62663da0 h1:XT/WvQCWVVOvXRJy0SCQHkhxXFHNRJ3+jzhW5PutEk8= -github.com/grafana/authlib/claims v0.0.0-20240926100702-4aee62663da0/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= +github.com/grafana/authlib/claims v0.0.0-20241018085709-130ad686d80e h1:I0sSXcqdt/ttiOJ/BVhpfa2q/xAyWSweQwaypGmvLss= +github.com/grafana/authlib/claims v0.0.0-20241018085709-130ad686d80e/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1 h1:ItDcDxUjVLPKja+hogpqgW/kj8LxUL2qscelXIsN1Bs= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1/go.mod h1:DkxMin+qOh1Fgkxfbt+CUfBqqsCQJMG9op8Os/irBPA= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= diff --git a/pkg/registry/apis/iam/authorizer.go b/pkg/registry/apis/iam/authorizer.go index 6ea63508d4c..d46614e4900 100644 --- a/pkg/registry/apis/iam/authorizer.go +++ b/pkg/registry/apis/iam/authorizer.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/grafana/authlib/authz" "github.com/grafana/authlib/claims" "k8s.io/apiserver/pkg/authorization/authorizer" @@ -14,7 +15,7 @@ import ( gfauthorizer "github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer" ) -func newLegacyAuthorizer(ac accesscontrol.AccessControl, store legacy.LegacyIdentityStore) (authorizer.Authorizer, claims.AccessClient) { +func newLegacyAuthorizer(ac accesscontrol.AccessControl, store legacy.LegacyIdentityStore) (authorizer.Authorizer, authz.AccessClient) { client := accesscontrol.NewLegacyAccessClient( ac, accesscontrol.ResourceAuthorizerOptions{ diff --git a/pkg/registry/apis/iam/common/common.go b/pkg/registry/apis/iam/common/common.go index ab6cf73bef5..b9174e7659d 100644 --- a/pkg/registry/apis/iam/common/common.go +++ b/pkg/registry/apis/iam/common/common.go @@ -4,9 +4,9 @@ import ( "context" "strconv" + "github.com/grafana/authlib/authz" "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/apimachinery/utils" iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/team" @@ -48,7 +48,7 @@ type ListFunc[T Resource] func(ctx context.Context, ns claims.NamespaceInfo, p P func List[T Resource]( ctx context.Context, resourceName string, - ac claims.AccessClient, + ac authz.AccessClient, p Pagination, fn ListFunc[T], ) (*ListResponse[T], error) { @@ -62,11 +62,10 @@ func List[T Resource]( return nil, err } - check := func(_ string, _ string) bool { return true } + check := func(_, _, _ string) bool { return true } if ac != nil { var err error - check, err = ac.Compile(ctx, ident, claims.AccessRequest{ - Verb: utils.VerbList, + check, err = ac.Compile(ctx, ident, authz.ListRequest{ Resource: resourceName, Namespace: ns.Value, }) @@ -84,7 +83,7 @@ func List[T Resource]( } for _, item := range first.Items { - if !check(ns.Value, item.AuthID()) { + if !check(ns.Value, item.AuthID(), "") { continue } res.Items = append(res.Items, item) @@ -107,7 +106,7 @@ outer: break outer } - if !check(ns.Value, item.AuthID()) { + if !check(ns.Value, item.AuthID(), "") { continue } diff --git a/pkg/registry/apis/iam/legacy/sql.go b/pkg/registry/apis/iam/legacy/sql.go index cdabeb6ac7c..bb79aadd068 100644 --- a/pkg/registry/apis/iam/legacy/sql.go +++ b/pkg/registry/apis/iam/legacy/sql.go @@ -40,11 +40,6 @@ func NewLegacySQLStores(sql legacysql.LegacyDatabaseProvider) LegacyIdentityStor type legacySQLStore struct { sql legacysql.LegacyDatabaseProvider - ac claims.AccessClient -} - -func (s *legacySQLStore) WithAccessClient(ac claims.AccessClient) { - s.ac = ac } // Templates setup. diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index 859618b58fa..cdc67eff0fd 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -11,7 +11,7 @@ import ( genericapiserver "k8s.io/apiserver/pkg/server" common "k8s.io/kube-openapi/pkg/common" - "github.com/grafana/authlib/claims" + "github.com/grafana/authlib/authz" "github.com/grafana/grafana/pkg/apimachinery/identity" iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" "github.com/grafana/grafana/pkg/infra/db" @@ -32,7 +32,7 @@ var _ builder.APIGroupBuilder = (*IdentityAccessManagementAPIBuilder)(nil) type IdentityAccessManagementAPIBuilder struct { store legacy.LegacyIdentityStore authorizer authorizer.Authorizer - accessClient claims.AccessClient + accessClient authz.AccessClient // Not set for multi-tenant deployment for now sso ssosettings.Service diff --git a/pkg/registry/apis/iam/serviceaccount/store.go b/pkg/registry/apis/iam/serviceaccount/store.go index fdc3e2f826e..445c49666fd 100644 --- a/pkg/registry/apis/iam/serviceaccount/store.go +++ b/pkg/registry/apis/iam/serviceaccount/store.go @@ -10,6 +10,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" + "github.com/grafana/authlib/authz" "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/utils" iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" @@ -28,13 +29,13 @@ var ( var resource = iamv0.ServiceAccountResourceInfo -func NewLegacyStore(store legacy.LegacyIdentityStore, ac claims.AccessClient) *LegacyStore { +func NewLegacyStore(store legacy.LegacyIdentityStore, ac authz.AccessClient) *LegacyStore { return &LegacyStore{store, ac} } type LegacyStore struct { store legacy.LegacyIdentityStore - ac claims.AccessClient + ac authz.AccessClient } func (s *LegacyStore) New() runtime.Object { diff --git a/pkg/registry/apis/iam/team/store.go b/pkg/registry/apis/iam/team/store.go index 864e5a36df8..d0cc9c5787e 100644 --- a/pkg/registry/apis/iam/team/store.go +++ b/pkg/registry/apis/iam/team/store.go @@ -10,6 +10,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" + "github.com/grafana/authlib/authz" "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/utils" iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" @@ -29,13 +30,13 @@ var ( var resource = iamv0.TeamResourceInfo -func NewLegacyStore(store legacy.LegacyIdentityStore, ac claims.AccessClient) *LegacyStore { +func NewLegacyStore(store legacy.LegacyIdentityStore, ac authz.AccessClient) *LegacyStore { return &LegacyStore{store, ac} } type LegacyStore struct { store legacy.LegacyIdentityStore - ac claims.AccessClient + ac authz.AccessClient } func (s *LegacyStore) New() runtime.Object { diff --git a/pkg/registry/apis/iam/user/store.go b/pkg/registry/apis/iam/user/store.go index 8931df0ff69..c8857ffa712 100644 --- a/pkg/registry/apis/iam/user/store.go +++ b/pkg/registry/apis/iam/user/store.go @@ -10,6 +10,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" + "github.com/grafana/authlib/authz" "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/utils" iamv0 "github.com/grafana/grafana/pkg/apis/iam/v0alpha1" @@ -29,13 +30,13 @@ var ( var resource = iamv0.UserResourceInfo -func NewLegacyStore(store legacy.LegacyIdentityStore, ac claims.AccessClient) *LegacyStore { +func NewLegacyStore(store legacy.LegacyIdentityStore, ac authz.AccessClient) *LegacyStore { return &LegacyStore{store, ac} } type LegacyStore struct { store legacy.LegacyIdentityStore - ac claims.AccessClient + ac authz.AccessClient } func (s *LegacyStore) New() runtime.Object { diff --git a/pkg/services/accesscontrol/authorizer.go b/pkg/services/accesscontrol/authorizer.go index 5d400b06dc9..cb14390e83a 100644 --- a/pkg/services/accesscontrol/authorizer.go +++ b/pkg/services/accesscontrol/authorizer.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/grafana/authlib/authz" "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" @@ -44,7 +45,7 @@ type ResourceAuthorizerOptions struct { Resolver ResourceResolver } -var _ claims.AccessClient = (*LegacyAccessClient)(nil) +var _ authz.AccessClient = (*LegacyAccessClient)(nil) func NewLegacyAccessClient(ac AccessControl, opts ...ResourceAuthorizerOptions) *LegacyAccessClient { stored := map[string]ResourceAuthorizerOptions{} @@ -82,35 +83,34 @@ type LegacyAccessClient struct { opts map[string]ResourceAuthorizerOptions } -// HasAccess implements claims.AccessClient. -func (c *LegacyAccessClient) HasAccess(ctx context.Context, id claims.AuthInfo, req claims.AccessRequest) (bool, error) { +func (c *LegacyAccessClient) Check(ctx context.Context, id claims.AuthInfo, req authz.CheckRequest) (authz.CheckResponse, error) { ident, ok := id.(identity.Requester) if !ok { - return false, errors.New("expected identity.Requester for legacy access control") + return authz.CheckResponse{}, errors.New("expected identity.Requester for legacy access control") } opts, ok := c.opts[req.Resource] if !ok { // For now we fallback to grafana admin if no options are found for resource. if ident.GetIsGrafanaAdmin() { - return true, nil + return authz.CheckResponse{Allowed: true}, nil } - return false, nil + return authz.CheckResponse{}, nil } skip := opts.Unchecked[req.Verb] if skip { - return true, nil + return authz.CheckResponse{Allowed: true}, nil } action, ok := opts.Mapping[req.Verb] if !ok { - return false, fmt.Errorf("missing action for %s %s", req.Verb, req.Resource) + return authz.CheckResponse{}, fmt.Errorf("missing action for %s %s", req.Verb, req.Resource) } ns, err := claims.ParseNamespace(req.Namespace) if err != nil { - return false, err + return authz.CheckResponse{}, err } var eval Evaluator @@ -118,7 +118,7 @@ func (c *LegacyAccessClient) HasAccess(ctx context.Context, id claims.AuthInfo, if opts.Resolver != nil { scopes, err := opts.Resolver.Resolve(ctx, ns, req.Name) if err != nil { - return false, err + return authz.CheckResponse{}, err } eval = EvalPermission(action, scopes...) } else { @@ -129,14 +129,18 @@ func (c *LegacyAccessClient) HasAccess(ctx context.Context, id claims.AuthInfo, eval = EvalPermission(action) } else { // Assuming that all non list request should have a valid name - return false, fmt.Errorf("unhandled authorization: %s %s", req.Group, req.Verb) + return authz.CheckResponse{}, fmt.Errorf("unhandled authorization: %s %s", req.Group, req.Verb) } - return c.ac.Evaluate(ctx, ident, eval) + allowed, err := c.ac.Evaluate(ctx, ident, eval) + if err != nil { + return authz.CheckResponse{}, err + } + + return authz.CheckResponse{Allowed: allowed}, nil } -// Compile implements claims.AccessClient. -func (c *LegacyAccessClient) Compile(ctx context.Context, id claims.AuthInfo, req claims.AccessRequest) (claims.AccessChecker, error) { +func (c *LegacyAccessClient) Compile(ctx context.Context, id claims.AuthInfo, req authz.ListRequest) (authz.ItemChecker, error) { ident, ok := id.(identity.Requester) if !ok { return nil, errors.New("expected identity.Requester for legacy access control") @@ -147,13 +151,13 @@ func (c *LegacyAccessClient) Compile(ctx context.Context, id claims.AuthInfo, re return nil, fmt.Errorf("unsupported resource: %s", req.Resource) } - action, ok := opts.Mapping[req.Verb] + action, ok := opts.Mapping[utils.VerbList] if !ok { - return nil, fmt.Errorf("missing action for %s %s", req.Verb, req.Resource) + return nil, fmt.Errorf("missing action for %s %s", utils.VerbList, req.Resource) } check := Checker(ident, action) - return func(_, name string) bool { + return func(_, name, _ string) bool { return check(fmt.Sprintf("%s:%s:%s", opts.Resource, opts.Attr, name)) }, nil } diff --git a/pkg/services/accesscontrol/authorizer_test.go b/pkg/services/accesscontrol/authorizer_test.go index 5d7e7f28f71..1a4ddf21b76 100644 --- a/pkg/services/accesscontrol/authorizer_test.go +++ b/pkg/services/accesscontrol/authorizer_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/grafana/authlib/claims" + "github.com/grafana/authlib/authz" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" @@ -14,20 +14,20 @@ import ( "github.com/grafana/grafana/pkg/services/featuremgmt" ) -func TestLegacyAccessClient_HasAccess(t *testing.T) { +func TestLegacyAccessClient_Check(t *testing.T) { ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) t.Run("should reject when when no configuration for resource exist", func(t *testing.T) { a := accesscontrol.NewLegacyAccessClient(ac) - ok, err := a.HasAccess(context.Background(), &identity.StaticRequester{}, claims.AccessRequest{ + res, err := a.Check(context.Background(), &identity.StaticRequester{}, authz.CheckRequest{ Verb: "get", Resource: "dashboards", Namespace: "default", Name: "1", }) assert.NoError(t, err) - assert.Equal(t, false, ok) + assert.Equal(t, false, res.Allowed) }) t.Run("should reject when user don't have correct scope", func(t *testing.T) { @@ -43,7 +43,7 @@ func TestLegacyAccessClient_HasAccess(t *testing.T) { accesscontrol.Permission{Action: "dashboards:read", Scope: "dashboards:uid:2"}, ) - ok, err := a.HasAccess(context.Background(), ident, claims.AccessRequest{ + res, err := a.Check(context.Background(), ident, authz.CheckRequest{ Verb: "get", Namespace: "default", Resource: "dashboards", @@ -51,7 +51,7 @@ func TestLegacyAccessClient_HasAccess(t *testing.T) { }) assert.NoError(t, err) - assert.Equal(t, false, ok) + assert.Equal(t, false, res.Allowed) }) t.Run("should just check action for list requests", func(t *testing.T) { @@ -67,14 +67,14 @@ func TestLegacyAccessClient_HasAccess(t *testing.T) { accesscontrol.Permission{Action: "dashboards:read"}, ) - ok, err := a.HasAccess(context.Background(), ident, claims.AccessRequest{ + res, err := a.Check(context.Background(), ident, authz.CheckRequest{ Verb: "list", Namespace: "default", Resource: "dashboards", }) assert.NoError(t, err) - assert.Equal(t, true, ok) + assert.Equal(t, true, res.Allowed) }) t.Run("should allow when user have correct scope", func(t *testing.T) { @@ -90,7 +90,7 @@ func TestLegacyAccessClient_HasAccess(t *testing.T) { accesscontrol.Permission{Action: "dashboards:read", Scope: "dashboards:uid:1"}, ) - ok, err := a.HasAccess(context.Background(), ident, claims.AccessRequest{ + res, err := a.Check(context.Background(), ident, authz.CheckRequest{ Verb: "get", Namespace: "default", Resource: "dashboards", @@ -98,7 +98,7 @@ func TestLegacyAccessClient_HasAccess(t *testing.T) { }) assert.NoError(t, err) - assert.Equal(t, true, ok) + assert.Equal(t, true, res.Allowed) }) t.Run("should skip authorization for configured verb", func(t *testing.T) { @@ -115,7 +115,7 @@ func TestLegacyAccessClient_HasAccess(t *testing.T) { ident := newIdent(accesscontrol.Permission{}) - ok, err := a.HasAccess(context.Background(), ident, claims.AccessRequest{ + res, err := a.Check(context.Background(), ident, authz.CheckRequest{ Verb: "get", Namespace: "default", Resource: "dashboards", @@ -123,9 +123,9 @@ func TestLegacyAccessClient_HasAccess(t *testing.T) { }) assert.NoError(t, err) - assert.Equal(t, true, ok) + assert.Equal(t, true, res.Allowed) - ok, err = a.HasAccess(context.Background(), ident, claims.AccessRequest{ + res, err = a.Check(context.Background(), ident, authz.CheckRequest{ Verb: "create", Namespace: "default", Resource: "dashboards", @@ -133,7 +133,7 @@ func TestLegacyAccessClient_HasAccess(t *testing.T) { }) assert.NoError(t, err) - assert.Equal(t, false, ok) + assert.Equal(t, false, res.Allowed) }) } diff --git a/pkg/services/apiserver/auth/authorizer/resource.go b/pkg/services/apiserver/auth/authorizer/resource.go index a2c0a05534a..06300c15d7a 100644 --- a/pkg/services/apiserver/auth/authorizer/resource.go +++ b/pkg/services/apiserver/auth/authorizer/resource.go @@ -4,17 +4,18 @@ import ( "context" "errors" + "github.com/grafana/authlib/authz" "github.com/grafana/authlib/claims" "k8s.io/apiserver/pkg/authorization/authorizer" ) -func NewResourceAuthorizer(c claims.AccessClient) authorizer.Authorizer { +func NewResourceAuthorizer(c authz.AccessClient) authorizer.Authorizer { return ResourceAuthorizer{c} } // ResourceAuthorizer is used to translate authorizer.Authorizer calls to claims.AccessClient calls type ResourceAuthorizer struct { - c claims.AccessClient + c authz.AccessClient } func (r ResourceAuthorizer) Authorize(ctx context.Context, attr authorizer.Attributes) (authorizer.Decision, string, error) { @@ -27,7 +28,7 @@ func (r ResourceAuthorizer) Authorize(ctx context.Context, attr authorizer.Attri return authorizer.DecisionDeny, "", errors.New("no identity found for request") } - ok, err := r.c.HasAccess(ctx, ident, claims.AccessRequest{ + res, err := r.c.Check(ctx, ident, authz.CheckRequest{ Verb: attr.GetVerb(), Group: attr.GetAPIGroup(), Resource: attr.GetResource(), @@ -41,7 +42,7 @@ func (r ResourceAuthorizer) Authorize(ctx context.Context, attr authorizer.Attri return authorizer.DecisionDeny, "", err } - if !ok { + if !res.Allowed { return authorizer.DecisionDeny, "unauthorized request", nil } diff --git a/pkg/services/authz/client.go b/pkg/services/authz/client.go index 004b6110721..57fdb464748 100644 --- a/pkg/services/authz/client.go +++ b/pkg/services/authz/client.go @@ -23,7 +23,7 @@ import ( const authzServiceAudience = "authzService" type Client interface { - authzlib.Client + authzlib.AccessChecker } // ProvideAuthZClient provides an AuthZ client and creates the AuthZ service. @@ -40,7 +40,7 @@ func ProvideAuthZClient( return nil, err } - var client authzlib.Client + var client Client // Register the server server, err := newLegacyServer(acSvc, features, grpcServer, tracer, authCfg) @@ -86,7 +86,7 @@ func ProvideStandaloneAuthZClient( return newGrpcLegacyClient(authCfg.remoteAddress) } -func newInProcLegacyClient(server *legacyServer) (authzlib.Client, error) { +func newInProcLegacyClient(server *legacyServer) (authzlib.AccessChecker, error) { noAuth := func(ctx context.Context) (context.Context, error) { return ctx, nil } @@ -101,14 +101,14 @@ func newInProcLegacyClient(server *legacyServer) (authzlib.Client, error) { server, ) - return authzlib.NewLegacyClient( + return authzlib.NewClient( &authzlib.ClientConfig{}, - authzlib.WithGrpcConnectionLCOption(channel), - authzlib.WithDisableAccessTokenLCOption(), + authzlib.WithGrpcConnectionClientOption(channel), + authzlib.WithDisableAccessTokenClientOption(), ) } -func newGrpcLegacyClient(address string) (authzlib.Client, error) { +func newGrpcLegacyClient(address string) (authzlib.AccessChecker, error) { // This client interceptor is a noop, as we don't send an access token grpcClientConfig := authnlib.GrpcClientConfig{} clientInterceptor, err := authnlib.NewGrpcClientInterceptor(&grpcClientConfig, @@ -119,15 +119,15 @@ func newGrpcLegacyClient(address string) (authzlib.Client, error) { } cfg := authzlib.ClientConfig{RemoteAddress: address} - client, err := authzlib.NewLegacyClient(&cfg, + client, err := authzlib.NewClient(&cfg, // TODO(drclau): make this configurable (e.g. allow to use insecure connections) - authzlib.WithGrpcDialOptionsLCOption( + authzlib.WithGrpcDialOptionsClientOption( grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithUnaryInterceptor(clientInterceptor.UnaryClientInterceptor), grpc.WithStreamInterceptor(clientInterceptor.StreamClientInterceptor), ), // TODO(drclau): remove this once we have access token support on-prem - authzlib.WithDisableAccessTokenLCOption(), + authzlib.WithDisableAccessTokenClientOption(), ) if err != nil { return nil, err @@ -136,7 +136,7 @@ func newGrpcLegacyClient(address string) (authzlib.Client, error) { return client, nil } -func newCloudLegacyClient(authCfg *Cfg) (authzlib.Client, error) { +func newCloudLegacyClient(authCfg *Cfg) (authzlib.AccessChecker, error) { grpcClientConfig := authnlib.GrpcClientConfig{ TokenClientConfig: &authnlib.TokenExchangeConfig{ Token: authCfg.token, @@ -154,9 +154,9 @@ func newCloudLegacyClient(authCfg *Cfg) (authzlib.Client, error) { } clientCfg := authzlib.ClientConfig{RemoteAddress: authCfg.remoteAddress} - client, err := authzlib.NewLegacyClient(&clientCfg, + client, err := authzlib.NewClient(&clientCfg, // TODO(drclau): make this configurable (e.g. allow to use insecure connections) - authzlib.WithGrpcDialOptionsLCOption( + authzlib.WithGrpcDialOptionsClientOption( grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithUnaryInterceptor(clientInterceptor.UnaryClientInterceptor), grpc.WithStreamInterceptor(clientInterceptor.StreamClientInterceptor), diff --git a/pkg/services/authz/server.go b/pkg/services/authz/server.go index 5f91f48e4fe..1fc7fe51f08 100644 --- a/pkg/services/authz/server.go +++ b/pkg/services/authz/server.go @@ -2,10 +2,9 @@ package authz import ( "context" - "fmt" + "errors" authzv1 "github.com/grafana/authlib/authz/proto/v1" - "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" @@ -16,14 +15,6 @@ import ( var _ authzv1.AuthzServiceServer = (*legacyServer)(nil) -type legacyServer struct { - authzv1.UnimplementedAuthzServiceServer - - acSvc accesscontrol.Service - logger log.Logger - tracer tracing.Tracer -} - func newLegacyServer( acSvc accesscontrol.Service, features featuremgmt.FeatureToggles, grpcServer grpcserver.Provider, tracer tracing.Tracer, cfg *Cfg, @@ -45,40 +36,15 @@ func newLegacyServer( return s, nil } -func (s *legacyServer) Read(ctx context.Context, req *authzv1.ReadRequest) (*authzv1.ReadResponse, error) { - ctx, span := s.tracer.Start(ctx, "authz.grpc.Read") - defer span.End() +type legacyServer struct { + authzv1.UnimplementedAuthzServiceServer - // FIXME: once we have access tokens, we need to do namespace validation here - - action := req.GetAction() - subject := req.GetSubject() - namespace := req.GetNamespace() // TODO can we consider the stackID as the orgID? - - info, err := claims.ParseNamespace(namespace) - if err != nil || info.OrgID == 0 { - return nil, fmt.Errorf("invalid namespace: %s", namespace) - } - - ctxLogger := s.logger.FromContext(ctx) - ctxLogger.Debug("Read", "action", action, "subject", subject, "namespace", namespace) - - permissions, err := s.acSvc.SearchUserPermissions( - ctx, - info.OrgID, - accesscontrol.SearchOptions{Action: action, TypedID: subject}, - ) - if err != nil { - ctxLogger.Error("failed to search user permissions", "error", err) - return nil, tracing.Errorf(span, "failed to search user permissions: %w", err) - } - - data := make([]*authzv1.ReadResponse_Data, 0, len(permissions)) - for _, perm := range permissions { - data = append(data, &authzv1.ReadResponse_Data{Scope: perm.Scope}) - } - return &authzv1.ReadResponse{ - Data: data, - Found: len(data) > 0, - }, nil + acSvc accesscontrol.Service + logger log.Logger + tracer tracing.Tracer +} + +func (l *legacyServer) Check(context.Context, *authzv1.CheckRequest) (*authzv1.CheckResponse, error) { + // FIXME: implement for legacy access control + return nil, errors.New("unimplemented") }