From 772e5993b6da81f81da0cece0940ae009cfba6bc Mon Sep 17 00:00:00 2001 From: Mihai Doarna Date: Wed, 10 Jan 2024 16:01:37 +0200 Subject: [PATCH] Auth: reload SSO settings for HA setups (#80231) * reload SSO settings for HA setups * remove check for grafana HA * add unit tests * fetch all sso settings with one sql query * register background service --- .../backgroundsvcs/background_services.go | 3 ++ .../ssosettings/ssosettingsimpl/service.go | 39 ++++++++++++++ .../ssosettingsimpl/service_test.go | 53 +++++++++++++++++++ 3 files changed, 95 insertions(+) diff --git a/pkg/registry/backgroundsvcs/background_services.go b/pkg/registry/backgroundsvcs/background_services.go index c2b0078087c..3e73398ffb6 100644 --- a/pkg/registry/backgroundsvcs/background_services.go +++ b/pkg/registry/backgroundsvcs/background_services.go @@ -36,6 +36,7 @@ import ( "github.com/grafana/grafana/pkg/services/serviceaccounts" samanager "github.com/grafana/grafana/pkg/services/serviceaccounts/manager" "github.com/grafana/grafana/pkg/services/ssosettings" + "github.com/grafana/grafana/pkg/services/ssosettings/ssosettingsimpl" "github.com/grafana/grafana/pkg/services/store" "github.com/grafana/grafana/pkg/services/store/entity" "github.com/grafana/grafana/pkg/services/store/sanitizer" @@ -58,6 +59,7 @@ func ProvideBackgroundServiceRegistry( keyRetriever *dynamic.KeyRetriever, dynamicAngularDetectorsProvider *angulardetectorsprovider.Dynamic, grafanaAPIServer grafanaapiserver.Service, anon *anonimpl.AnonDeviceService, + ssoSettings *ssosettingsimpl.SSOSettingsService, // Need to make sure these are initialized, is there a better place to put them? _ dashboardsnapshots.Service, _ *alerting.AlertNotificationService, _ serviceaccounts.Service, _ *guardian.Provider, @@ -98,6 +100,7 @@ func ProvideBackgroundServiceRegistry( dynamicAngularDetectorsProvider, grafanaAPIServer, anon, + ssoSettings, ) } diff --git a/pkg/services/ssosettings/ssosettingsimpl/service.go b/pkg/services/ssosettings/ssosettingsimpl/service.go index adfbe8969bc..c183f45928d 100644 --- a/pkg/services/ssosettings/ssosettingsimpl/service.go +++ b/pkg/services/ssosettings/ssosettingsimpl/service.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "strings" + "time" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/infra/db" @@ -250,6 +251,44 @@ func (s *SSOSettingsService) encryptSecrets(ctx context.Context, settings map[st return result, nil } +func (s *SSOSettingsService) Run(ctx context.Context) error { + ticker := time.NewTicker(1 * time.Minute) + + // start a background process for reloading the SSO settings for all providers at a fixed interval + // it is useful for high availability setups running multiple Grafana instances + for { + select { + case <-ticker.C: + s.doReload(ctx) + + case <-ctx.Done(): + return ctx.Err() + } + } +} + +func (s *SSOSettingsService) doReload(ctx context.Context) { + s.log.Debug("reloading SSO Settings for all providers") + + settingsList, err := s.List(ctx) + if err != nil { + s.log.Error("failed to fetch SSO Settings for all providers", "err", err) + return + } + + for provider, connector := range s.reloadables { + settings := getSettingsByProvider(provider, settingsList) + + if len(settings) > 0 { + err = connector.Reload(ctx, *settings[0]) + if err != nil { + s.log.Error("failed to reload SSO Settings", "provider", provider, "err", err) + continue + } + } + } +} + func isSecret(fieldName string) bool { secretFieldPatterns := []string{"secret"} diff --git a/pkg/services/ssosettings/ssosettingsimpl/service_test.go b/pkg/services/ssosettings/ssosettingsimpl/service_test.go index 17fe14ec1aa..104eb200a92 100644 --- a/pkg/services/ssosettings/ssosettingsimpl/service_test.go +++ b/pkg/services/ssosettings/ssosettingsimpl/service_test.go @@ -634,6 +634,59 @@ func TestSSOSettingsService_Delete(t *testing.T) { }) } +func TestSSOSettingsService_DoReload(t *testing.T) { + t.Run("successfully reload settings", func(t *testing.T) { + env := setupTestEnv(t) + + settingsList := []*models.SSOSettings{ + { + Provider: "github", + Settings: map[string]any{ + "enabled": true, + "client_id": "github_client_id", + }, + }, + { + Provider: "google", + Settings: map[string]any{ + "enabled": true, + "client_id": "google_client_id", + }, + }, + { + Provider: "azuread", + Settings: map[string]any{ + "enabled": true, + "client_id": "azuread_client_id", + }, + }, + } + env.store.ExpectedSSOSettings = settingsList + + reloadable := ssosettingstests.NewMockReloadable(t) + + for _, settings := range settingsList { + reloadable.On("Reload", mock.Anything, *settings).Return(nil).Once() + env.reloadables[settings.Provider] = reloadable + } + + env.service.doReload(context.Background()) + }) + + t.Run("failed fetching the SSO settings", func(t *testing.T) { + env := setupTestEnv(t) + + provider := "github" + + env.store.ExpectedError = errors.New("failed fetching the settings") + + reloadable := ssosettingstests.NewMockReloadable(t) + env.reloadables[provider] = reloadable + + env.service.doReload(context.Background()) + }) +} + func setupTestEnv(t *testing.T) testEnv { store := ssosettingstests.NewFakeStore() fallbackStrategy := ssosettingstests.NewFakeFallbackStrategy()