Encryption: Expose secrets migrations through HTTP API (#51707)

* Encryption: Move secrets migrations into secrets.Migrator

* Encryption: Refactor secrets.Service initialization

* Encryption: Add support to run secrets migrations even when EE is disabled

* Encryption: Expose secrets migrations through HTTP API

* Update docs

* Fix docs links

* Some adjustments to makes errors explicit through HTTP response
This commit is contained in:
Joan López de la Franca Beltran
2022-07-18 08:57:58 +02:00
committed by GitHub
parent a71b4f13e4
commit 9abe9fa702
8 changed files with 168 additions and 45 deletions
+35 -1
View File
@@ -9,8 +9,42 @@ import (
func (hs *HTTPServer) AdminRotateDataEncryptionKeys(c *models.ReqContext) response.Response {
if err := hs.SecretsService.RotateDataKeys(c.Req.Context()); err != nil {
return response.Error(http.StatusInternalServerError, "Failed to rotate data key", err)
return response.Error(http.StatusInternalServerError, "Failed to rotate data keys", err)
}
return response.Respond(http.StatusNoContent, "")
}
func (hs *HTTPServer) AdminReEncryptEncryptionKeys(c *models.ReqContext) response.Response {
if err := hs.SecretsService.ReEncryptDataKeys(c.Req.Context()); err != nil {
return response.Error(http.StatusInternalServerError, "Failed to re-encrypt data keys", err)
}
return response.Respond(http.StatusOK, "Data encryption keys re-encrypted successfully")
}
func (hs *HTTPServer) AdminReEncryptSecrets(c *models.ReqContext) response.Response {
success, err := hs.secretsMigrator.ReEncryptSecrets(c.Req.Context())
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to re-encrypt secrets", err)
}
if !success {
return response.Error(http.StatusPartialContent, "Something unexpected happened, refer to the server logs for more details", err)
}
return response.Respond(http.StatusOK, "Secrets re-encrypted successfully")
}
func (hs *HTTPServer) AdminRollbackSecrets(c *models.ReqContext) response.Response {
success, err := hs.secretsMigrator.RollBackSecrets(c.Req.Context())
if err != nil {
return response.Error(http.StatusInternalServerError, "Failed to rollback secrets", err)
}
if !success {
return response.Error(http.StatusPartialContent, "Something unexpected happened, refer to the server logs for more details", err)
}
return response.Respond(http.StatusOK, "Secrets rolled back successfully")
}
+3
View File
@@ -575,6 +575,9 @@ func (hs *HTTPServer) registerRoutes() {
}
adminRoute.Post("/encryption/rotate-data-keys", reqGrafanaAdmin, routing.Wrap(hs.AdminRotateDataEncryptionKeys))
adminRoute.Post("/encryption/reencrypt-data-keys", reqGrafanaAdmin, routing.Wrap(hs.AdminReEncryptEncryptionKeys))
adminRoute.Post("/encryption/reencrypt-secrets", reqGrafanaAdmin, routing.Wrap(hs.AdminReEncryptSecrets))
adminRoute.Post("/encryption/rollback-secrets", reqGrafanaAdmin, routing.Wrap(hs.AdminRollbackSecrets))
adminRoute.Post("/provisioning/dashboards/reload", authorize(reqGrafanaAdmin, ac.EvalPermission(ActionProvisioningReload, ScopeProvisionersDashboards)), routing.Wrap(hs.AdminProvisioningReloadDashboards))
adminRoute.Post("/provisioning/plugins/reload", authorize(reqGrafanaAdmin, ac.EvalPermission(ActionProvisioningReload, ScopeProvisionersPlugins)), routing.Wrap(hs.AdminProvisioningReloadPlugins))
@@ -12,9 +12,11 @@ func ReEncryptDEKS(_ utils.CommandLine, runner runner.Runner) error {
}
func ReEncryptSecrets(_ utils.CommandLine, runner runner.Runner) error {
return runner.SecretsMigrator.ReEncryptSecrets(context.Background())
_, err := runner.SecretsMigrator.ReEncryptSecrets(context.Background())
return err
}
func RollBackSecrets(_ utils.CommandLine, runner runner.Runner) error {
return runner.SecretsMigrator.RollBackSecrets(context.Background())
_, err := runner.SecretsMigrator.RollBackSecrets(context.Background())
return err
}
+27 -21
View File
@@ -37,14 +37,14 @@ func ProvideSecretsMigrator(
}
}
func (m *SecretsMigrator) ReEncryptSecrets(ctx context.Context) error {
func (m *SecretsMigrator) ReEncryptSecrets(ctx context.Context) (bool, error) {
err := m.initProvidersIfNeeded()
if err != nil {
return err
return false, err
}
toReencrypt := []interface {
reencrypt(context.Context, *manager.SecretsService, *sqlstore.SQLStore)
reencrypt(context.Context, *manager.SecretsService, *sqlstore.SQLStore) bool
}{
simpleSecret{tableName: "dashboard_snapshot", columnName: "dashboard_encrypted"},
b64Secret{simpleSecret: simpleSecret{tableName: "user_auth", columnName: "o_auth_access_token"}, encoding: base64.StdEncoding},
@@ -56,30 +56,21 @@ func (m *SecretsMigrator) ReEncryptSecrets(ctx context.Context) error {
alertingSecret{},
}
var anyFailure bool
for _, r := range toReencrypt {
r.reencrypt(ctx, m.secretsSrv, m.sqlStore)
}
return nil
}
func (m *SecretsMigrator) initProvidersIfNeeded() error {
if m.features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) {
logger.Info("Envelope encryption is not enabled but trying to init providers anyway...")
if err := m.secretsSrv.InitProviders(); err != nil {
logger.Error("Envelope encryption providers initialization failed", "error", err)
return err
if success := r.reencrypt(ctx, m.secretsSrv, m.sqlStore); !success {
anyFailure = true
}
}
return nil
return !anyFailure, nil
}
func (m *SecretsMigrator) RollBackSecrets(ctx context.Context) error {
func (m *SecretsMigrator) RollBackSecrets(ctx context.Context) (bool, error) {
err := m.initProvidersIfNeeded()
if err != nil {
return err
return false, err
}
toRollback := []interface {
@@ -110,11 +101,26 @@ func (m *SecretsMigrator) RollBackSecrets(ctx context.Context) error {
if anyFailure {
logger.Warn("Some errors happened, not cleaning up data keys table...")
return nil
return false, nil
}
if _, sqlErr := m.sqlStore.NewSession(ctx).Exec("DELETE FROM data_keys"); sqlErr != nil {
_, sqlErr := m.sqlStore.NewSession(ctx).Exec("DELETE FROM data_keys")
if sqlErr != nil {
logger.Warn("Error while cleaning up data keys table...", "error", sqlErr)
return false, nil
}
return true, nil
}
func (m *SecretsMigrator) initProvidersIfNeeded() error {
if m.features.IsEnabled(featuremgmt.FlagDisableEnvelopeEncryption) {
logger.Info("Envelope encryption is not enabled but trying to init providers anyway...")
if err := m.secretsSrv.InitProviders(); err != nil {
logger.Error("Envelope encryption providers initialization failed", "error", err)
return err
}
}
return nil
+16 -8
View File
@@ -12,7 +12,7 @@ import (
"github.com/grafana/grafana/pkg/services/sqlstore"
)
func (s simpleSecret) reencrypt(ctx context.Context, secretsSrv *manager.SecretsService, sqlStore *sqlstore.SQLStore) {
func (s simpleSecret) reencrypt(ctx context.Context, secretsSrv *manager.SecretsService, sqlStore *sqlstore.SQLStore) bool {
var rows []struct {
Id int
Secret []byte
@@ -20,7 +20,7 @@ func (s simpleSecret) reencrypt(ctx context.Context, secretsSrv *manager.Secrets
if err := sqlStore.NewSession(ctx).Table(s.tableName).Select(fmt.Sprintf("id, %s as secret", s.columnName)).Find(&rows); err != nil {
logger.Warn("Could not find any secret to re-encrypt", "table", s.tableName)
return
return false
}
var anyFailure bool
@@ -62,9 +62,11 @@ func (s simpleSecret) reencrypt(ctx context.Context, secretsSrv *manager.Secrets
} else {
logger.Info(fmt.Sprintf("Column %s from %s has been re-encrypted successfully", s.columnName, s.tableName))
}
return !anyFailure
}
func (s b64Secret) reencrypt(ctx context.Context, secretsSrv *manager.SecretsService, sqlStore *sqlstore.SQLStore) {
func (s b64Secret) reencrypt(ctx context.Context, secretsSrv *manager.SecretsService, sqlStore *sqlstore.SQLStore) bool {
var rows []struct {
Id int
Secret string
@@ -72,7 +74,7 @@ func (s b64Secret) reencrypt(ctx context.Context, secretsSrv *manager.SecretsSer
if err := sqlStore.NewSession(ctx).Table(s.tableName).Select(fmt.Sprintf("id, %s as secret", s.columnName)).Find(&rows); err != nil {
logger.Warn("Could not find any secret to re-encrypt", "table", s.tableName)
return
return false
}
var anyFailure bool
@@ -128,9 +130,11 @@ func (s b64Secret) reencrypt(ctx context.Context, secretsSrv *manager.SecretsSer
} else {
logger.Info(fmt.Sprintf("Column %s from %s has been re-encrypted successfully", s.columnName, s.tableName))
}
return !anyFailure
}
func (s jsonSecret) reencrypt(ctx context.Context, secretsSrv *manager.SecretsService, sqlStore *sqlstore.SQLStore) {
func (s jsonSecret) reencrypt(ctx context.Context, secretsSrv *manager.SecretsService, sqlStore *sqlstore.SQLStore) bool {
var rows []struct {
Id int
SecureJsonData map[string][]byte
@@ -138,7 +142,7 @@ func (s jsonSecret) reencrypt(ctx context.Context, secretsSrv *manager.SecretsSe
if err := sqlStore.NewSession(ctx).Table(s.tableName).Cols("id", "secure_json_data").Find(&rows); err != nil {
logger.Warn("Could not find any secret to re-encrypt", "table", s.tableName)
return
return false
}
var anyFailure bool
@@ -184,9 +188,11 @@ func (s jsonSecret) reencrypt(ctx context.Context, secretsSrv *manager.SecretsSe
} else {
logger.Info(fmt.Sprintf("Secure json data secrets from %s have been re-encrypted successfully", s.tableName))
}
return !anyFailure
}
func (s alertingSecret) reencrypt(ctx context.Context, secretsSrv *manager.SecretsService, sqlStore *sqlstore.SQLStore) {
func (s alertingSecret) reencrypt(ctx context.Context, secretsSrv *manager.SecretsService, sqlStore *sqlstore.SQLStore) bool {
var results []struct {
Id int
AlertmanagerConfiguration string
@@ -195,7 +201,7 @@ func (s alertingSecret) reencrypt(ctx context.Context, secretsSrv *manager.Secre
selectSQL := "SELECT id, alertmanager_configuration FROM alert_configuration"
if err := sqlStore.NewSession(ctx).SQL(selectSQL).Find(&results); err != nil {
logger.Warn("Could not find any alert_configuration secret to re-encrypt")
return
return false
}
var anyFailure bool
@@ -261,4 +267,6 @@ func (s alertingSecret) reencrypt(ctx context.Context, secretsSrv *manager.Secre
} else {
logger.Info("Alerting configuration secrets have been re-encrypted successfully")
}
return !anyFailure
}
+10 -2
View File
@@ -72,6 +72,14 @@ type BackgroundProvider interface {
// Migrator is responsible for secrets migrations like re-encrypting or rolling back secrets.
type Migrator interface {
ReEncryptSecrets(ctx context.Context) error
RollBackSecrets(ctx context.Context) error
// ReEncryptSecrets decrypts and re-encrypts the secrets with most recent
// available data key. If a secret-specific decryption / re-encryption fails,
// it does not stop, but returns false as the first return (success or not)
// at the end of the process.
ReEncryptSecrets(ctx context.Context) (bool, error)
// RollBackSecrets decrypts and re-encrypts the secrets using the legacy
// encryption. If a secret-specific decryption / re-encryption fails, it
// does not stop, but returns false as the first return (success or not)
// at the end of the process.
RollBackSecrets(ctx context.Context) (bool, error)
}