IAM: Target resource authorization for TeamBinding (#116117)

* wip

* Review VerbGet vs VerbGetPermissions

* Fix tests
This commit is contained in:
Misi
2026-01-13 14:45:18 +01:00
committed by GitHub
parent d2b788eb53
commit c9a14f1774
6 changed files with 496 additions and 55 deletions
+1 -1
View File
@@ -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)
})
}
}
+10 -2
View File
@@ -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: ""