From 4bf9405ce49323fb2bd36ec8c65f950468081812 Mon Sep 17 00:00:00 2001 From: Mihai Doarna Date: Thu, 25 Apr 2024 18:30:23 +0300 Subject: [PATCH] SSO: add SSO settings to secrets migrator (#86913) * add sso settings to secrets migrator * unify SSO settings in all log lines --- pkg/services/secrets/migrator/migrator.go | 3 + pkg/services/secrets/migrator/reencrypt.go | 89 +++++++++++++++++++ pkg/services/secrets/migrator/rollback.go | 70 +++++++++++++++ .../ssosettings/ssosettingsimpl/service.go | 11 +-- 4 files changed, 168 insertions(+), 5 deletions(-) diff --git a/pkg/services/secrets/migrator/migrator.go b/pkg/services/secrets/migrator/migrator.go index 3361bc097f8..02872935b9f 100644 --- a/pkg/services/secrets/migrator/migrator.go +++ b/pkg/services/secrets/migrator/migrator.go @@ -45,6 +45,7 @@ func ProvideSecretsMigrator( jsonSecret{tableName: "plugin_setting"}, b64Secret{simpleSecret: simpleSecret{tableName: "signing_key", columnName: "private_key"}, encoding: base64.StdEncoding}, alertingSecret{}, + ssoSettingsSecret{}, } return &SecretsMigrator{ @@ -157,6 +158,8 @@ type jsonSecret struct { type alertingSecret struct{} +type ssoSettingsSecret struct{} + func nowInUTC() string { return time.Now().UTC().Format("2006-01-02 15:04:05") } diff --git a/pkg/services/secrets/migrator/reencrypt.go b/pkg/services/secrets/migrator/reencrypt.go index 990774e6b86..6c1271277a4 100644 --- a/pkg/services/secrets/migrator/reencrypt.go +++ b/pkg/services/secrets/migrator/reencrypt.go @@ -11,6 +11,8 @@ import ( "github.com/grafana/grafana/pkg/services/secrets" "github.com/grafana/grafana/pkg/services/secrets/manager" "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/ssosettings/models" + "github.com/grafana/grafana/pkg/services/ssosettings/ssosettingsimpl" ) func (s simpleSecret) ReEncrypt(ctx context.Context, secretsSrv *manager.SecretsService, sqlStore db.DB) bool { @@ -289,3 +291,90 @@ func (s alertingSecret) ReEncrypt(ctx context.Context, secretsSrv *manager.Secre return !anyFailure } + +func (s ssoSettingsSecret) ReEncrypt(ctx context.Context, secretsSrv *manager.SecretsService, sqlStore db.DB) bool { + results := make([]*models.SSOSettings, 0) + + err := sqlStore.WithDbSession(ctx, func(sess *db.Session) error { + return sess.Find(&results) + }) + + if err != nil { + logger.Warn("Failed to fetch SSO settings to re-encrypt", "err", err) + return false + } + + var anyFailure bool + + for _, result := range results { + err := sqlStore.InTransaction(ctx, func(ctx context.Context) error { + for field, value := range result.Settings { + if ssosettingsimpl.IsSecretField(field) { + decrypted, err := s.decryptValue(ctx, value, secretsSrv) + if err != nil { + logger.Warn("Could not decrypt SSO settings secret", "id", result.ID, "field", field, "error", err) + return err + } + + if decrypted == nil { + continue + } + + reencrypted, err := secretsSrv.Encrypt(ctx, decrypted, secrets.WithoutScope()) + if err != nil { + logger.Warn("Could not re-encrypt SSO settings secret", "id", result.ID, "field", field, "error", err) + return err + } + + result.Settings[field] = base64.RawStdEncoding.EncodeToString(reencrypted) + } + } + + err = sqlStore.WithDbSession(ctx, func(sess *db.Session) error { + _, err := sess.Where("id = ?", result.ID).Update(result) + return err + }) + if err != nil { + logger.Warn("Could not update SSO settings secrets while re-encrypting it", "id", result.ID, "error", err) + return err + } + + return nil + }) + + if err != nil { + anyFailure = true + } + } + + if anyFailure { + logger.Warn("SSO settings secrets have been re-encrypted with errors") + } else { + logger.Info("SSO settings secrets have been re-encrypted successfully") + } + + return !anyFailure +} + +func (s ssoSettingsSecret) decryptValue(ctx context.Context, value any, secretsSrv *manager.SecretsService) ([]byte, error) { + strValue, ok := value.(string) + if !ok { + return nil, fmt.Errorf("SSO secret value is not a string") + } + + if strValue == "" { + return nil, nil + } + + decoded, err := base64.RawStdEncoding.DecodeString(strValue) + if err != nil { + return nil, fmt.Errorf("could not decode base64-encoded SSO settings secret: %w", err) + } + + decrypted, err := secretsSrv.Decrypt(ctx, decoded) + if err != nil { + return nil, fmt.Errorf("could not decrypt SSO settings secret: %w", err) + } + + return decrypted, nil +} diff --git a/pkg/services/secrets/migrator/rollback.go b/pkg/services/secrets/migrator/rollback.go index c68c0962771..8a8838c0f96 100644 --- a/pkg/services/secrets/migrator/rollback.go +++ b/pkg/services/secrets/migrator/rollback.go @@ -10,6 +10,8 @@ import ( "github.com/grafana/grafana/pkg/services/encryption" "github.com/grafana/grafana/pkg/services/ngalert/notifier" "github.com/grafana/grafana/pkg/services/secrets/manager" + "github.com/grafana/grafana/pkg/services/ssosettings/models" + "github.com/grafana/grafana/pkg/services/ssosettings/ssosettingsimpl" ) func (s simpleSecret) Rollback( @@ -294,3 +296,71 @@ func (s alertingSecret) Rollback( return anyFailure } + +func (s ssoSettingsSecret) Rollback( + ctx context.Context, + secretsSrv *manager.SecretsService, + encryptionSrv encryption.Internal, + sqlStore db.DB, + secretKey string, +) (anyFailure bool) { + results := make([]*models.SSOSettings, 0) + + err := sqlStore.WithDbSession(ctx, func(sess *db.Session) error { + return sess.Find(&results) + }) + + if err != nil { + logger.Warn("Failed to fetch SSO settings to roll back") + return true + } + + for _, result := range results { + err := sqlStore.WithTransactionalDbSession(ctx, func(sess *db.Session) error { + for field, value := range result.Settings { + if ssosettingsimpl.IsSecretField(field) { + decrypted, err := s.decryptValue(ctx, value, secretsSrv) + if err != nil { + logger.Warn("Could not decrypt SSO settings secret", "id", result.ID, "field", field, "error", err) + return err + } + + if decrypted == nil { + continue + } + + reencrypted, err := encryptionSrv.Encrypt(ctx, decrypted, secretKey) + if err != nil { + logger.Warn("Could not re-encrypt SSO settings secret", "id", result.ID, "field", field, "error", err) + return err + } + + result.Settings[field] = base64.RawStdEncoding.EncodeToString(reencrypted) + } + } + + err = sqlStore.WithDbSession(ctx, func(sess *db.Session) error { + _, err := sess.Where("id = ?", result.ID).Update(result) + return err + }) + if err != nil { + logger.Warn("Could not update SSO settings secrets while re-encrypting it", "id", result.ID, "error", err) + return err + } + + return nil + }) + + if err != nil { + anyFailure = true + } + } + + if anyFailure { + logger.Warn("SSO settings secrets have been rolled back with errors") + } else { + logger.Info("SSO settings secrets have been rolled back successfully") + } + + return anyFailure +} diff --git a/pkg/services/ssosettings/ssosettingsimpl/service.go b/pkg/services/ssosettings/ssosettingsimpl/service.go index b3edf2910a0..f1f2f3e97bc 100644 --- a/pkg/services/ssosettings/ssosettingsimpl/service.go +++ b/pkg/services/ssosettings/ssosettingsimpl/service.go @@ -324,7 +324,7 @@ func (s *Service) getFallbackStrategyFor(provider string) (ssosettings.FallbackS func (s *Service) encryptSecrets(ctx context.Context, settings map[string]any) (map[string]any, error) { result := make(map[string]any) for k, v := range settings { - if isSecret(k) && v != "" { + if IsSecretField(k) && v != "" { strValue, ok := v.(string) if !ok { return result, fmt.Errorf("failed to encrypt %s setting because it is not a string: %v", k, v) @@ -414,7 +414,7 @@ func (s *Service) mergeSSOSettings(dbSettings, systemSettings *models.SSOSetting func (s *Service) decryptSecrets(ctx context.Context, settings map[string]any) (map[string]any, error) { for k, v := range settings { - if isSecret(k) && v != "" { + if IsSecretField(k) && v != "" { strValue, ok := v.(string) if !ok { s.logger.Error("Failed to parse secret value, it is not a string", "key", k) @@ -449,7 +449,7 @@ func (s *Service) isProviderConfigurable(provider string) bool { func removeSecrets(settings map[string]any) map[string]any { result := make(map[string]any) for k, v := range settings { - if isSecret(k) { + if IsSecretField(k) { result[k] = setting.RedactedPassword continue } @@ -487,7 +487,7 @@ func mergeSettings(storedSettings, systemSettings map[string]any) map[string]any func mergeSecrets(settings map[string]any, storedSettings map[string]any) (map[string]any, error) { settingsWithSecrets := map[string]any{} for k, v := range settings { - if isSecret(k) { + if IsSecretField(k) { strValue, ok := v.(string) if !ok { return nil, fmt.Errorf("secret value is not a string") @@ -515,7 +515,8 @@ func overrideMaps(maps ...map[string]any) map[string]any { return result } -func isSecret(fieldName string) bool { +// IsSecretField returns true if the SSO settings field provided is a secret +func IsSecretField(fieldName string) bool { secretFieldPatterns := []string{"secret", "private", "certificate"} for _, v := range secretFieldPatterns {