RBAC: Add legacy authorization checks to service accounts (#93753)

* Extract a helper funtion to perform list with authorization checks

* Add k8s verb to utils package

* Construct default mapping when no custom mapping is passed

* Configure authorization checks for service accounts

* Fix helper and add filtering to service accounts
This commit is contained in:
Karl Persson
2024-09-27 15:53:11 +02:00
committed by GitHub
parent 7710f1c3cf
commit 0160f4f72c
14 changed files with 424 additions and 118 deletions
+19 -2
View File
@@ -7,6 +7,7 @@ import (
"github.com/grafana/authlib/claims"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/apimachinery/utils"
)
// ResourceResolver is called before authorization is performed.
@@ -35,6 +36,7 @@ type ResourceAuthorizerOptions struct {
Attr string
// Mapping is used to translate k8s verb to rbac action.
// Key is the desired verb and value the rbac action it should be translated into.
// If no mapping is provided it will get a "default" mapping.
Mapping map[string]string
// Resolver if passed can translate into one or more scopes used to authorize resource.
// This is useful when stored scopes are based on something else than k8s name or
@@ -47,13 +49,28 @@ var _ claims.AccessClient = (*LegacyAccessClient)(nil)
func NewLegacyAccessClient(ac AccessControl, opts ...ResourceAuthorizerOptions) *LegacyAccessClient {
stored := map[string]ResourceAuthorizerOptions{}
defaultMapping := func(r string) map[string]string {
return map[string]string{
utils.VerbGet: fmt.Sprintf("%s:read", r),
utils.VerbList: fmt.Sprintf("%s:read", r),
utils.VerbWatch: fmt.Sprintf("%s:read", r),
utils.VerbCreate: fmt.Sprintf("%s:create", r),
utils.VerbUpdate: fmt.Sprintf("%s:write", r),
utils.VerbPatch: fmt.Sprintf("%s:write", r),
utils.VerbDelete: fmt.Sprintf("%s:delete", r),
utils.VerbDeleteCollection: fmt.Sprintf("%s:delete", r),
}
}
for _, o := range opts {
if o.Unchecked == nil {
o.Unchecked = map[string]bool{}
}
if o.Mapping == nil {
o.Mapping = map[string]string{}
o.Mapping = defaultMapping(o.Resource)
}
stored[o.Resource] = o
}
@@ -107,7 +124,7 @@ func (c *LegacyAccessClient) HasAccess(ctx context.Context, id claims.AuthInfo,
} else {
eval = EvalPermission(action, fmt.Sprintf("%s:%s:%s", opts.Resource, opts.Attr, req.Name))
}
} else if req.Verb == "list" {
} else if req.Verb == utils.VerbList {
// For list request we need to filter out in storage layer.
eval = EvalPermission(action)
} else {
@@ -14,14 +14,11 @@ import (
"github.com/grafana/grafana/pkg/services/featuremgmt"
)
func TestResourceAuthorizer_HasAccess(t *testing.T) {
func TestLegacyAccessClient_HasAccess(t *testing.T) {
ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient())
t.Run("should have no opinion for non resource requests", func(t *testing.T) {
a := accesscontrol.NewLegacyAccessClient(ac, accesscontrol.ResourceAuthorizerOptions{
Resource: "dashboards",
Attr: "uid",
})
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{
Verb: "get",
@@ -29,7 +26,7 @@ func TestResourceAuthorizer_HasAccess(t *testing.T) {
Namespace: "default",
Name: "1",
})
assert.Error(t, err)
assert.NoError(t, err)
assert.Equal(t, false, ok)
})