SSO: add SSO settings to secrets migrator (#86913)

* add sso settings to secrets migrator

* unify SSO settings in all log lines
This commit is contained in:
Mihai Doarna 2024-04-25 18:30:23 +03:00 committed by GitHub
parent ac152ca416
commit 4bf9405ce4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 168 additions and 5 deletions

View File

@ -45,6 +45,7 @@ func ProvideSecretsMigrator(
jsonSecret{tableName: "plugin_setting"}, jsonSecret{tableName: "plugin_setting"},
b64Secret{simpleSecret: simpleSecret{tableName: "signing_key", columnName: "private_key"}, encoding: base64.StdEncoding}, b64Secret{simpleSecret: simpleSecret{tableName: "signing_key", columnName: "private_key"}, encoding: base64.StdEncoding},
alertingSecret{}, alertingSecret{},
ssoSettingsSecret{},
} }
return &SecretsMigrator{ return &SecretsMigrator{
@ -157,6 +158,8 @@ type jsonSecret struct {
type alertingSecret struct{} type alertingSecret struct{}
type ssoSettingsSecret struct{}
func nowInUTC() string { func nowInUTC() string {
return time.Now().UTC().Format("2006-01-02 15:04:05") return time.Now().UTC().Format("2006-01-02 15:04:05")
} }

View File

@ -11,6 +11,8 @@ import (
"github.com/grafana/grafana/pkg/services/secrets" "github.com/grafana/grafana/pkg/services/secrets"
"github.com/grafana/grafana/pkg/services/secrets/manager" "github.com/grafana/grafana/pkg/services/secrets/manager"
"github.com/grafana/grafana/pkg/services/sqlstore" "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 { 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 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
}

View File

@ -10,6 +10,8 @@ import (
"github.com/grafana/grafana/pkg/services/encryption" "github.com/grafana/grafana/pkg/services/encryption"
"github.com/grafana/grafana/pkg/services/ngalert/notifier" "github.com/grafana/grafana/pkg/services/ngalert/notifier"
"github.com/grafana/grafana/pkg/services/secrets/manager" "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( func (s simpleSecret) Rollback(
@ -294,3 +296,71 @@ func (s alertingSecret) Rollback(
return anyFailure 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
}

View File

@ -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) { func (s *Service) encryptSecrets(ctx context.Context, settings map[string]any) (map[string]any, error) {
result := make(map[string]any) result := make(map[string]any)
for k, v := range settings { for k, v := range settings {
if isSecret(k) && v != "" { if IsSecretField(k) && v != "" {
strValue, ok := v.(string) strValue, ok := v.(string)
if !ok { if !ok {
return result, fmt.Errorf("failed to encrypt %s setting because it is not a string: %v", k, v) 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) { func (s *Service) decryptSecrets(ctx context.Context, settings map[string]any) (map[string]any, error) {
for k, v := range settings { for k, v := range settings {
if isSecret(k) && v != "" { if IsSecretField(k) && v != "" {
strValue, ok := v.(string) strValue, ok := v.(string)
if !ok { if !ok {
s.logger.Error("Failed to parse secret value, it is not a string", "key", k) 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 { func removeSecrets(settings map[string]any) map[string]any {
result := make(map[string]any) result := make(map[string]any)
for k, v := range settings { for k, v := range settings {
if isSecret(k) { if IsSecretField(k) {
result[k] = setting.RedactedPassword result[k] = setting.RedactedPassword
continue 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) { func mergeSecrets(settings map[string]any, storedSettings map[string]any) (map[string]any, error) {
settingsWithSecrets := map[string]any{} settingsWithSecrets := map[string]any{}
for k, v := range settings { for k, v := range settings {
if isSecret(k) { if IsSecretField(k) {
strValue, ok := v.(string) strValue, ok := v.(string)
if !ok { if !ok {
return nil, fmt.Errorf("secret value is not a string") 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 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"} secretFieldPatterns := []string{"secret", "private", "certificate"}
for _, v := range secretFieldPatterns { for _, v := range secretFieldPatterns {