mirror of https://github.com/grafana/grafana
K8s/Permissions: Enable a grant-permissions annotation action to set default permissions (#102527)
* create permissions * add key * lint * structure as a delayed callback * legacy API hook * merge main * wired up * and folders * watch repos * missing return statement * Set the correct permissions * add TestAfterCreatePermissionCreator * do not add perms on folder create * fix tests * add annotation on create * lint * lint * ensure we set permissions when the FT is disabled * remove custom folder_storage * fix lint * change default * lint * lint * fix: annotation * ensure permissions are added on folder legacy * remove folderstorage again * fix tests * add FT * undo change to folder * dashboard on create * remove annotation for folder * fix tests * fix prepare after rebase * fix tests * fix tests * fix tests * lint * address comments * add test for prepareObjectForStorage * add again skipIfMode as per comment --------- Co-authored-by: Georges Chaudy <chaudyg@gmail.com>pull/103472/head
parent
ceed824378
commit
af8a70bbab
@ -0,0 +1,52 @@ |
||||
package apistore |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"fmt" |
||||
|
||||
"k8s.io/apimachinery/pkg/runtime" |
||||
|
||||
authtypes "github.com/grafana/authlib/types" |
||||
"github.com/grafana/grafana/pkg/apimachinery/utils" |
||||
"github.com/grafana/grafana/pkg/storage/unified/resource" |
||||
) |
||||
|
||||
type permissionCreatorFunc = func(ctx context.Context) error |
||||
|
||||
func afterCreatePermissionCreator(ctx context.Context, |
||||
key *resource.ResourceKey, |
||||
grantPermisions string, |
||||
obj runtime.Object, |
||||
setter DefaultPermissionSetter, |
||||
) (permissionCreatorFunc, error) { |
||||
if grantPermisions == "" { |
||||
return nil, nil |
||||
} |
||||
if grantPermisions != utils.AnnoGrantPermissionsDefault { |
||||
return nil, fmt.Errorf("invalid permissions value. only '%s' supported", utils.AnnoGrantPermissionsDefault) |
||||
} |
||||
if setter == nil { |
||||
return nil, fmt.Errorf("missing default permission creator") |
||||
} |
||||
val, err := utils.MetaAccessor(obj) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if val.GetAnnotation(utils.AnnoKeyManagerKind) != "" { |
||||
return nil, fmt.Errorf("managed resource may not grant permissions") |
||||
} |
||||
auth, ok := authtypes.AuthInfoFrom(ctx) |
||||
if !ok { |
||||
return nil, errors.New("missing auth info") |
||||
} |
||||
|
||||
idtype := auth.GetIdentityType() |
||||
if !(idtype == authtypes.TypeUser || idtype == authtypes.TypeServiceAccount) { |
||||
return nil, fmt.Errorf("only users or service accounts may grant themselves permissions using an annotation") |
||||
} |
||||
|
||||
return func(ctx context.Context) error { |
||||
return setter(ctx, key, auth, val) |
||||
}, nil |
||||
} |
||||
@ -0,0 +1,120 @@ |
||||
package apistore |
||||
|
||||
import ( |
||||
"context" |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/require" |
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
||||
|
||||
authtypes "github.com/grafana/authlib/types" |
||||
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" |
||||
"github.com/grafana/grafana/pkg/apimachinery/identity" |
||||
"github.com/grafana/grafana/pkg/apimachinery/utils" |
||||
"github.com/grafana/grafana/pkg/services/user" |
||||
"github.com/grafana/grafana/pkg/storage/unified/resource" |
||||
) |
||||
|
||||
func TestAfterCreatePermissionCreator(t *testing.T) { |
||||
mockSetter := func(ctx context.Context, key *resource.ResourceKey, auth authtypes.AuthInfo, val utils.GrafanaMetaAccessor) error { |
||||
return nil |
||||
} |
||||
|
||||
t.Run("should return nil when grantPermissions is empty", func(t *testing.T) { |
||||
creator, err := afterCreatePermissionCreator(context.Background(), nil, "", nil, mockSetter) |
||||
require.NoError(t, err) |
||||
require.Nil(t, creator) |
||||
}) |
||||
|
||||
t.Run("should error with invalid grantPermissions value", func(t *testing.T) { |
||||
creator, err := afterCreatePermissionCreator(context.Background(), nil, "invalid", nil, mockSetter) |
||||
require.Error(t, err) |
||||
require.Nil(t, creator) |
||||
require.Contains(t, err.Error(), "invalid permissions value") |
||||
}) |
||||
|
||||
t.Run("should error when setter is nil", func(t *testing.T) { |
||||
creator, err := afterCreatePermissionCreator(context.Background(), nil, utils.AnnoGrantPermissionsDefault, nil, nil) |
||||
require.Error(t, err) |
||||
require.Nil(t, creator) |
||||
require.Contains(t, err.Error(), "missing default permission creator") |
||||
}) |
||||
|
||||
t.Run("should error for managed resources", func(t *testing.T) { |
||||
obj := &v0alpha1.Dashboard{ |
||||
ObjectMeta: metav1.ObjectMeta{ |
||||
Annotations: map[string]string{ |
||||
utils.AnnoKeyManagerKind: "test", |
||||
}, |
||||
}, |
||||
} |
||||
creator, err := afterCreatePermissionCreator(context.Background(), nil, utils.AnnoGrantPermissionsDefault, obj, mockSetter) |
||||
require.Error(t, err) |
||||
require.Nil(t, creator) |
||||
require.Contains(t, err.Error(), "managed resource may not grant permissions") |
||||
}) |
||||
|
||||
t.Run("should error when auth info is missing", func(t *testing.T) { |
||||
obj := &v0alpha1.Dashboard{} |
||||
creator, err := afterCreatePermissionCreator(context.Background(), nil, utils.AnnoGrantPermissionsDefault, obj, mockSetter) |
||||
require.Error(t, err) |
||||
require.Nil(t, creator) |
||||
require.Contains(t, err.Error(), "missing auth info") |
||||
}) |
||||
|
||||
t.Run("should succeed for user identity", func(t *testing.T) { |
||||
ctx := identity.WithRequester(context.Background(), &user.SignedInUser{ |
||||
OrgID: 1, |
||||
OrgRole: "Admin", |
||||
UserID: 1, |
||||
}) |
||||
obj := &v0alpha1.Dashboard{} |
||||
key := &resource.ResourceKey{ |
||||
Group: "test", |
||||
Resource: "test", |
||||
Namespace: "test", |
||||
Name: "test", |
||||
} |
||||
|
||||
creator, err := afterCreatePermissionCreator(ctx, key, utils.AnnoGrantPermissionsDefault, obj, mockSetter) |
||||
require.NoError(t, err) |
||||
require.NotNil(t, creator) |
||||
|
||||
err = creator(ctx) |
||||
require.NoError(t, err) |
||||
}) |
||||
|
||||
t.Run("should succeed for service account identity", func(t *testing.T) { |
||||
ctx := identity.WithRequester(context.Background(), &user.SignedInUser{ |
||||
OrgID: 1, |
||||
OrgRole: "Admin", |
||||
UserID: 1, |
||||
}) |
||||
obj := &v0alpha1.Dashboard{} |
||||
key := &resource.ResourceKey{ |
||||
Group: "test", |
||||
Resource: "test", |
||||
Namespace: "test", |
||||
Name: "test", |
||||
} |
||||
|
||||
creator, err := afterCreatePermissionCreator(ctx, key, utils.AnnoGrantPermissionsDefault, obj, mockSetter) |
||||
require.NoError(t, err) |
||||
require.NotNil(t, creator) |
||||
|
||||
err = creator(ctx) |
||||
require.NoError(t, err) |
||||
}) |
||||
|
||||
t.Run("should error for non-user/non-service-account identity", func(t *testing.T) { |
||||
ctx := identity.WithRequester(context.Background(), &identity.StaticRequester{ |
||||
Type: authtypes.TypeAnonymous, |
||||
}) |
||||
obj := &v0alpha1.Dashboard{} |
||||
|
||||
creator, err := afterCreatePermissionCreator(ctx, nil, utils.AnnoGrantPermissionsDefault, obj, mockSetter) |
||||
require.Error(t, err) |
||||
require.Nil(t, creator) |
||||
require.Contains(t, err.Error(), "only users or service accounts may grant themselves permissions") |
||||
}) |
||||
} |
||||
Loading…
Reference in new issue