IAM: Add ExternalGroupMapping kind for TeamSync (#113052)

* wip

* wip

* Add authorizer -> VERIFY it's working correctly

* Update openapi definitions

* Authorizer wip

* regen apis

* Increase timeout of pg int tests to 20m

* Revert "Increase timeout of pg int tests to 20m"

This reverts commit 8c20568217.

* Fix NewTestStore when Truncate is enabled
This commit is contained in:
Misi
2025-11-05 18:02:34 +01:00
committed by GitHub
parent daa28773d6
commit 06373ae47b
22 changed files with 2179 additions and 48 deletions
+1
View File
@@ -35,6 +35,7 @@ func newIAMAuthorizer(accessClient authlib.AccessClient, legacyAccessClient auth
resourceAuthorizer[iamv0.RoleBindingInfo.GetName()] = authorizer
resourceAuthorizer[iamv0.ServiceAccountResourceInfo.GetName()] = authorizer
resourceAuthorizer[iamv0.UserResourceInfo.GetName()] = authorizer
resourceAuthorizer[iamv0.ExternalGroupMappingResourceInfo.GetName()] = authorizer
resourceAuthorizer[iamv0.TeamResourceInfo.GetName()] = authorizer
return &iamAuthorizer{resourceAuthorizer: resourceAuthorizer}
+10 -5
View File
@@ -35,14 +35,19 @@ type RoleStorageBackend interface{ resource.StorageBackend }
// Used by wire to identify the storage backend for role bindings.
type RoleBindingStorageBackend interface{ resource.StorageBackend }
// ExternalGroupMappingStorageBackend uses the resource.StorageBackend interface to provide storage for external group mappings.
// Used by wire to identify the storage backend for external group mappings.
type ExternalGroupMappingStorageBackend interface{ resource.StorageBackend }
// This is used just so wire has something unique to return
type IdentityAccessManagementAPIBuilder struct {
// Stores
store legacy.LegacyIdentityStore
coreRolesStorage CoreRoleStorageBackend
rolesStorage RoleStorageBackend
resourcePermissionsStorage resource.StorageBackend
roleBindingsStorage RoleBindingStorageBackend
store legacy.LegacyIdentityStore
coreRolesStorage CoreRoleStorageBackend
rolesStorage RoleStorageBackend
resourcePermissionsStorage resource.StorageBackend
roleBindingsStorage RoleBindingStorageBackend
externalGroupMappingStorage ExternalGroupMappingStorageBackend
// Access Control
authorizer authorizer.Authorizer
+42 -19
View File
@@ -63,6 +63,7 @@ func RegisterAPIService(
coreRolesStorage CoreRoleStorageBackend,
rolesStorage RoleStorageBackend,
roleBindingsStorage RoleBindingStorageBackend,
externalGroupMappingStorageBackend ExternalGroupMappingStorageBackend,
dual dualwrite.Service,
unified resource.ResourceClient,
userService legacyuser.Service,
@@ -74,25 +75,26 @@ func RegisterAPIService(
registerMetrics(reg)
builder := &IdentityAccessManagementAPIBuilder{
store: store,
coreRolesStorage: coreRolesStorage,
rolesStorage: rolesStorage,
resourcePermissionsStorage: resourcepermission.ProvideStorageBackend(dbProvider),
roleBindingsStorage: roleBindingsStorage,
sso: ssoService,
authorizer: authorizer,
legacyAccessClient: legacyAccessClient,
accessClient: accessClient,
zClient: zClient,
zTickets: make(chan bool, MaxConcurrentZanzanaWrites),
display: user.NewLegacyDisplayREST(store),
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),
store: store,
coreRolesStorage: coreRolesStorage,
rolesStorage: rolesStorage,
resourcePermissionsStorage: resourcepermission.ProvideStorageBackend(dbProvider),
roleBindingsStorage: roleBindingsStorage,
externalGroupMappingStorage: externalGroupMappingStorageBackend,
sso: ssoService,
authorizer: authorizer,
legacyAccessClient: legacyAccessClient,
accessClient: accessClient,
zClient: zClient,
zTickets: make(chan bool, MaxConcurrentZanzanaWrites),
display: user.NewLegacyDisplayREST(store),
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),
}
apiregistration.RegisterAPI(builder)
@@ -289,6 +291,27 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge
storage[ssoResource.StoragePath()] = sso.NewLegacyStore(b.sso)
}
externalGroupMappingResource := iamv0.ExternalGroupMappingResourceInfo
externalGroupMappingLegacyStore, err := NewLocalStore(externalGroupMappingResource, apiGroupInfo.Scheme, opts.OptsGetter, b.reg, b.accessClient, b.externalGroupMappingStorage)
if err != nil {
return err
}
storage[externalGroupMappingResource.StoragePath()] = externalGroupMappingLegacyStore
if b.enableDualWriter {
externalGroupMappingStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, externalGroupMappingResource, opts.OptsGetter)
if err != nil {
return err
}
externalGroupMappingDW, err := opts.DualWriteBuilder(externalGroupMappingResource.GroupResource(), externalGroupMappingLegacyStore, externalGroupMappingStore)
if err != nil {
return err
}
storage[externalGroupMappingResource.StoragePath()] = externalGroupMappingDW
}
//nolint:staticcheck // not yet migrated to OpenFeature
if b.features.IsEnabledGlobally(featuremgmt.FlagKubernetesAuthzApis) {
// v0alpha1
+1
View File
@@ -28,6 +28,7 @@ var WireSetExts = wire.NewSet(
wire.Bind(new(iam.CoreRoleStorageBackend), new(*noopstorage.StorageBackendImpl)),
wire.Bind(new(iam.RoleStorageBackend), new(*noopstorage.StorageBackendImpl)),
wire.Bind(new(iam.RoleBindingStorageBackend), new(*noopstorage.StorageBackendImpl)),
wire.Bind(new(iam.ExternalGroupMappingStorageBackend), new(*noopstorage.StorageBackendImpl)),
)
var provisioningExtras = wire.NewSet(
+2 -2
View File
@@ -848,7 +848,7 @@ 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()
identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, storageBackendImpl, storageBackendImpl, dualwriteService, resourceClient, userService)
identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, storageBackendImpl, storageBackendImpl, storageBackendImpl, dualwriteService, resourceClient, userService)
if err != nil {
return nil, err
}
@@ -1482,7 +1482,7 @@ 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()
identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, storageBackendImpl, storageBackendImpl, dualwriteService, resourceClient, userService)
identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, zanzanaClient, registerer, storageBackendImpl, storageBackendImpl, storageBackendImpl, storageBackendImpl, dualwriteService, resourceClient, userService)
if err != nil {
return nil, err
}
+21
View File
@@ -182,6 +182,25 @@ func newFolderTranslation() translation {
return folderTranslation
}
func newExternalGroupMappingTranslation() translation {
return translation{
resource: "teams.permissions",
attribute: "uid",
verbMapping: map[string]string{
utils.VerbGet: "teams.permissions:read",
utils.VerbList: "teams.permissions:read",
utils.VerbWatch: "teams.permissions:read",
utils.VerbCreate: "teams.permissions:write",
utils.VerbUpdate: "teams.permissions:write",
utils.VerbPatch: "teams.permissions:write",
utils.VerbDelete: "teams.permissions:write",
utils.VerbGetPermissions: "teams.permissions:write",
utils.VerbSetPermissions: "teams.permissions:write",
},
folderSupport: false,
}
}
func NewMapperRegistry() MapperRegistry {
skipScopeOnAllVerbs := map[string]bool{
utils.VerbCreate: true,
@@ -210,6 +229,8 @@ func NewMapperRegistry() MapperRegistry {
"serviceaccounts": newResourceTranslation("serviceaccounts", "uid", false, map[string]bool{utils.VerbCreate: true}),
// Teams is a special case. We translate user permissions from id to uid based.
"teams": newResourceTranslation("teams", "uid", false, map[string]bool{utils.VerbCreate: true}),
// ExternalGroupMappings is a special case. We translate team permissions from id to uid based.
"externalgroupmappings": newExternalGroupMappingTranslation(),
"coreroles": translation{
resource: "roles",
attribute: "uid",
+3 -1
View File
@@ -7,6 +7,7 @@ import (
"strings"
"github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/registry/apis/iam/common"
"github.com/grafana/grafana/pkg/registry/apis/iam/legacy"
"github.com/grafana/grafana/pkg/services/accesscontrol"
)
@@ -54,7 +55,7 @@ func (s *Service) newServiceAccountNameResolver(ctx context.Context, ns types.Na
func (s *Service) fetchTeams(ctx context.Context, ns types.NamespaceInfo) (map[int64]string, error) {
key := teamIDsCacheKey(ns.Value)
res, err, _ := s.sf.Do(key, func() (any, error) {
teams, err := s.identityStore.ListTeams(ctx, ns, legacy.ListTeamQuery{})
teams, err := s.identityStore.ListTeams(ctx, ns, legacy.ListTeamQuery{Pagination: common.Pagination{Limit: 100}})
if err != nil {
return nil, fmt.Errorf("could not fetch teams: %w", err)
}
@@ -170,6 +171,7 @@ func (s *Service) nameResolver(ctx context.Context, ns types.NamespaceInfo, scop
if scopePrefix == "teams:id:" {
return s.newTeamNameResolver(ctx, ns)
}
if scopePrefix == "permissions:type:" {
return permissionsDelegateResolverFunc, nil
}
+1 -1
View File
@@ -195,7 +195,7 @@ func NewTestStore(tb TestingTB, opts ...TestOption) *SQLStore {
tb.Fatalf("failed to truncate DB tables after migrations: %v", err)
panic("unreachable")
}
testSQLStore.engine.ResetSequenceGenerator()
store.engine.ResetSequenceGenerator()
}
return store
File diff suppressed because it is too large Load Diff