grafana-iam: Use an API Installer interface (#115310)

* `grafana-iam`: Add basic roles to the apis

* Fix validation

* chore: trigger CI

* Leave the hooks intact for now, moving them later

* Remove Role mention from the interface

* Refactor to use a NoopRest backend and Deny access
This commit is contained in:
Gabriel MABILLE
2025-12-18 10:29:50 +01:00
committed by GitHub
parent f970cbb42b
commit e5b0353c41
9 changed files with 275 additions and 99 deletions
+79
View File
@@ -0,0 +1,79 @@
package iam
import (
"context"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/server"
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/registry/apis/iam/noopstorage"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
)
// ApiInstaller is a generic interface for installing API resources.
// This allows different implementations (OSS noop, Enterprise with features)
// to provide their own resource registration and validation logic.
type ApiInstaller[T runtime.Object] interface {
// GetAuthorizer returns the authorizer for the API group.
GetAuthorizer() authorizer.Authorizer
// RegisterStorage registers storage and hooks with the API group.
RegisterStorage(apiGroupInfo *server.APIGroupInfo, opts *builder.APIGroupOptions, storage map[string]rest.Storage) error
// ValidateOnCreate validates object creation.
ValidateOnCreate(ctx context.Context, obj T) error
// ValidateOnUpdate validates object updates.
ValidateOnUpdate(ctx context.Context, oldObj, newObj T) error
// ValidateOnDelete validates object deletion.
ValidateOnDelete(ctx context.Context, obj T) error
}
// RoleApiInstaller provides Role-specific API registration and validation.
// This interface allows enterprise implementations to provide custom role handling
// while keeping the core IAM registration logic in OSS.
type RoleApiInstaller ApiInstaller[*iamv0.Role]
// NoopApiInstaller is a no-op implementation for when roles are not available (OSS).
type NoopApiInstaller[T runtime.Object] struct {
ResourceInfo utils.ResourceInfo
}
func (n *NoopApiInstaller[T]) GetAuthorizer() authorizer.Authorizer {
return authorizer.AuthorizerFunc(func(ctx context.Context, attr authorizer.Attributes) (authorizer.Decision, string, error) {
return authorizer.DecisionDeny, "Unavailable functionality", nil
})
}
func (n *NoopApiInstaller[T]) RegisterStorage(apiGroupInfo *server.APIGroupInfo, opts *builder.APIGroupOptions, storage map[string]rest.Storage) error {
storage[n.ResourceInfo.StoragePath()] = &noopstorage.NoopREST{ResourceInfo: n.ResourceInfo}
return nil
}
func (n *NoopApiInstaller[T]) ValidateOnCreate(ctx context.Context, obj T) error {
// No validation needed in OSS
return nil
}
func (n *NoopApiInstaller[T]) ValidateOnUpdate(ctx context.Context, oldObj, newObj T) error {
// No validation needed in OSS
return nil
}
func (n *NoopApiInstaller[T]) ValidateOnDelete(ctx context.Context, obj T) error {
// No validation needed in OSS
return nil
}
// ProvideNoopRoleApiInstaller provides a no-op role installer specifically for Role types.
// This is needed for Wire dependency injection which doesn't handle generic functions well.
func ProvideNoopRoleApiInstaller() RoleApiInstaller {
return &NoopApiInstaller[*iamv0.Role]{
ResourceInfo: iamv0.RoleInfo,
}
}
+6 -2
View File
@@ -19,7 +19,11 @@ type iamAuthorizer struct {
resourceAuthorizer map[string]authorizer.Authorizer // Map resource to its authorizer
}
func newIAMAuthorizer(accessClient authlib.AccessClient, legacyAccessClient authlib.AccessClient) authorizer.Authorizer {
func newIAMAuthorizer(
accessClient authlib.AccessClient,
legacyAccessClient authlib.AccessClient,
roleApiInstaller RoleApiInstaller,
) authorizer.Authorizer {
resourceAuthorizer := make(map[string]authorizer.Authorizer)
serviceAuthorizer := gfauthorizer.NewServiceAuthorizer()
@@ -44,7 +48,7 @@ func newIAMAuthorizer(accessClient authlib.AccessClient, legacyAccessClient auth
// Access specific resources
authorizer := gfauthorizer.NewResourceAuthorizer(accessClient)
resourceAuthorizer[iamv0.CoreRoleInfo.GetName()] = iamauthorizer.NewCoreRoleAuthorizer(accessClient)
resourceAuthorizer[iamv0.RoleInfo.GetName()] = authorizer
resourceAuthorizer[iamv0.RoleInfo.GetName()] = roleApiInstaller.GetAuthorizer()
resourceAuthorizer[iamv0.ResourcePermissionInfo.GetName()] = allowAuthorizer // Handled by the backend wrapper
resourceAuthorizer[iamv0.RoleBindingInfo.GetName()] = authorizer
resourceAuthorizer[iamv0.ServiceAccountResourceInfo.GetName()] = authorizer
+1 -1
View File
@@ -56,7 +56,7 @@ type IdentityAccessManagementAPIBuilder struct {
teamBindingLegacyStore *teambinding.LegacyBindingStore
ssoLegacyStore *sso.LegacyStore
coreRolesStorage CoreRoleStorageBackend
rolesStorage RoleStorageBackend
roleApiInstaller RoleApiInstaller
resourcePermissionsStorage resource.StorageBackend
roleBindingsStorage RoleBindingStorageBackend
externalGroupMappingStorage ExternalGroupMappingStorageBackend
+74
View File
@@ -0,0 +1,74 @@
package noopstorage
import (
"context"
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/apiserver/pkg/registry/rest"
"github.com/grafana/grafana/pkg/apimachinery/utils"
)
type NoopREST struct {
ResourceInfo utils.ResourceInfo
}
var (
_ rest.Storage = (*NoopREST)(nil)
_ rest.Scoper = (*NoopREST)(nil)
_ rest.SingularNameProvider = (*NoopREST)(nil)
_ rest.Getter = (*NoopREST)(nil)
)
func (n *NoopREST) New() runtime.Object {
return n.ResourceInfo.NewFunc()
}
func (n *NoopREST) NewList() runtime.Object {
return n.ResourceInfo.NewListFunc()
}
func (n *NoopREST) NamespaceScoped() bool {
return true
}
func (n *NoopREST) GetSingularName() string {
return n.ResourceInfo.GetSingularName()
}
func (n *NoopREST) Destroy() {}
func (n *NoopREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
return nil, errNoopStorage
}
func (n *NoopREST) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
return nil, errNoopStorage
}
func (n *NoopREST) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
return nil, errNoopStorage
}
func (n *NoopREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
return nil, false, errNoopStorage
}
func (n *NoopREST) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
return nil, false, errNoopStorage
}
func (n *NoopREST) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) {
return nil, errNoopStorage
}
func (n *NoopREST) Watch(ctx context.Context, options *internalversion.ListOptions) (watch.Interface, error) {
return nil, errNoopStorage
}
func (n *NoopREST) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
return nil, errNoopStorage
}
+23 -17
View File
@@ -72,7 +72,7 @@ func RegisterAPIService(
zClient zanzana.Client,
reg prometheus.Registerer,
coreRolesStorage CoreRoleStorageBackend,
rolesStorage RoleStorageBackend,
roleApiInstaller RoleApiInstaller,
tracing *tracing.TracingService,
roleBindingsStorage RoleBindingStorageBackend,
externalGroupMappingStorageBackend ExternalGroupMappingStorageBackend,
@@ -87,7 +87,7 @@ func RegisterAPIService(
dbProvider := legacysql.NewDatabaseProvider(sql)
store := legacy.NewLegacySQLStores(dbProvider)
legacyAccessClient := newLegacyAccessClient(ac, store)
authorizer := newIAMAuthorizer(accessClient, legacyAccessClient)
authorizer := newIAMAuthorizer(accessClient, legacyAccessClient, roleApiInstaller)
registerMetrics(reg)
//nolint:staticcheck // not yet migrated to OpenFeature
@@ -106,7 +106,7 @@ func RegisterAPIService(
teamBindingLegacyStore: teambinding.NewLegacyBindingStore(store, enableAuthnMutation, tracing),
ssoLegacyStore: sso.NewLegacyStore(ssoService, tracing),
coreRolesStorage: coreRolesStorage,
rolesStorage: rolesStorage,
roleApiInstaller: roleApiInstaller,
resourcePermissionsStorage: resourcepermission.ProvideStorageBackend(dbProvider),
roleBindingsStorage: roleBindingsStorage,
externalGroupMappingStorage: externalGroupMappingStorageBackend,
@@ -139,7 +139,7 @@ func NewAPIService(
accessClient types.AccessClient,
dbProvider legacysql.LegacyDatabaseProvider,
coreRoleStorage CoreRoleStorageBackend,
roleStorage RoleStorageBackend,
roleApiInstaller RoleApiInstaller,
features featuremgmt.FeatureToggles,
zClient zanzana.Client,
reg prometheus.Registerer,
@@ -162,7 +162,6 @@ func NewAPIService(
store: store,
display: user.NewLegacyDisplayREST(store),
resourcePermissionsStorage: resourcePermissionsStorage,
rolesStorage: roleStorage,
coreRolesStorage: coreRoleStorage,
roleBindingsStorage: noopstorage.ProvideStorageBackend(), // TODO: add a proper storage backend
logger: log.New("iam.apis"),
@@ -172,6 +171,7 @@ func NewAPIService(
zClient: zClient,
zTickets: make(chan bool, MaxConcurrentZanzanaWrites),
reg: reg,
roleApiInstaller: roleApiInstaller,
authorizer: authorizer.AuthorizerFunc(
func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
user, ok := types.AuthInfoFrom(ctx)
@@ -377,23 +377,17 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge
}
if enableZanzanaSync {
b.logger.Info("Enabling hooks for CoreRole to sync to Zanzana")
coreRoleStore.AfterCreate = b.AfterRoleCreate
coreRoleStore.AfterDelete = b.AfterRoleDelete
coreRoleStore.BeginUpdate = b.BeginRoleUpdate
h := NewRoleHooks(b.zClient, b.zTickets, b.logger)
coreRoleStore.AfterCreate = h.AfterRoleCreate
coreRoleStore.AfterDelete = h.AfterRoleDelete
coreRoleStore.BeginUpdate = h.BeginRoleUpdate
}
storage[iamv0.CoreRoleInfo.StoragePath()] = coreRoleStore
roleStore, err := NewLocalStore(iamv0.RoleInfo, apiGroupInfo.Scheme, opts.OptsGetter, b.reg, b.accessClient, b.rolesStorage)
if err != nil {
// Role registration is delegated to the RoleApiInstaller
if err := b.roleApiInstaller.RegisterStorage(apiGroupInfo, &opts, storage); err != nil {
return err
}
if enableZanzanaSync {
b.logger.Info("Enabling hooks for Role to sync to Zanzana")
roleStore.AfterCreate = b.AfterRoleCreate
roleStore.AfterDelete = b.AfterRoleDelete
roleStore.BeginUpdate = b.BeginRoleUpdate
}
storage[iamv0.RoleInfo.StoragePath()] = roleStore
roleBindingStore, err := NewLocalStore(iamv0.RoleBindingInfo, apiGroupInfo.Scheme, opts.OptsGetter, b.reg, b.accessClient, b.roleBindingsStorage)
if err != nil {
@@ -568,6 +562,8 @@ func (b *IdentityAccessManagementAPIBuilder) Validate(ctx context.Context, a adm
return resourcepermission.ValidateCreateAndUpdateInput(ctx, typedObj)
case *iamv0.ExternalGroupMapping:
return externalgroupmapping.ValidateOnCreate(typedObj)
case *iamv0.Role:
return b.roleApiInstaller.ValidateOnCreate(ctx, typedObj)
}
return nil
case admission.Update:
@@ -592,9 +588,19 @@ func (b *IdentityAccessManagementAPIBuilder) Validate(ctx context.Context, a adm
return fmt.Errorf("expected old object to be a TeamBinding, got %T", oldTeamBindingObj)
}
return teambinding.ValidateOnUpdate(ctx, typedObj, oldTeamBindingObj)
case *iamv0.Role:
oldRoleObj, ok := a.GetOldObject().(*iamv0.Role)
if !ok {
return fmt.Errorf("expected old object to be a Role, got %T", oldRoleObj)
}
return b.roleApiInstaller.ValidateOnUpdate(ctx, oldRoleObj, typedObj)
}
return nil
case admission.Delete:
switch oldRoleObj := a.GetOldObject().(type) {
case *iamv0.Role:
return b.roleApiInstaller.ValidateOnDelete(ctx, oldRoleObj)
}
return nil
case admission.Connect:
return nil
+41 -30
View File
@@ -9,12 +9,23 @@ import (
"k8s.io/apiserver/pkg/registry/generic/registry"
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/accesscontrol"
v1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
"github.com/grafana/grafana/pkg/services/authz/zanzana"
"github.com/grafana/grafana/pkg/services/authz/zanzana/common"
)
type RoleHooks struct {
zClient zanzana.Client
zTickets chan bool
logger log.Logger
}
func NewRoleHooks(zClient zanzana.Client, zTickets chan bool, logger log.Logger) *RoleHooks {
return &RoleHooks{zClient: zClient, zTickets: zTickets, logger: logger}
}
// convertRolePermissionsToTuples converts role permissions (action/scope) to v1 TupleKey format
// using the shared zanzana.ConvertRolePermissionsToTuples utility and common.ToAuthzExtTupleKeys
func convertRolePermissionsToTuples(roleUID string, permissions []iamv0.CoreRolespecPermission) ([]*v1.TupleKey, error) {
@@ -44,8 +55,8 @@ func convertRolePermissionsToTuples(roleUID string, permissions []iamv0.CoreRole
// AfterRoleCreate is a post-create hook that writes the role permissions to Zanzana (openFGA)
// It handles both Role and CoreRole types
func (b *IdentityAccessManagementAPIBuilder) AfterRoleCreate(obj runtime.Object, _ *metav1.CreateOptions) {
if b.zClient == nil {
func (h *RoleHooks) AfterRoleCreate(obj runtime.Object, _ *metav1.CreateOptions) {
if h.zClient == nil {
return
}
@@ -76,21 +87,21 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleCreate(obj runtime.Object,
}
wait := time.Now()
b.zTickets <- true
h.zTickets <- true
hooksWaitHistogram.WithLabelValues(rType, "create").Observe(time.Since(wait).Seconds())
go func(role *iamv0.CoreRole, roleType string) {
start := time.Now()
status := "success"
defer func() {
<-b.zTickets
<-h.zTickets
hooksDurationHistogram.WithLabelValues(rType, "create", status).Observe(time.Since(start).Seconds())
hooksOperationCounter.WithLabelValues(rType, "create", status).Inc()
}()
tuples, err := convertRolePermissionsToTuples(role.Name, role.Spec.Permissions)
if err != nil {
b.logger.Error("failed to convert role permissions to tuples",
h.logger.Error("failed to convert role permissions to tuples",
"namespace", role.Namespace,
"roleUID", role.Name,
"roleType", roleType,
@@ -103,7 +114,7 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleCreate(obj runtime.Object,
// Avoid writing if there are no valid tuples
if len(tuples) == 0 {
b.logger.Debug("no valid tuples to write for role",
h.logger.Debug("no valid tuples to write for role",
"namespace", role.Namespace,
"roleUID", role.Name,
"roleType", roleType,
@@ -113,7 +124,7 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleCreate(obj runtime.Object,
return
}
b.logger.Debug("writing role permissions to zanzana",
h.logger.Debug("writing role permissions to zanzana",
"namespace", role.Namespace,
"roleUID", role.Name,
"roleType", roleType,
@@ -124,14 +135,14 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleCreate(obj runtime.Object,
ctx, cancel := context.WithTimeout(context.Background(), defaultWriteTimeout)
defer cancel()
err = b.zClient.Write(ctx, &v1.WriteRequest{
err = h.zClient.Write(ctx, &v1.WriteRequest{
Namespace: role.Namespace,
Writes: &v1.WriteRequestWrites{
TupleKeys: tuples,
},
})
if err != nil {
b.logger.Error("failed to write role permissions to zanzana",
h.logger.Error("failed to write role permissions to zanzana",
"err", err,
"namespace", role.Namespace,
"roleUID", role.Name,
@@ -149,8 +160,8 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleCreate(obj runtime.Object,
// AfterRoleDelete is a post-delete hook that removes the role permissions from Zanzana (openFGA)
// It handles both Role and CoreRole types
func (b *IdentityAccessManagementAPIBuilder) AfterRoleDelete(obj runtime.Object, _ *metav1.DeleteOptions) {
if b.zClient == nil {
func (h *RoleHooks) AfterRoleDelete(obj runtime.Object, _ *metav1.DeleteOptions) {
if h.zClient == nil {
return
}
@@ -182,15 +193,15 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleDelete(obj runtime.Object,
}
wait := time.Now()
b.zTickets <- true
h.zTickets <- true
hooksWaitHistogram.WithLabelValues("role", "delete").Observe(time.Since(wait).Seconds()) // Record wait time
go func(role *iamv0.CoreRole, roleType string) {
defer func() {
<-b.zTickets
<-h.zTickets
}()
b.logger.Debug("deleting role permissions from zanzana",
h.logger.Debug("deleting role permissions from zanzana",
"namespace", role.Namespace,
"roleUID", role.Name,
"roleType", roleType,
@@ -199,7 +210,7 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleDelete(obj runtime.Object,
tuples, err := convertRolePermissionsToTuples(role.Name, role.Spec.Permissions)
if err != nil {
b.logger.Error("failed to convert role permissions to tuples for deletion",
h.logger.Error("failed to convert role permissions to tuples for deletion",
"namespace", role.Namespace,
"roleUID", role.Name,
"roleType", roleType,
@@ -211,7 +222,7 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleDelete(obj runtime.Object,
// Avoid deleting if there are no valid tuples
if len(tuples) == 0 {
b.logger.Debug("no valid tuples to delete for role",
h.logger.Debug("no valid tuples to delete for role",
"namespace", role.Namespace,
"roleUID", role.Name,
"roleType", roleType,
@@ -223,7 +234,7 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleDelete(obj runtime.Object,
// Convert tuples to TupleKeyWithoutCondition for deletion
deleteTuples := toTupleKeysWithoutCondition(tuples)
b.logger.Debug("deleting role permissions from zanzana",
h.logger.Debug("deleting role permissions from zanzana",
"namespace", role.Namespace,
"roleUID", role.Name,
"roleType", roleType,
@@ -234,14 +245,14 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleDelete(obj runtime.Object,
ctx, cancel := context.WithTimeout(context.Background(), defaultWriteTimeout)
defer cancel()
err = b.zClient.Write(ctx, &v1.WriteRequest{
err = h.zClient.Write(ctx, &v1.WriteRequest{
Namespace: role.Namespace,
Deletes: &v1.WriteRequestDeletes{
TupleKeys: deleteTuples,
},
})
if err != nil {
b.logger.Error("failed to delete role permissions from zanzana",
h.logger.Error("failed to delete role permissions from zanzana",
"err", err,
"namespace", role.Namespace,
"roleUID", role.Name,
@@ -255,8 +266,8 @@ func (b *IdentityAccessManagementAPIBuilder) AfterRoleDelete(obj runtime.Object,
// beginRoleUpdate is a pre-update hook that prepares zanzana updates
// It converts old and new permissions to tuples and performs the zanzana write after K8s update succeeds
// It handles both Role and CoreRole types
func (b *IdentityAccessManagementAPIBuilder) BeginRoleUpdate(ctx context.Context, obj, oldObj runtime.Object, options *metav1.UpdateOptions) (registry.FinishFunc, error) {
if b.zClient == nil {
func (h *RoleHooks) BeginRoleUpdate(ctx context.Context, obj, oldObj runtime.Object, options *metav1.UpdateOptions) (registry.FinishFunc, error) {
if h.zClient == nil {
return nil, nil
}
var oldRole, newRole *iamv0.CoreRole
@@ -316,12 +327,12 @@ func (b *IdentityAccessManagementAPIBuilder) BeginRoleUpdate(ctx context.Context
// Grab a ticket to write to Zanzana
wait := time.Now()
b.zTickets <- true
h.zTickets <- true
hooksWaitHistogram.WithLabelValues(roleType, "update").Observe(time.Since(wait).Seconds()) // Record wait time
go func(old *iamv0.CoreRole, new *iamv0.CoreRole) {
defer func() {
<-b.zTickets
<-h.zTickets
}()
roleUID, namespace := old.Name, old.Namespace
oldPermissions, newPermissions := old.Spec.Permissions, new.Spec.Permissions
@@ -332,7 +343,7 @@ func (b *IdentityAccessManagementAPIBuilder) BeginRoleUpdate(ctx context.Context
var err error
oldTuples, err = convertRolePermissionsToTuples(roleUID, oldPermissions)
if err != nil {
b.logger.Error("failed to convert old role permissions to tuples",
h.logger.Error("failed to convert old role permissions to tuples",
"namespace", namespace,
"roleUID", roleUID,
"roleType", roleType,
@@ -344,7 +355,7 @@ func (b *IdentityAccessManagementAPIBuilder) BeginRoleUpdate(ctx context.Context
// Convert new permissions to tuples for writing
newTuples, err := convertRolePermissionsToTuples(roleUID, newPermissions)
if err != nil {
b.logger.Error("failed to convert new role permissions to tuples",
h.logger.Error("failed to convert new role permissions to tuples",
"namespace", namespace,
"roleUID", roleUID,
"roleType", roleType,
@@ -353,7 +364,7 @@ func (b *IdentityAccessManagementAPIBuilder) BeginRoleUpdate(ctx context.Context
return
}
b.logger.Debug("updating role permissions in zanzana",
h.logger.Debug("updating role permissions in zanzana",
"namespace", namespace,
"roleUID", roleUID,
"roleType", roleType,
@@ -375,7 +386,7 @@ func (b *IdentityAccessManagementAPIBuilder) BeginRoleUpdate(ctx context.Context
req.Deletes = &v1.WriteRequestDeletes{
TupleKeys: deleteTuples,
}
b.logger.Debug("deleting existing role permissions from zanzana",
h.logger.Debug("deleting existing role permissions from zanzana",
"namespace", namespace,
"roleUID", roleUID,
"roleType", roleType,
@@ -388,7 +399,7 @@ func (b *IdentityAccessManagementAPIBuilder) BeginRoleUpdate(ctx context.Context
req.Writes = &v1.WriteRequestWrites{
TupleKeys: newTuples,
}
b.logger.Debug("writing new role permissions to zanzana",
h.logger.Debug("writing new role permissions to zanzana",
"namespace", namespace,
"roleUID", roleUID,
"roleType", roleType,
@@ -398,9 +409,9 @@ func (b *IdentityAccessManagementAPIBuilder) BeginRoleUpdate(ctx context.Context
// Only make the request if there are deletes or writes
if req.Deletes != nil || req.Writes != nil {
err = b.zClient.Write(ctx, req)
err = h.zClient.Write(ctx, req)
if err != nil {
b.logger.Error("failed to update role permissions in zanzana",
h.logger.Error("failed to update role permissions in zanzana",
"err", err,
"namespace", namespace,
"roleUID", roleUID,
+46 -46
View File
@@ -53,7 +53,7 @@ func requireDeleteTuplesMatch(t *testing.T, actual []*v1.TupleKeyWithoutConditio
func TestAfterCoreRoleCreate(t *testing.T) {
var wg sync.WaitGroup
b := &IdentityAccessManagementAPIBuilder{
h := &RoleHooks{
logger: log.NewNopLogger(),
zTickets: make(chan bool, 1),
}
@@ -90,8 +90,8 @@ func TestAfterCoreRoleCreate(t *testing.T) {
return nil
}
b.zClient = &FakeZanzanaClient{writeCallback: testCoreRoleEntries}
b.AfterRoleCreate(&coreRole, nil)
h.zClient = &FakeZanzanaClient{writeCallback: testCoreRoleEntries}
h.AfterRoleCreate(&coreRole, nil)
wg.Wait()
})
@@ -129,8 +129,8 @@ func TestAfterCoreRoleCreate(t *testing.T) {
return nil
}
b.zClient = &FakeZanzanaClient{writeCallback: testDashboardRoleEntries}
b.AfterRoleCreate(&coreRole, nil)
h.zClient = &FakeZanzanaClient{writeCallback: testDashboardRoleEntries}
h.AfterRoleCreate(&coreRole, nil)
wg.Wait()
})
@@ -163,8 +163,8 @@ func TestAfterCoreRoleCreate(t *testing.T) {
return nil
}
b.zClient = &FakeZanzanaClient{writeCallback: testWildcardEntries}
b.AfterRoleCreate(&coreRole, nil)
h.zClient = &FakeZanzanaClient{writeCallback: testWildcardEntries}
h.AfterRoleCreate(&coreRole, nil)
wg.Wait()
})
@@ -198,15 +198,15 @@ func TestAfterCoreRoleCreate(t *testing.T) {
return nil
}
b.zClient = &FakeZanzanaClient{writeCallback: testMixedEntries}
b.AfterRoleCreate(&coreRole, nil)
h.zClient = &FakeZanzanaClient{writeCallback: testMixedEntries}
h.AfterRoleCreate(&coreRole, nil)
wg.Wait()
})
}
func TestAfterRoleCreate(t *testing.T) {
var wg sync.WaitGroup
b := &IdentityAccessManagementAPIBuilder{
h := &RoleHooks{
logger: log.NewNopLogger(),
zTickets: make(chan bool, 1),
}
@@ -243,8 +243,8 @@ func TestAfterRoleCreate(t *testing.T) {
return nil
}
b.zClient = &FakeZanzanaClient{writeCallback: testRoleEntries}
b.AfterRoleCreate(&role, nil)
h.zClient = &FakeZanzanaClient{writeCallback: testRoleEntries}
h.AfterRoleCreate(&role, nil)
wg.Wait()
})
@@ -281,8 +281,8 @@ func TestAfterRoleCreate(t *testing.T) {
return nil
}
b.zClient = &FakeZanzanaClient{writeCallback: testDashRoleEntries}
b.AfterRoleCreate(&role, nil)
h.zClient = &FakeZanzanaClient{writeCallback: testDashRoleEntries}
h.AfterRoleCreate(&role, nil)
wg.Wait()
})
@@ -317,15 +317,15 @@ func TestAfterRoleCreate(t *testing.T) {
return nil
}
b.zClient = &FakeZanzanaClient{writeCallback: testMergedEntries}
b.AfterRoleCreate(&role, nil)
h.zClient = &FakeZanzanaClient{writeCallback: testMergedEntries}
h.AfterRoleCreate(&role, nil)
wg.Wait()
})
}
func TestBeginCoreRoleUpdate(t *testing.T) {
var wg sync.WaitGroup
b := &IdentityAccessManagementAPIBuilder{
h := &RoleHooks{
logger: log.NewNopLogger(),
zTickets: make(chan bool, 1),
}
@@ -387,10 +387,10 @@ func TestBeginCoreRoleUpdate(t *testing.T) {
return nil
}
b.zClient = &FakeZanzanaClient{writeCallback: testUpdate}
h.zClient = &FakeZanzanaClient{writeCallback: testUpdate}
// Call BeginUpdate which does all the work
finishFunc, err := b.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil)
finishFunc, err := h.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil)
require.NoError(t, err)
require.NotNil(t, finishFunc)
@@ -445,10 +445,10 @@ func TestBeginCoreRoleUpdate(t *testing.T) {
return nil
}
b.zClient = &FakeZanzanaClient{writeCallback: testExpand}
h.zClient = &FakeZanzanaClient{writeCallback: testExpand}
// Call BeginUpdate which does all the work
finishFunc, err := b.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil)
finishFunc, err := h.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil)
require.NoError(t, err)
require.NotNil(t, finishFunc)
@@ -499,10 +499,10 @@ func TestBeginCoreRoleUpdate(t *testing.T) {
return nil
}
b.zClient = &FakeZanzanaClient{writeCallback: testClear}
h.zClient = &FakeZanzanaClient{writeCallback: testClear}
// Call BeginUpdate which does all the work
finishFunc, err := b.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil)
finishFunc, err := h.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil)
require.NoError(t, err)
require.NotNil(t, finishFunc)
@@ -514,7 +514,7 @@ func TestBeginCoreRoleUpdate(t *testing.T) {
func TestBeginRoleUpdate(t *testing.T) {
var wg sync.WaitGroup
b := &IdentityAccessManagementAPIBuilder{
h := &RoleHooks{
logger: log.NewNopLogger(),
zTickets: make(chan bool, 1),
}
@@ -577,10 +577,10 @@ func TestBeginRoleUpdate(t *testing.T) {
return nil
}
b.zClient = &FakeZanzanaClient{writeCallback: testUpdate}
h.zClient = &FakeZanzanaClient{writeCallback: testUpdate}
// Call BeginUpdate which does all the work
finishFunc, err := b.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil)
finishFunc, err := h.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil)
require.NoError(t, err)
require.NotNil(t, finishFunc)
@@ -640,10 +640,10 @@ func TestBeginRoleUpdate(t *testing.T) {
return nil
}
b.zClient = &FakeZanzanaClient{writeCallback: testSwap}
h.zClient = &FakeZanzanaClient{writeCallback: testSwap}
// Call BeginUpdate which does all the work
finishFunc, err := b.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil)
finishFunc, err := h.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil)
require.NoError(t, err)
require.NotNil(t, finishFunc)
@@ -695,10 +695,10 @@ func TestBeginRoleUpdate(t *testing.T) {
return nil
}
b.zClient = &FakeZanzanaClient{writeCallback: testAddToEmpty}
h.zClient = &FakeZanzanaClient{writeCallback: testAddToEmpty}
// Call BeginUpdate which does all the work
finishFunc, err := b.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil)
finishFunc, err := h.BeginRoleUpdate(context.Background(), &newRole, &oldRole, nil)
require.NoError(t, err)
require.NotNil(t, finishFunc)
@@ -710,7 +710,7 @@ func TestBeginRoleUpdate(t *testing.T) {
func TestAfterCoreRoleDelete(t *testing.T) {
var wg sync.WaitGroup
b := &IdentityAccessManagementAPIBuilder{
h := &RoleHooks{
logger: log.NewNopLogger(),
zTickets: make(chan bool, 1),
}
@@ -747,8 +747,8 @@ func TestAfterCoreRoleDelete(t *testing.T) {
return nil
}
b.zClient = &FakeZanzanaClient{writeCallback: testCoreRoleDeletes}
b.AfterRoleDelete(&coreRole, nil)
h.zClient = &FakeZanzanaClient{writeCallback: testCoreRoleDeletes}
h.AfterRoleDelete(&coreRole, nil)
wg.Wait()
})
@@ -785,8 +785,8 @@ func TestAfterCoreRoleDelete(t *testing.T) {
return nil
}
b.zClient = &FakeZanzanaClient{writeCallback: testDashboardRoleDeletes}
b.AfterRoleDelete(&coreRole, nil)
h.zClient = &FakeZanzanaClient{writeCallback: testDashboardRoleDeletes}
h.AfterRoleDelete(&coreRole, nil)
wg.Wait()
})
@@ -818,8 +818,8 @@ func TestAfterCoreRoleDelete(t *testing.T) {
return nil
}
b.zClient = &FakeZanzanaClient{writeCallback: testWildcardDeletes}
b.AfterRoleDelete(&coreRole, nil)
h.zClient = &FakeZanzanaClient{writeCallback: testWildcardDeletes}
h.AfterRoleDelete(&coreRole, nil)
wg.Wait()
})
@@ -853,15 +853,15 @@ func TestAfterCoreRoleDelete(t *testing.T) {
return nil
}
b.zClient = &FakeZanzanaClient{writeCallback: testMixedDeletes}
b.AfterRoleDelete(&coreRole, nil)
h.zClient = &FakeZanzanaClient{writeCallback: testMixedDeletes}
h.AfterRoleDelete(&coreRole, nil)
wg.Wait()
})
}
func TestAfterRoleDelete(t *testing.T) {
var wg sync.WaitGroup
b := &IdentityAccessManagementAPIBuilder{
h := &RoleHooks{
logger: log.NewNopLogger(),
zTickets: make(chan bool, 1),
}
@@ -898,8 +898,8 @@ func TestAfterRoleDelete(t *testing.T) {
return nil
}
b.zClient = &FakeZanzanaClient{writeCallback: testRoleDeletes}
b.AfterRoleDelete(&role, nil)
h.zClient = &FakeZanzanaClient{writeCallback: testRoleDeletes}
h.AfterRoleDelete(&role, nil)
wg.Wait()
})
@@ -936,8 +936,8 @@ func TestAfterRoleDelete(t *testing.T) {
return nil
}
b.zClient = &FakeZanzanaClient{writeCallback: testDashRoleDeletes}
b.AfterRoleDelete(&role, nil)
h.zClient = &FakeZanzanaClient{writeCallback: testDashRoleDeletes}
h.AfterRoleDelete(&role, nil)
wg.Wait()
})
@@ -982,8 +982,8 @@ func TestAfterRoleDelete(t *testing.T) {
return nil
}
b.zClient = &FakeZanzanaClient{writeCallback: testMultiDeletes}
b.AfterRoleDelete(&role, nil)
h.zClient = &FakeZanzanaClient{writeCallback: testMultiDeletes}
h.AfterRoleDelete(&role, nil)
wg.Wait()
})
}
+1 -1
View File
@@ -28,7 +28,7 @@ import (
var WireSetExts = wire.NewSet(
noopstorage.ProvideStorageBackend,
wire.Bind(new(iam.CoreRoleStorageBackend), new(*noopstorage.StorageBackendImpl)),
wire.Bind(new(iam.RoleStorageBackend), new(*noopstorage.StorageBackendImpl)),
iam.ProvideNoopRoleApiInstaller,
wire.Bind(new(iam.RoleBindingStorageBackend), new(*noopstorage.StorageBackendImpl)),
wire.Bind(new(iam.ExternalGroupMappingStorageBackend), new(*noopstorage.StorageBackendImpl)),
+4 -2
View File
@@ -882,8 +882,9 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api
}
folderAPIBuilder := folders.RegisterAPIService(cfg, featureToggles, apiserverService, folderimplService, folderPermissionsService, accessControl, acimplService, accessClient, registerer, resourceClient, zanzanaClient)
storageBackendImpl := noopstorage.ProvideStorageBackend()
roleApiInstaller := iam.ProvideNoopRoleApiInstaller()
noopTeamGroupsREST := externalgroupmapping.ProvideNoopTeamGroupsREST()
identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, storageBackendImpl, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, dualwriteService, resourceClient, orgService, userService, teamService, eventualRestConfigProvider)
identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, roleApiInstaller, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, dualwriteService, resourceClient, orgService, userService, teamService, eventualRestConfigProvider)
if err != nil {
return nil, err
}
@@ -1543,8 +1544,9 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
}
folderAPIBuilder := folders.RegisterAPIService(cfg, featureToggles, apiserverService, folderimplService, folderPermissionsService, accessControl, acimplService, accessClient, registerer, resourceClient, zanzanaClient)
storageBackendImpl := noopstorage.ProvideStorageBackend()
roleApiInstaller := iam.ProvideNoopRoleApiInstaller()
noopTeamGroupsREST := externalgroupmapping.ProvideNoopTeamGroupsREST()
identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, storageBackendImpl, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, dualwriteService, resourceClient, orgService, userService, teamService, eventualRestConfigProvider)
identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(cfg, featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, roleApiInstaller, tracingService, storageBackendImpl, storageBackendImpl, noopTeamGroupsREST, dualwriteService, resourceClient, orgService, userService, teamService, eventualRestConfigProvider)
if err != nil {
return nil, err
}