IAM: Target resource authorization for TeamBinding (#116117)
* wip * Review VerbGet vs VerbGetPermissions * Fix tests
This commit is contained in:
@@ -42,7 +42,6 @@ func newIAMAuthorizer(
|
||||
|
||||
// Identity specific resources
|
||||
legacyAuthorizer := gfauthorizer.NewResourceAuthorizer(legacyAccessClient)
|
||||
resourceAuthorizer[iamv0.TeamBindingResourceInfo.GetName()] = legacyAuthorizer
|
||||
resourceAuthorizer["display"] = legacyAuthorizer
|
||||
|
||||
// Access specific resources
|
||||
@@ -55,6 +54,7 @@ func newIAMAuthorizer(
|
||||
resourceAuthorizer[iamv0.UserResourceInfo.GetName()] = authorizer
|
||||
resourceAuthorizer[iamv0.ExternalGroupMappingResourceInfo.GetName()] = allowAuthorizer
|
||||
resourceAuthorizer[iamv0.TeamResourceInfo.GetName()] = authorizer
|
||||
resourceAuthorizer[iamv0.TeamBindingResourceInfo.GetName()] = allowAuthorizer
|
||||
resourceAuthorizer["searchUsers"] = serviceAuthorizer
|
||||
resourceAuthorizer["searchTeams"] = serviceAuthorizer
|
||||
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
package authorizer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/authlib/types"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer/storewrapper"
|
||||
)
|
||||
|
||||
type TeamBindingAuthorizer struct {
|
||||
accessClient types.AccessClient
|
||||
}
|
||||
|
||||
var _ storewrapper.ResourceStorageAuthorizer = (*TeamBindingAuthorizer)(nil)
|
||||
|
||||
func NewTeamBindingAuthorizer(
|
||||
accessClient types.AccessClient,
|
||||
) *TeamBindingAuthorizer {
|
||||
return &TeamBindingAuthorizer{
|
||||
accessClient: accessClient,
|
||||
}
|
||||
}
|
||||
|
||||
// AfterGet implements ResourceStorageAuthorizer.
|
||||
func (r *TeamBindingAuthorizer) AfterGet(ctx context.Context, obj runtime.Object) error {
|
||||
authInfo, ok := types.AuthInfoFrom(ctx)
|
||||
if !ok {
|
||||
return storewrapper.ErrUnauthenticated
|
||||
}
|
||||
|
||||
concreteObj, ok := obj.(*iamv0.TeamBinding)
|
||||
if !ok {
|
||||
return apierrors.NewInternalError(fmt.Errorf("expected TeamBinding, got %T: %w", obj, storewrapper.ErrUnexpectedType))
|
||||
}
|
||||
|
||||
// Accesscontrol should check on the TeamResourceInfo group resource if the user can use VerbGetPermissions
|
||||
// on the team (TeamRef.Name) (handled below) OR if the subject's name (TeamBindingSpec.Subject.Name) is equal to the current Identity's UID/Identifier.
|
||||
if concreteObj.Spec.Subject.Name == authInfo.GetIdentifier() {
|
||||
return nil
|
||||
}
|
||||
|
||||
teamName := concreteObj.Spec.TeamRef.Name
|
||||
checkReq := types.CheckRequest{
|
||||
Namespace: authInfo.GetNamespace(),
|
||||
Group: iamv0.TeamResourceInfo.GroupResource().Group,
|
||||
Resource: iamv0.TeamResourceInfo.GroupResource().Resource,
|
||||
Verb: utils.VerbGetPermissions,
|
||||
Name: teamName,
|
||||
}
|
||||
res, err := r.accessClient.Check(ctx, authInfo, checkReq, "")
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
|
||||
if !res.Allowed {
|
||||
return apierrors.NewForbidden(
|
||||
iamv0.TeamBindingResourceInfo.GroupResource(),
|
||||
concreteObj.Name,
|
||||
fmt.Errorf("user cannot access team %s", teamName),
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeCreate implements ResourceStorageAuthorizer.
|
||||
func (r *TeamBindingAuthorizer) BeforeCreate(ctx context.Context, obj runtime.Object) error {
|
||||
return r.beforeWrite(ctx, obj)
|
||||
}
|
||||
|
||||
// BeforeDelete implements ResourceStorageAuthorizer.
|
||||
func (r *TeamBindingAuthorizer) BeforeDelete(ctx context.Context, obj runtime.Object) error {
|
||||
return r.beforeWrite(ctx, obj)
|
||||
}
|
||||
|
||||
// BeforeUpdate implements ResourceStorageAuthorizer.
|
||||
func (r *TeamBindingAuthorizer) BeforeUpdate(ctx context.Context, obj runtime.Object) error {
|
||||
return r.beforeWrite(ctx, obj)
|
||||
}
|
||||
|
||||
func (r *TeamBindingAuthorizer) beforeWrite(ctx context.Context, obj runtime.Object) error {
|
||||
authInfo, ok := types.AuthInfoFrom(ctx)
|
||||
if !ok {
|
||||
return storewrapper.ErrUnauthenticated
|
||||
}
|
||||
|
||||
concreteObj, ok := obj.(*iamv0.TeamBinding)
|
||||
if !ok {
|
||||
return apierrors.NewInternalError(fmt.Errorf("expected TeamBinding, got %T: %w", obj, storewrapper.ErrUnexpectedType))
|
||||
}
|
||||
|
||||
teamName := concreteObj.Spec.TeamRef.Name
|
||||
checkReq := types.CheckRequest{
|
||||
Namespace: authInfo.GetNamespace(),
|
||||
Group: iamv0.GROUP,
|
||||
Resource: iamv0.TeamResourceInfo.GetName(),
|
||||
Verb: utils.VerbSetPermissions,
|
||||
Name: teamName,
|
||||
}
|
||||
|
||||
res, err := r.accessClient.Check(ctx, authInfo, checkReq, "")
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
|
||||
if !res.Allowed {
|
||||
return apierrors.NewForbidden(
|
||||
iamv0.TeamBindingResourceInfo.GroupResource(),
|
||||
concreteObj.Name,
|
||||
fmt.Errorf("user cannot write team %s", teamName),
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FilterList implements ResourceStorageAuthorizer.
|
||||
func (r *TeamBindingAuthorizer) FilterList(ctx context.Context, list runtime.Object) (runtime.Object, error) {
|
||||
authInfo, ok := types.AuthInfoFrom(ctx)
|
||||
if !ok {
|
||||
return nil, storewrapper.ErrUnauthenticated
|
||||
}
|
||||
|
||||
l, ok := list.(*iamv0.TeamBindingList)
|
||||
if !ok {
|
||||
return nil, apierrors.NewInternalError(fmt.Errorf("expected TeamBindingList, got %T: %w", list, storewrapper.ErrUnexpectedType))
|
||||
}
|
||||
|
||||
var filteredItems []iamv0.TeamBinding
|
||||
|
||||
listReq := types.ListRequest{
|
||||
Namespace: authInfo.GetNamespace(),
|
||||
Group: iamv0.TeamResourceInfo.GroupResource().Group,
|
||||
Resource: iamv0.TeamResourceInfo.GroupResource().Resource,
|
||||
Verb: utils.VerbGetPermissions,
|
||||
}
|
||||
canView, _, err := r.accessClient.Compile(ctx, authInfo, listReq)
|
||||
if err != nil {
|
||||
return nil, apierrors.NewInternalError(err)
|
||||
}
|
||||
|
||||
for _, item := range l.Items {
|
||||
// Accesscontrol should check on the TeamResourceInfo group resource if the user can use VerbGetPermissions
|
||||
// on the team (TeamRef.Name) OR if the subject's name (TeamBindingSpec.Subject.Name) is equal to the current Identity's UID/Identifier.
|
||||
if item.Spec.Subject.Name == authInfo.GetIdentifier() || canView(item.Spec.TeamRef.Name, "") {
|
||||
filteredItems = append(filteredItems, item)
|
||||
}
|
||||
}
|
||||
|
||||
l.Items = filteredItems
|
||||
return l, nil
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
package authorizer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/grafana/authlib/types"
|
||||
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
)
|
||||
|
||||
func newTeamBinding(teamName, name, subjectName string) *iamv0.TeamBinding {
|
||||
return &iamv0.TeamBinding{
|
||||
ObjectMeta: metav1.ObjectMeta{Namespace: "org-2", Name: name},
|
||||
Spec: iamv0.TeamBindingSpec{
|
||||
TeamRef: iamv0.TeamBindingTeamRef{
|
||||
Name: teamName,
|
||||
},
|
||||
Subject: iamv0.TeamBindingspecSubject{
|
||||
Name: subjectName,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestTeamBinding_AfterGet(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
teamBinding *iamv0.TeamBinding
|
||||
shouldAllow bool
|
||||
checkCalled bool
|
||||
}{
|
||||
{
|
||||
name: "allow access via permission",
|
||||
teamBinding: newTeamBinding("team-1", "binding-1", "other"),
|
||||
shouldAllow: true,
|
||||
checkCalled: true,
|
||||
},
|
||||
{
|
||||
name: "deny access",
|
||||
teamBinding: newTeamBinding("team-1", "binding-1", "other"),
|
||||
shouldAllow: false,
|
||||
checkCalled: true, // called but returns allowed=false
|
||||
},
|
||||
{
|
||||
name: "allow access via subject match",
|
||||
teamBinding: newTeamBinding("team-1", "binding-1", "u001"),
|
||||
shouldAllow: true,
|
||||
checkCalled: false, // short-circuits
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
checkFunc := func(id types.AuthInfo, req *types.CheckRequest, folder string) (types.CheckResponse, error) {
|
||||
require.NotNil(t, id)
|
||||
require.Equal(t, "u001", id.GetIdentifier())
|
||||
|
||||
require.Equal(t, "org-2", req.Namespace)
|
||||
require.Equal(t, iamv0.GROUP, req.Group)
|
||||
require.Equal(t, iamv0.TeamResourceInfo.GetName(), req.Resource)
|
||||
require.Equal(t, "team-1", req.Name)
|
||||
require.Equal(t, utils.VerbGetPermissions, req.Verb)
|
||||
|
||||
return types.CheckResponse{Allowed: tt.shouldAllow}, nil
|
||||
}
|
||||
|
||||
accessClient := &fakeAccessClient{checkFunc: checkFunc}
|
||||
authz := NewTeamBindingAuthorizer(accessClient)
|
||||
ctx := types.WithAuthInfo(context.Background(), user)
|
||||
|
||||
err := authz.AfterGet(ctx, tt.teamBinding)
|
||||
if tt.shouldAllow {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
}
|
||||
require.Equal(t, tt.checkCalled, accessClient.checkCalled)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTeamBinding_FilterList(t *testing.T) {
|
||||
list := &iamv0.TeamBindingList{
|
||||
Items: []iamv0.TeamBinding{
|
||||
*newTeamBinding("team-1", "binding-1", "other"), // Access via permission
|
||||
*newTeamBinding("team-2", "binding-2", "other"), // No access
|
||||
*newTeamBinding("team-3", "binding-3", "u001"), // Access via subject match
|
||||
},
|
||||
}
|
||||
|
||||
compileFunc := func(id types.AuthInfo, req types.ListRequest) (types.ItemChecker, types.Zookie, error) {
|
||||
require.NotNil(t, id)
|
||||
require.Equal(t, "u001", id.GetIdentifier())
|
||||
|
||||
require.Equal(t, "org-2", req.Namespace)
|
||||
require.Equal(t, iamv0.GROUP, req.Group)
|
||||
require.Equal(t, iamv0.TeamResourceInfo.GroupResource().Resource, req.Resource)
|
||||
require.Equal(t, utils.VerbGetPermissions, req.Verb)
|
||||
|
||||
return func(name, folder string) bool {
|
||||
return name == "team-1"
|
||||
}, &types.NoopZookie{}, nil
|
||||
}
|
||||
|
||||
accessClient := &fakeAccessClient{compileFunc: compileFunc}
|
||||
authz := NewTeamBindingAuthorizer(accessClient)
|
||||
ctx := types.WithAuthInfo(context.Background(), user)
|
||||
|
||||
obj, err := authz.FilterList(ctx, list)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, list)
|
||||
require.True(t, accessClient.compileCalled)
|
||||
|
||||
filtered, ok := obj.(*iamv0.TeamBindingList)
|
||||
require.True(t, ok)
|
||||
require.Len(t, filtered.Items, 2)
|
||||
|
||||
names := []string{filtered.Items[0].Name, filtered.Items[1].Name}
|
||||
require.Contains(t, names, "binding-1")
|
||||
require.Contains(t, names, "binding-3")
|
||||
}
|
||||
|
||||
func TestTeamBinding_BeforeCreate(t *testing.T) {
|
||||
binding := newTeamBinding("team-1", "binding-1", "other")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
shouldAllow bool
|
||||
}{
|
||||
{
|
||||
name: "allow create",
|
||||
shouldAllow: true,
|
||||
},
|
||||
{
|
||||
name: "deny create",
|
||||
shouldAllow: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
checkFunc := func(id types.AuthInfo, req *types.CheckRequest, folder string) (types.CheckResponse, error) {
|
||||
require.Equal(t, "org-2", req.Namespace)
|
||||
require.Equal(t, iamv0.GROUP, req.Group)
|
||||
require.Equal(t, iamv0.TeamResourceInfo.GetName(), req.Resource)
|
||||
require.Equal(t, "team-1", req.Name)
|
||||
require.Equal(t, utils.VerbSetPermissions, req.Verb)
|
||||
|
||||
return types.CheckResponse{Allowed: tt.shouldAllow}, nil
|
||||
}
|
||||
|
||||
accessClient := &fakeAccessClient{checkFunc: checkFunc}
|
||||
authz := NewTeamBindingAuthorizer(accessClient)
|
||||
ctx := types.WithAuthInfo(context.Background(), user)
|
||||
|
||||
err := authz.BeforeCreate(ctx, binding)
|
||||
if tt.shouldAllow {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
}
|
||||
require.True(t, accessClient.checkCalled)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTeamBinding_BeforeUpdate(t *testing.T) {
|
||||
binding := newTeamBinding("team-1", "binding-1", "other")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
shouldAllow bool
|
||||
}{
|
||||
{
|
||||
name: "allow update",
|
||||
shouldAllow: true,
|
||||
},
|
||||
{
|
||||
name: "deny update",
|
||||
shouldAllow: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
checkFunc := func(id types.AuthInfo, req *types.CheckRequest, folder string) (types.CheckResponse, error) {
|
||||
require.Equal(t, "org-2", req.Namespace)
|
||||
require.Equal(t, iamv0.GROUP, req.Group)
|
||||
require.Equal(t, iamv0.TeamResourceInfo.GetName(), req.Resource)
|
||||
require.Equal(t, "team-1", req.Name)
|
||||
require.Equal(t, utils.VerbSetPermissions, req.Verb)
|
||||
|
||||
return types.CheckResponse{Allowed: tt.shouldAllow}, nil
|
||||
}
|
||||
|
||||
accessClient := &fakeAccessClient{checkFunc: checkFunc}
|
||||
authz := NewTeamBindingAuthorizer(accessClient)
|
||||
ctx := types.WithAuthInfo(context.Background(), user)
|
||||
|
||||
err := authz.BeforeUpdate(ctx, binding)
|
||||
if tt.shouldAllow {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
}
|
||||
require.True(t, accessClient.checkCalled)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTeamBinding_BeforeDelete(t *testing.T) {
|
||||
binding := newTeamBinding("team-1", "binding-1", "other")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
shouldAllow bool
|
||||
}{
|
||||
{
|
||||
name: "allow delete",
|
||||
shouldAllow: true,
|
||||
},
|
||||
{
|
||||
name: "deny delete",
|
||||
shouldAllow: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
checkFunc := func(id types.AuthInfo, req *types.CheckRequest, folder string) (types.CheckResponse, error) {
|
||||
require.Equal(t, "org-2", req.Namespace)
|
||||
require.Equal(t, iamv0.GROUP, req.Group)
|
||||
require.Equal(t, iamv0.TeamResourceInfo.GetName(), req.Resource)
|
||||
require.Equal(t, "team-1", req.Name)
|
||||
require.Equal(t, utils.VerbSetPermissions, req.Verb)
|
||||
|
||||
return types.CheckResponse{Allowed: tt.shouldAllow}, nil
|
||||
}
|
||||
|
||||
accessClient := &fakeAccessClient{checkFunc: checkFunc}
|
||||
authz := NewTeamBindingAuthorizer(accessClient)
|
||||
ctx := types.WithAuthInfo(context.Background(), user)
|
||||
|
||||
err := authz.BeforeDelete(ctx, binding)
|
||||
if tt.shouldAllow {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
}
|
||||
require.True(t, accessClient.checkCalled)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -361,7 +361,7 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateTeamBindingsAPIGroup(opts bui
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
storage[teamBindingResource.StoragePath()] = teamBindingUniStore
|
||||
var teamBindingStore storewrapper.K8sStorage = teamBindingUniStore
|
||||
|
||||
// Only teamBindingStore exposes the AfterCreate, AfterDelete, and BeginUpdate hooks
|
||||
if enableZanzanaSync {
|
||||
@@ -376,8 +376,16 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateTeamBindingsAPIGroup(opts bui
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
storage[teamBindingResource.StoragePath()] = dw
|
||||
|
||||
var ok bool
|
||||
teamBindingStore, ok = dw.(storewrapper.K8sStorage)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected storewrapper.K8sStorage, got %T", dw)
|
||||
}
|
||||
}
|
||||
|
||||
authzWrapper := storewrapper.New(teamBindingStore, iamauthorizer.NewTeamBindingAuthorizer(b.accessClient))
|
||||
storage[teamBindingResource.StoragePath()] = authzWrapper
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ func TestIntegrationTeamBindings(t *testing.T) {
|
||||
doTeamBindingCRUDTestsUsingTheNewAPIs(t, helper, team, user)
|
||||
|
||||
if mode < 3 {
|
||||
doTeamBindingCRUDTestsUsingTheLegacyAPIs(t, helper, mode)
|
||||
doTeamBindingCRUDTestsUsingTheLegacyAPIs(t, helper)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -84,13 +84,15 @@ func doTeamBindingCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHel
|
||||
})
|
||||
|
||||
// Create the team binding
|
||||
toCreate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml")
|
||||
toCreate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = user.GetName()
|
||||
toCreate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = team.GetName()
|
||||
toCreate := createTeamBindingObject(helper, user.GetName(), team.GetName())
|
||||
created, err := teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, created)
|
||||
|
||||
defer func() {
|
||||
_ = teamBindingClient.Resource.Delete(ctx, created.GetName(), metav1.DeleteOptions{})
|
||||
}()
|
||||
|
||||
createdSpec := created.Object["spec"].(map[string]interface{})
|
||||
require.Equal(t, user.GetName(), createdSpec["subject"].(map[string]interface{})["name"])
|
||||
require.Equal(t, team.GetName(), createdSpec["teamRef"].(map[string]interface{})["name"])
|
||||
@@ -115,6 +117,7 @@ func doTeamBindingCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHel
|
||||
// Update the team binding
|
||||
toUpdate := toCreate.DeepCopy()
|
||||
toUpdate.Object["spec"].(map[string]interface{})["permission"] = "member"
|
||||
toUpdate.Object["metadata"].(map[string]interface{})["name"] = createdUID
|
||||
updated, err := teamBindingClient.Resource.Update(ctx, toUpdate, metav1.UpdateOptions{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, updated)
|
||||
@@ -164,9 +167,7 @@ func doTeamBindingCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHel
|
||||
GVR: gvrTeamBindings,
|
||||
})
|
||||
|
||||
toCreate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml")
|
||||
toCreate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = user.GetName()
|
||||
toCreate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = team.GetName()
|
||||
toCreate := createTeamBindingObject(helper, user.GetName(), team.GetName())
|
||||
_, err := teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
||||
require.Error(t, err)
|
||||
|
||||
@@ -185,9 +186,7 @@ func doTeamBindingCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHel
|
||||
GVR: gvrTeamBindings,
|
||||
})
|
||||
|
||||
toCreate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml")
|
||||
toCreate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = ""
|
||||
toCreate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = team.GetName()
|
||||
toCreate := createTeamBindingObject(helper, "", team.GetName())
|
||||
|
||||
_, err := teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
||||
require.Error(t, err)
|
||||
@@ -205,9 +204,7 @@ func doTeamBindingCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHel
|
||||
GVR: gvrTeamBindings,
|
||||
})
|
||||
|
||||
toCreate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml")
|
||||
toCreate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = user.GetName()
|
||||
toCreate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = ""
|
||||
toCreate := createTeamBindingObject(helper, user.GetName(), "")
|
||||
|
||||
_, err := teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
||||
require.Error(t, err)
|
||||
@@ -225,9 +222,7 @@ func doTeamBindingCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHel
|
||||
GVR: gvrTeamBindings,
|
||||
})
|
||||
|
||||
toCreate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml")
|
||||
toCreate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = user.GetName()
|
||||
toCreate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = team.GetName()
|
||||
toCreate := createTeamBindingObject(helper, user.GetName(), team.GetName())
|
||||
toCreate.Object["spec"].(map[string]interface{})["permission"] = "invalid"
|
||||
|
||||
_, err := teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
||||
@@ -245,17 +240,31 @@ func doTeamBindingCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHel
|
||||
} {
|
||||
t.Run(fmt.Sprintf("with basic role_%s", u.Identity.GetOrgRole()), func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Create the team binding using admin
|
||||
adminClient := helper.GetResourceClient(apis.ResourceClientArgs{
|
||||
User: helper.Org1.Admin,
|
||||
Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()),
|
||||
GVR: gvrTeamBindings,
|
||||
})
|
||||
|
||||
toCreate := createTeamBindingObject(helper, user.GetName(), team.GetName())
|
||||
created, err := adminClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
_ = adminClient.Resource.Delete(ctx, created.GetName(), metav1.DeleteOptions{})
|
||||
}()
|
||||
|
||||
teamBindingClient := helper.GetResourceClient(apis.ResourceClientArgs{
|
||||
User: u,
|
||||
Namespace: helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID()),
|
||||
GVR: gvrTeamBindings,
|
||||
})
|
||||
|
||||
toUpdate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml")
|
||||
toUpdate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = user.GetName()
|
||||
toUpdate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = team.GetName()
|
||||
toUpdate := created.DeepCopy()
|
||||
toUpdate.Object["spec"].(map[string]interface{})["permission"] = "member"
|
||||
_, err := teamBindingClient.Resource.Update(ctx, toUpdate, metav1.UpdateOptions{})
|
||||
_, err = teamBindingClient.Resource.Update(ctx, toUpdate, metav1.UpdateOptions{})
|
||||
require.Error(t, err)
|
||||
|
||||
var statusErr *errors.StatusError
|
||||
@@ -273,10 +282,8 @@ func doTeamBindingCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHel
|
||||
GVR: gvrTeamBindings,
|
||||
})
|
||||
|
||||
toUpdate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml")
|
||||
toUpdate := createTeamBindingObject(helper, user.GetName(), team.GetName())
|
||||
toUpdate.Object["metadata"].(map[string]interface{})["name"] = "invalid-team-binding-name"
|
||||
toUpdate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = user.GetName()
|
||||
toUpdate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = team.GetName()
|
||||
_, err := teamBindingClient.Resource.Update(ctx, toUpdate, metav1.UpdateOptions{})
|
||||
require.Error(t, err)
|
||||
var statusErr *errors.StatusError
|
||||
@@ -293,15 +300,18 @@ func doTeamBindingCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHel
|
||||
GVR: gvrTeamBindings,
|
||||
})
|
||||
|
||||
// Create the team binding if it doesn't already exist
|
||||
toCreate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml")
|
||||
toCreate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = user.GetName()
|
||||
toCreate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = team.GetName()
|
||||
_, _ = teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
||||
toCreate := createTeamBindingObject(helper, user.GetName(), team.GetName())
|
||||
created, err := teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
_ = teamBindingClient.Resource.Delete(ctx, created.GetName(), metav1.DeleteOptions{})
|
||||
}()
|
||||
|
||||
toUpdate := toCreate.DeepCopy()
|
||||
toUpdate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = "test-team-2"
|
||||
_, err := teamBindingClient.Resource.Update(ctx, toUpdate, metav1.UpdateOptions{})
|
||||
toUpdate.Object["metadata"].(map[string]interface{})["name"] = created.GetName()
|
||||
_, err = teamBindingClient.Resource.Update(ctx, toUpdate, metav1.UpdateOptions{})
|
||||
require.Error(t, err)
|
||||
var statusErr *errors.StatusError
|
||||
require.ErrorAs(t, err, &statusErr)
|
||||
@@ -317,16 +327,19 @@ func doTeamBindingCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHel
|
||||
GVR: gvrTeamBindings,
|
||||
})
|
||||
|
||||
// Create the team binding if it doesn't already exist
|
||||
toCreate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml")
|
||||
toCreate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = user.GetName()
|
||||
toCreate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = team.GetName()
|
||||
_, _ = teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
||||
toCreate := createTeamBindingObject(helper, user.GetName(), team.GetName())
|
||||
created, err := teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
_ = teamBindingClient.Resource.Delete(ctx, created.GetName(), metav1.DeleteOptions{})
|
||||
}()
|
||||
|
||||
toUpdate := toCreate.DeepCopy()
|
||||
toUpdate.Object["metadata"].(map[string]interface{})["name"] = created.GetName()
|
||||
toUpdate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = "test-user-2"
|
||||
|
||||
_, err := teamBindingClient.Resource.Update(ctx, toUpdate, metav1.UpdateOptions{})
|
||||
_, err = teamBindingClient.Resource.Update(ctx, toUpdate, metav1.UpdateOptions{})
|
||||
require.Error(t, err)
|
||||
var statusErr *errors.StatusError
|
||||
require.ErrorAs(t, err, &statusErr)
|
||||
@@ -342,15 +355,18 @@ func doTeamBindingCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHel
|
||||
GVR: gvrTeamBindings,
|
||||
})
|
||||
|
||||
// Create the team binding if it doesn't already exist
|
||||
toCreate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml")
|
||||
toCreate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = user.GetName()
|
||||
toCreate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = team.GetName()
|
||||
_, _ = teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
||||
toCreate := createTeamBindingObject(helper, user.GetName(), team.GetName())
|
||||
created, err := teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
defer func() {
|
||||
_ = teamBindingClient.Resource.Delete(ctx, created.GetName(), metav1.DeleteOptions{})
|
||||
}()
|
||||
|
||||
toUpdate := toCreate.DeepCopy()
|
||||
toUpdate.Object["spec"].(map[string]interface{})["external"] = true
|
||||
_, err := teamBindingClient.Resource.Update(ctx, toUpdate, metav1.UpdateOptions{})
|
||||
toUpdate.Object["metadata"].(map[string]interface{})["name"] = created.GetName()
|
||||
_, err = teamBindingClient.Resource.Update(ctx, toUpdate, metav1.UpdateOptions{})
|
||||
require.Error(t, err)
|
||||
var statusErr *errors.StatusError
|
||||
require.ErrorAs(t, err, &statusErr)
|
||||
@@ -366,17 +382,18 @@ func doTeamBindingCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHel
|
||||
GVR: gvrTeamBindings,
|
||||
})
|
||||
|
||||
// Create the team binding if it doesn't already exist
|
||||
toCreate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml")
|
||||
toCreate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = user.GetName()
|
||||
toCreate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = team.GetName()
|
||||
_, _ = teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
||||
toCreate := createTeamBindingObject(helper, user.GetName(), team.GetName())
|
||||
created, err := teamBindingClient.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
toUpdate := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml")
|
||||
toUpdate.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = user.GetName()
|
||||
toUpdate.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = team.GetName()
|
||||
defer func() {
|
||||
_ = teamBindingClient.Resource.Delete(ctx, created.GetName(), metav1.DeleteOptions{})
|
||||
}()
|
||||
|
||||
toUpdate := createTeamBindingObject(helper, user.GetName(), team.GetName())
|
||||
toUpdate.Object["spec"].(map[string]interface{})["permission"] = "invalid"
|
||||
_, err := teamBindingClient.Resource.Update(ctx, toUpdate, metav1.UpdateOptions{})
|
||||
toUpdate.Object["metadata"].(map[string]interface{})["name"] = created.GetName()
|
||||
_, err = teamBindingClient.Resource.Update(ctx, toUpdate, metav1.UpdateOptions{})
|
||||
require.Error(t, err)
|
||||
var statusErr *errors.StatusError
|
||||
require.ErrorAs(t, err, &statusErr)
|
||||
@@ -385,7 +402,7 @@ func doTeamBindingCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHel
|
||||
})
|
||||
}
|
||||
|
||||
func doTeamBindingCRUDTestsUsingTheLegacyAPIs(t *testing.T, helper *apis.K8sTestHelper, mode rest.DualWriterMode) {
|
||||
func doTeamBindingCRUDTestsUsingTheLegacyAPIs(t *testing.T, helper *apis.K8sTestHelper) {
|
||||
t.Run("should create team binding using legacy APIs and get it using the new APIs", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
@@ -499,3 +516,10 @@ func doTeamBindingCRUDTestsUsingTheLegacyAPIs(t *testing.T, helper *apis.K8sTest
|
||||
require.Equal(t, teamBindingName, teamBinding.GetName())
|
||||
})
|
||||
}
|
||||
|
||||
func createTeamBindingObject(helper *apis.K8sTestHelper, userName, teamName string) *unstructured.Unstructured {
|
||||
obj := helper.LoadYAMLOrJSONFile("testdata/teambinding-test-create-v0.yaml")
|
||||
obj.Object["spec"].(map[string]interface{})["subject"].(map[string]interface{})["name"] = userName
|
||||
obj.Object["spec"].(map[string]interface{})["teamRef"].(map[string]interface{})["name"] = teamName
|
||||
return obj
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
apiVersion: iam.grafana.app/v0alpha1
|
||||
kind: TeamBinding
|
||||
metadata:
|
||||
name: test-team-binding-1
|
||||
generateName: test-team-binding-
|
||||
spec:
|
||||
subject:
|
||||
name: ""
|
||||
|
||||
Reference in New Issue
Block a user