grafana-iam: Use the UniStore as the default store (#113614)

* `grafana-iam`: Use the UniStore as the default store

* Refactor all instantiations

* Remove enableDualWriter

* Nit. dw is clear enough

* Use the correct access control client
This commit is contained in:
Gabriel MABILLE
2025-11-20 15:51:50 +01:00
committed by GitHub
parent 65fd15bbf9
commit b5a50e7772
2 changed files with 112 additions and 115 deletions
+11 -4
View File
@@ -8,6 +8,10 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
"github.com/grafana/grafana/pkg/registry/apis/iam/serviceaccount"
"github.com/grafana/grafana/pkg/registry/apis/iam/sso"
"github.com/grafana/grafana/pkg/registry/apis/iam/team"
"github.com/grafana/grafana/pkg/registry/apis/iam/teambinding"
"github.com/grafana/grafana/pkg/registry/apis/iam/user"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/services/authz/zanzana"
@@ -42,7 +46,13 @@ type ExternalGroupMappingStorageBackend interface{ resource.StorageBackend }
// This is used just so wire has something unique to return
type IdentityAccessManagementAPIBuilder struct {
// Stores
store legacy.LegacyIdentityStore
store legacy.LegacyIdentityStore
userLegacyStore *user.LegacyStore
saLegacyStore *serviceaccount.LegacyStore
legacyTeamStore *team.LegacyStore
teamBindingLegacyStore *teambinding.LegacyBindingStore
ssoLegacyStore *sso.LegacyStore
coreRolesStorage CoreRoleStorageBackend
rolesStorage RoleStorageBackend
resourcePermissionsStorage resource.StorageBackend
@@ -78,7 +88,4 @@ type IdentityAccessManagementAPIBuilder struct {
// Toggle for enabling authz management apis
features featuremgmt.FeatureToggles
// Toggle for enabling dual writer
enableDualWriter bool
}
+101 -111
View File
@@ -31,7 +31,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/registry/apis/iam/externalgroupmapping"
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
"github.com/grafana/grafana/pkg/registry/apis/iam/noopstorage"
"github.com/grafana/grafana/pkg/registry/apis/iam/resourcepermission"
"github.com/grafana/grafana/pkg/registry/apis/iam/serviceaccount"
"github.com/grafana/grafana/pkg/registry/apis/iam/sso"
@@ -76,8 +75,16 @@ func RegisterAPIService(
authorizer := newIAMAuthorizer(accessClient, legacyAccessClient)
registerMetrics(reg)
//nolint:staticcheck // not yet migrated to OpenFeature
enableAuthnMutation := features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthnMutation)
builder := &IdentityAccessManagementAPIBuilder{
store: store,
userLegacyStore: user.NewLegacyStore(store, accessClient, enableAuthnMutation),
saLegacyStore: serviceaccount.NewLegacyStore(store, accessClient, enableAuthnMutation),
legacyTeamStore: team.NewLegacyStore(store, legacyAccessClient, enableAuthnMutation),
teamBindingLegacyStore: teambinding.NewLegacyBindingStore(store, enableAuthnMutation),
ssoLegacyStore: sso.NewLegacyStore(ssoService),
coreRolesStorage: coreRolesStorage,
rolesStorage: rolesStorage,
resourcePermissionsStorage: resourcepermission.ProvideStorageBackend(dbProvider),
@@ -93,7 +100,6 @@ func RegisterAPIService(
reg: reg,
logger: log.New("iam.apis"),
features: features,
enableDualWriter: true,
dual: dual,
unified: unified,
userSearchClient: resource.NewSearchClient(dualwrite.NewSearchAdapter(dual), iamv0.UserResourceInfo.GroupResource(), unified, user.NewUserLegacySearchClient(userService), features),
@@ -113,18 +119,16 @@ func NewAPIService(
store := legacy.NewLegacySQLStores(dbProvider)
resourcePermissionsStorage := resourcepermission.ProvideStorageBackend(dbProvider)
resourceAuthorizer := gfauthorizer.NewResourceAuthorizer(accessClient)
noopStorage := noopstorage.ProvideStorageBackend()
registerMetrics(reg)
return &IdentityAccessManagementAPIBuilder{
store: store,
display: user.NewLegacyDisplayREST(store),
resourcePermissionsStorage: resourcePermissionsStorage,
externalGroupMappingStorage: noopStorage,
logger: log.New("iam.apis"),
features: features,
zClient: zClient,
zTickets: make(chan bool, MaxConcurrentZanzanaWrites),
reg: reg,
store: store,
display: user.NewLegacyDisplayREST(store),
resourcePermissionsStorage: resourcePermissionsStorage,
logger: log.New("iam.apis"),
features: features,
zClient: zClient,
zTickets: make(chan bool, MaxConcurrentZanzanaWrites),
reg: reg,
authorizer: authorizer.AuthorizerFunc(
func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
// For now only authorize resourcepermissions resource
@@ -183,8 +187,7 @@ func (b *IdentityAccessManagementAPIBuilder) AllowedV0Alpha1Resources() []string
func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupInfo, opts builder.APIGroupOptions) error {
storage := map[string]rest.Storage{}
//nolint:staticcheck // not yet migrated to OpenFeature
enableAuthnMutation := b.features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthnMutation)
//nolint:staticcheck // not yet migrated to OpenFeature
enableZanzanaSync := b.features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthzZanzanaSync)
@@ -197,69 +200,63 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge
})
teamResource := iamv0.TeamResourceInfo
teamLegacyStore := team.NewLegacyStore(b.store, b.legacyAccessClient, enableAuthnMutation)
storage[teamResource.StoragePath()] = teamLegacyStore
storage[teamResource.StoragePath("members")] = team.NewLegacyTeamMemberREST(b.store)
teamUniStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, teamResource, opts.OptsGetter)
if err != nil {
return err
}
storage[teamResource.StoragePath()] = teamUniStore
if b.enableDualWriter {
teamStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, teamResource, opts.OptsGetter)
if b.legacyTeamStore != nil {
dw, err := opts.DualWriteBuilder(teamResource.GroupResource(), b.legacyTeamStore, teamUniStore)
if err != nil {
return err
}
teamDW, err := opts.DualWriteBuilder(teamResource.GroupResource(), teamLegacyStore, teamStore)
if err != nil {
return err
}
storage[teamResource.StoragePath()] = teamDW
storage[teamResource.StoragePath()] = dw
}
storage[teamResource.StoragePath("members")] = team.NewLegacyTeamMemberREST(b.store)
teamBindingResource := iamv0.TeamBindingResourceInfo
teamBindingLegacyStore := teambinding.NewLegacyBindingStore(b.store, enableAuthnMutation)
storage[teamBindingResource.StoragePath()] = teamBindingLegacyStore
teamBindingUniStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, teamBindingResource, opts.OptsGetter)
if err != nil {
return err
}
storage[teamBindingResource.StoragePath()] = teamBindingUniStore
if b.enableDualWriter {
teamBindingStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, teamBindingResource, opts.OptsGetter)
// Only teamBindingStore exposes the AfterCreate, AfterDelete, and BeginUpdate hooks
if enableZanzanaSync {
b.logger.Info("Enabling hooks for TeamBinding to sync to Zanzana")
teamBindingUniStore.AfterCreate = b.AfterTeamBindingCreate
teamBindingUniStore.AfterDelete = b.AfterTeamBindingDelete
teamBindingUniStore.BeginUpdate = b.BeginTeamBindingUpdate
}
if b.teamBindingLegacyStore != nil {
dw, err := opts.DualWriteBuilder(teamBindingResource.GroupResource(), b.teamBindingLegacyStore, teamBindingUniStore)
if err != nil {
return err
}
teamBindingDW, err := opts.DualWriteBuilder(teamBindingResource.GroupResource(), teamBindingLegacyStore, teamBindingStore)
if err != nil {
return err
}
// Only teamBindingStore exposes the AfterCreate, AfterDelete, and BeginUpdate hooks
if enableZanzanaSync {
b.logger.Info("Enabling hooks for TeamBinding to sync to Zanzana")
teamBindingStore.AfterCreate = b.AfterTeamBindingCreate
teamBindingStore.AfterDelete = b.AfterTeamBindingDelete
teamBindingStore.BeginUpdate = b.BeginTeamBindingUpdate
}
storage[teamBindingResource.StoragePath()] = teamBindingDW
storage[teamBindingResource.StoragePath()] = dw
}
// User store registration
userResource := iamv0.UserResourceInfo
legacyStore := user.NewLegacyStore(b.store, b.accessClient, enableAuthnMutation)
storage[userResource.StoragePath()] = legacyStore
userUniStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, userResource, opts.OptsGetter)
if err != nil {
return err
}
storage[userResource.StoragePath()] = userUniStore
if b.enableDualWriter {
store, err := grafanaregistry.NewRegistryStore(opts.Scheme, userResource, opts.OptsGetter)
if err != nil {
return err
}
if enableZanzanaSync {
b.logger.Info("Enabling hooks for User to sync basic role assignments to Zanzana")
userUniStore.AfterCreate = b.AfterUserCreate
userUniStore.BeginUpdate = b.BeginUserUpdate
userUniStore.AfterDelete = b.AfterUserDelete
}
if enableZanzanaSync {
b.logger.Info("Enabling hooks for User to sync basic role assignments to Zanzana")
store.AfterCreate = b.AfterUserCreate
store.BeginUpdate = b.BeginUserUpdate
store.AfterDelete = b.AfterUserDelete
}
dw, err := opts.DualWriteBuilder(userResource.GroupResource(), legacyStore, store)
if b.userLegacyStore != nil {
dw, err := opts.DualWriteBuilder(userResource.GroupResource(), b.userLegacyStore, userUniStore)
if err != nil {
return err
}
@@ -270,50 +267,46 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge
storage[userResource.StoragePath("teams")] = user.NewLegacyTeamMemberREST(b.store)
// Service Accounts store registration
serviceAccountResource := iamv0.ServiceAccountResourceInfo
saLegacyStore := serviceaccount.NewLegacyStore(b.store, b.accessClient, enableAuthnMutation)
storage[serviceAccountResource.StoragePath()] = saLegacyStore
if b.enableDualWriter {
store, err := grafanaregistry.NewRegistryStore(opts.Scheme, serviceAccountResource, opts.OptsGetter)
if err != nil {
return err
}
dw, err := opts.DualWriteBuilder(serviceAccountResource.GroupResource(), saLegacyStore, store)
if err != nil {
return err
}
storage[serviceAccountResource.StoragePath()] = dw
}
storage[serviceAccountResource.StoragePath("tokens")] = serviceaccount.NewLegacyTokenREST(b.store)
if b.sso != nil {
ssoResource := legacyiamv0.SSOSettingResourceInfo
storage[ssoResource.StoragePath()] = sso.NewLegacyStore(b.sso)
}
externalGroupMappingResource := iamv0.ExternalGroupMappingResourceInfo
externalGroupMappingLegacyStore, err := NewLocalStore(externalGroupMappingResource, apiGroupInfo.Scheme, opts.OptsGetter, b.reg, b.accessClient, b.externalGroupMappingStorage)
saResource := iamv0.ServiceAccountResourceInfo
saUniStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, saResource, opts.OptsGetter)
if err != nil {
return err
}
storage[externalGroupMappingResource.StoragePath()] = externalGroupMappingLegacyStore
storage[saResource.StoragePath()] = saUniStore
if b.enableDualWriter {
externalGroupMappingStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, externalGroupMappingResource, opts.OptsGetter)
if b.saLegacyStore != nil {
dw, err := opts.DualWriteBuilder(saResource.GroupResource(), b.saLegacyStore, saUniStore)
if err != nil {
return err
}
storage[saResource.StoragePath()] = dw
}
storage[saResource.StoragePath("tokens")] = serviceaccount.NewLegacyTokenREST(b.store)
if b.ssoLegacyStore != nil {
ssoResource := legacyiamv0.SSOSettingResourceInfo
storage[ssoResource.StoragePath()] = b.ssoLegacyStore
}
extGroupMappingResource := iamv0.ExternalGroupMappingResourceInfo
extGroupMappingUniStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, extGroupMappingResource, opts.OptsGetter)
if err != nil {
return err
}
storage[extGroupMappingResource.StoragePath()] = extGroupMappingUniStore
if b.externalGroupMappingStorage != nil {
extGroupMappingLegacyStore, err := NewLocalStore(extGroupMappingResource, apiGroupInfo.Scheme, opts.OptsGetter, b.reg, b.accessClient, b.externalGroupMappingStorage)
if err != nil {
return err
}
externalGroupMappingDW, err := opts.DualWriteBuilder(externalGroupMappingResource.GroupResource(), externalGroupMappingLegacyStore, externalGroupMappingStore)
dw, err := opts.DualWriteBuilder(extGroupMappingResource.GroupResource(), extGroupMappingLegacyStore, extGroupMappingUniStore)
if err != nil {
return err
}
storage[externalGroupMappingResource.StoragePath()] = externalGroupMappingDW
storage[extGroupMappingResource.StoragePath()] = dw
}
//nolint:staticcheck // not yet migrated to OpenFeature
@@ -351,7 +344,7 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge
}
//nolint:staticcheck // not yet migrated to OpenFeature
if b.features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthzResourcePermissionApis) {
if err := b.UpdateResourcePermissionsAPIGroup(apiGroupInfo, opts, storage, b.enableDualWriter, enableZanzanaSync); err != nil {
if err := b.UpdateResourcePermissionsAPIGroup(apiGroupInfo, opts, storage, enableZanzanaSync); err != nil {
return err
}
}
@@ -364,11 +357,19 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateResourcePermissionsAPIGroup(
apiGroupInfo *genericapiserver.APIGroupInfo,
opts builder.APIGroupOptions,
storage map[string]rest.Storage,
enableDualWriter bool,
enableZanzanaSync bool,
) error {
var store rest.Storage
// Create the legacy store first
uniStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, iamv0.ResourcePermissionInfo, opts.OptsGetter)
if err != nil {
return err
}
storage[iamv0.ResourcePermissionInfo.StoragePath()] = uniStore
if b.resourcePermissionsStorage == nil {
// No legacy storage configured, nothing more to do
return nil
}
legacyStore, err := NewLocalStore(iamv0.ResourcePermissionInfo, apiGroupInfo.Scheme, opts.OptsGetter, b.reg, b.accessClient, b.resourcePermissionsStorage)
if err != nil {
return err
@@ -384,23 +385,12 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateResourcePermissionsAPIGroup(
legacyStore.AfterDelete = b.AfterResourcePermissionDelete
}
// Set the default store to the legacy store
store = legacyStore
if enableDualWriter {
// Create the dual write store (UniStore + LegacyStore)
uniStore, err := grafanaregistry.NewRegistryStore(apiGroupInfo.Scheme, iamv0.ResourcePermissionInfo, opts.OptsGetter)
if err != nil {
return err
}
store, err = opts.DualWriteBuilder(iamv0.ResourcePermissionInfo.GroupResource(), legacyStore, uniStore)
if err != nil {
return err
}
dw, err := opts.DualWriteBuilder(iamv0.ResourcePermissionInfo.GroupResource(), legacyStore, uniStore)
if err != nil {
return err
}
storage[iamv0.ResourcePermissionInfo.StoragePath()] = store
storage[iamv0.ResourcePermissionInfo.StoragePath()] = dw
return nil
}