LBAC for datasources: Adds cleanup of lbac rules added for teams that have been deleted (#111405)

add cleanup of lbac rules for teams that have been deleted
This commit is contained in:
Eric Leijonmarck
2025-09-26 17:55:00 +01:00
committed by GitHub
parent 8fe2925c47
commit e7ca0fdf7b
2 changed files with 140 additions and 3 deletions
+2 -2
View File
@@ -600,7 +600,6 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api
deleteExpiredService := image.ProvideDeleteExpiredService(dBstore)
tempuserService := tempuserimpl.ProvideService(sqlStore, cfg)
cleanupServiceImpl := annotationsimpl.ProvideCleanupService(sqlStore, cfg)
cleanUpService := cleanup.ProvideService(cfg, featureToggles, serverLockService, shortURLService, sqlStore, queryHistoryService, dashverService, serviceImpl, deleteExpiredService, tempuserService, tracingService, cleanupServiceImpl, dBstore, eventualRestConfigProvider, orgService)
secretsKVStore, err := kvstore2.ProvideService(sqlStore, secretsService)
if err != nil {
return nil, err
@@ -612,6 +611,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api
if err != nil {
return nil, err
}
cleanUpService := cleanup.ProvideService(cfg, featureToggles, serverLockService, shortURLService, sqlStore, queryHistoryService, dashverService, serviceImpl, deleteExpiredService, tempuserService, tracingService, cleanupServiceImpl, dBstore, eventualRestConfigProvider, orgService, teamService, service15)
correlationsService, err := correlations.ProvideService(sqlStore, routeRegisterImpl, service15, accessControl, inProcBus, quotaService, cfg)
if err != nil {
return nil, err
@@ -1200,7 +1200,6 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
deleteExpiredService := image.ProvideDeleteExpiredService(dBstore)
tempuserService := tempuserimpl.ProvideService(sqlStore, cfg)
cleanupServiceImpl := annotationsimpl.ProvideCleanupService(sqlStore, cfg)
cleanUpService := cleanup.ProvideService(cfg, featureToggles, serverLockService, shortURLService, sqlStore, queryHistoryService, dashverService, serviceImpl, deleteExpiredService, tempuserService, tracingService, cleanupServiceImpl, dBstore, eventualRestConfigProvider, orgService)
secretsKVStore, err := kvstore2.ProvideService(sqlStore, secretsService)
if err != nil {
return nil, err
@@ -1212,6 +1211,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
if err != nil {
return nil, err
}
cleanUpService := cleanup.ProvideService(cfg, featureToggles, serverLockService, shortURLService, sqlStore, queryHistoryService, dashverService, serviceImpl, deleteExpiredService, tempuserService, tracingService, cleanupServiceImpl, dBstore, eventualRestConfigProvider, orgService, teamService, service15)
correlationsService, err := correlations.ProvideService(sqlStore, routeRegisterImpl, service15, accessControl, inProcBus, quotaService, cfg)
if err != nil {
return nil, err
+138 -1
View File
@@ -28,11 +28,13 @@ import (
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/dashboardsnapshots"
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/ngalert/image"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/queryhistory"
"github.com/grafana/grafana/pkg/services/shorturls"
"github.com/grafana/grafana/pkg/services/team"
tempuser "github.com/grafana/grafana/pkg/services/temp_user"
"github.com/grafana/grafana/pkg/setting"
)
@@ -58,12 +60,14 @@ type CleanUpService struct {
alertRuleService AlertRuleService
clientConfigProvider grafanaapiserver.RestConfigProvider
orgService org.Service
teamService team.Service
dataSourceService datasources.DataSourceService
}
func ProvideService(cfg *setting.Cfg, Features featuremgmt.FeatureToggles, serverLockService *serverlock.ServerLockService,
shortURLService shorturls.Service, sqlstore db.DB, queryHistoryService queryhistory.Service,
dashboardVersionService dashver.Service, dashSnapSvc dashboardsnapshots.Service, deleteExpiredImageService *image.DeleteExpiredService,
tempUserService tempuser.Service, tracer tracing.Tracer, annotationCleaner annotations.Cleaner, service AlertRuleService, clientConfigProvider grafanaapiserver.RestConfigProvider, orgService org.Service) *CleanUpService {
tempUserService tempuser.Service, tracer tracing.Tracer, annotationCleaner annotations.Cleaner, service AlertRuleService, clientConfigProvider grafanaapiserver.RestConfigProvider, orgService org.Service, teamService team.Service, dataSourceService datasources.DataSourceService) *CleanUpService {
s := &CleanUpService{
Cfg: cfg,
Features: Features,
@@ -81,6 +85,8 @@ func ProvideService(cfg *setting.Cfg, Features featuremgmt.FeatureToggles, serve
alertRuleService: service,
clientConfigProvider: clientConfigProvider,
orgService: orgService,
teamService: teamService,
dataSourceService: dataSourceService,
}
return s
}
@@ -125,6 +131,7 @@ func (srv *CleanUpService) clean(ctx context.Context) {
{"expire old user invites", srv.expireOldUserInvites},
{"delete stale query history", srv.deleteStaleQueryHistory},
{"expire old email verifications", srv.expireOldVerifications},
{"cleanup stale LBAC rules", srv.cleanupStaleLBACRules},
}
if srv.Cfg.ShortLinkExpiration > 0 {
@@ -418,3 +425,133 @@ func (srv *CleanUpService) cleanUpTrashAlertRules(ctx context.Context) {
logger.Debug("Cleaned up deleted alert rules", "rows affected", affected)
}
}
// cleanupStaleLBACRules exists to clean up lbac rules that are stale from teams getting deleted as we do not have
// cascading deletions on teams to delete existing lbac rules
func (srv *CleanUpService) cleanupStaleLBACRules(ctx context.Context) {
logger := srv.log.FromContext(ctx)
// Get all datasources
allDataSources, err := srv.dataSourceService.GetAllDataSources(ctx, &datasources.GetAllDataSourcesQuery{})
if err != nil {
logger.Error("Failed to get datasources for LBAC cleanup", "error", err)
return
}
var totalCleaned int
var totalDataSources int
for _, ds := range allDataSources {
if ds.JsonData == nil {
continue
}
// Check if datasource has team LBAC rules
teamHTTPHeaders, err := datasources.GetTeamHTTPHeaders(ds.JsonData)
if err != nil || teamHTTPHeaders == nil {
continue
}
totalDataSources++
// Extract team UIDs and check if teams still exist
cleanedRules, removedCount := srv.getLBACRulesForTeamsStillExisting(ctx, teamHTTPHeaders, ds.OrgID)
if removedCount > 0 {
// Update the datasource with cleaned rules
err := srv.updateDataSourceLBACRules(ctx, ds, cleanedRules)
if err != nil {
logger.Error("Failed to update datasource LBAC rules",
"datasource", ds.UID, "error", err)
} else {
totalCleaned += removedCount
logger.Debug("Cleaned stale LBAC rules",
"datasource", ds.UID, "removed", removedCount)
}
}
}
if totalCleaned > 0 {
logger.Info("Cleaned up stale team LBAC rules",
"datasources_processed", totalDataSources,
"total_rules_removed", totalCleaned)
}
}
func (srv *CleanUpService) getLBACRulesForTeamsStillExisting(ctx context.Context, teamHeaders *datasources.TeamHTTPHeaders, orgID int64) (*datasources.TeamHTTPHeaders, int) {
logger := srv.log.FromContext(ctx)
cleanedHeaders := &datasources.TeamHTTPHeaders{Headers: make(map[string][]datasources.TeamHTTPHeader)}
removedCount := 0
for teamIdentifier, headers := range teamHeaders.Headers {
// Determine if this is a UID or ID
var teamUID string
teamID, err := strconv.ParseInt(teamIdentifier, 10, 64)
if err != nil {
// It's a UID
teamUID = teamIdentifier
} else {
// It's an ID, need to resolve to UID
teamByID, err := srv.teamService.GetTeamByID(ctx, &team.GetTeamByIDQuery{
OrgID: orgID,
ID: teamID,
})
if err != nil {
logger.Debug("Team ID no longer exists, removing LBAC rules",
"teamID", teamIdentifier, "orgID", orgID)
removedCount++
continue
}
teamUID = teamByID.UID
}
// Check if team still exists by UID
_, err = srv.teamService.GetTeamByID(ctx, &team.GetTeamByIDQuery{
OrgID: orgID,
UID: teamUID,
})
if err != nil {
logger.Debug("Team UID no longer exists, removing LBAC rules",
"teamUID", teamUID, "orgID", orgID)
removedCount++
continue
}
// Team exists, keep the rules
cleanedHeaders.Headers[teamIdentifier] = headers
}
return cleanedHeaders, removedCount
}
func (srv *CleanUpService) updateDataSourceLBACRules(ctx context.Context, ds *datasources.DataSource, cleanedHeaders *datasources.TeamHTTPHeaders) error {
// Update JsonData with cleaned rules
jsonData := ds.JsonData
jsonData.Set("teamHttpHeaders", cleanedHeaders)
updateCmd := &datasources.UpdateDataSourceCommand{
ID: ds.ID,
OrgID: ds.OrgID,
UID: ds.UID,
Name: ds.Name,
Type: ds.Type,
Access: ds.Access,
URL: ds.URL,
User: ds.User,
Database: ds.Database,
BasicAuth: ds.BasicAuth,
BasicAuthUser: ds.BasicAuthUser,
WithCredentials: ds.WithCredentials,
IsDefault: ds.IsDefault,
JsonData: jsonData,
AllowLBACRuleUpdates: true,
Version: ds.Version,
ReadOnly: ds.ReadOnly,
APIVersion: ds.APIVersion,
}
_, err := srv.dataSourceService.UpdateDataSource(ctx, updateCmd)
return err
}