/
githubmirror
/
grafana
Обзор
Документация
Войти
/
githubmirror
/
grafana
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
pkg/tests/api/admin/encryption/reencrypt_test.go
294 строки
10 KB
Matheus Macabu
Admin: Retry queries and tx more times in sqlite (#121926)
06 апр 2026, 15:01
Не верифицирован
06 апр 2026, 15:01
765f5a8
Код
Авторство
О чём код?
package encryption import ( "context" "encoding/base64" "fmt" "testing" "time" "github.com/grafana/alerting/receivers/oncall" claims "github.com/grafana/authlib/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/server" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apiserver/options" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/datasources" alertmodels "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/notifier" "github.com/grafana/grafana/pkg/services/user" "github.com/grafana/grafana/pkg/tests/testinfra" "github.com/grafana/grafana/pkg/tests/testsuite" "github.com/grafana/grafana/pkg/util/testutil" ) func TestMain(m *testing.M) { testsuite.Run(m) } func TestIntegration_AdminApiReencrypt(t *testing.T) { testutil.SkipIntegrationTestInShortMode(t) const ( dataSourceTable = "data_source" secretsTable = "secrets" secretsValueColumn = "value" alertmanagerSecureSettingKey = "password" secureJsonKey = "db-secure-key" ) getSecretsFunctions := map[string]func(*testing.T, *server.TestEnv) map[int]secret{} getSecretsFunctions["secureJson-"+dataSourceTable] = func(t *testing.T, env *server.TestEnv) map[int]secret { return getSecureJsonSecrets(t, env.SQLStore, dataSourceTable, secureJsonKey) } getSecretsFunctions["base64-"+secretsTable+"-"+secretsValueColumn] = func(t *testing.T, env *server.TestEnv) map[int]secret { return getBase64Secrets(t, env.SQLStore, secretsTable, secretsValueColumn, base64.RawStdEncoding) } getSecretsFunctions["alertmanager"] = func(t *testing.T, env *server.TestEnv) map[int]secret { return getAlertmanagerSecrets(t, env.SQLStore, alertmanagerSecureSettingKey) } getSecretsFunctions["signing_keys"] = func(t *testing.T, env *server.TestEnv) map[int]secret { return getSigningKeys(t, env.SQLStore) } setup := func(t *testing.T, env *server.TestEnv, grafanaListenAddr string) { dsCmd := &datasources.AddDataSourceCommand{ Name: "TestDatasource", Type: "testdata", Access: datasources.DS_ACCESS_DIRECT, UID: "testuid", UserID: 1, OrgID: 1, WithCredentials: true, SecureJsonData: map[string]string{ secureJsonKey: "db-secure-value", }, } // This creates secret both in `data_source` table and `secrets` table. _, err := env.Server.HTTPServer.DataSourcesService.AddDataSource(context.Background(), dsCmd) require.NoError(t, err) // Trigger creation of signing key _, _, err = env.IDService.SignIdentity(context.Background(), &authn.Identity{ID: "1", Type: claims.TypeUser}) require.NoError(t, err) // Add alerting config with secure settings. addAlertingConfig(t, env) } RunAdminApiReencryptTest(t, setup, getSecretsFunctions) } // This test verifies that secrets in various databases are reencrypted with new data key when reencryption is triggered. // Setup function is supposed to create various secrets that are then // obtained via "secretsFunctions". // This test is quite generic so that it can be called from enterprise repository as well. func RunAdminApiReencryptTest( t *testing.T, setup func(t *testing.T, env *server.TestEnv, grafanaListenAddr string), secretsFns map[string]func(t *testing.T, env *server.TestEnv) map[int]secret, ) { dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ APIServerStorageType: options.StorageTypeUnified, EnableLog: false, QueryRetries: 30, // arbitrary 3x from the default TransactionRetries: 30, // arbitrary 3x from the default }) grafanaListenAddr, env := testinfra.StartGrafanaEnv(t, dir, path) setup(t, env, grafanaListenAddr) beforeReencrypt := getSecrets(t, secretsFns, env) err := env.Server.HTTPServer.SecretsService.RotateDataKeys(context.Background()) require.NoError(t, err) // Reencrypt with new data key. ok, err := env.Server.HTTPServer.SecretsMigrator.ReEncryptSecrets(context.Background()) require.NoError(t, err) require.True(t, ok, "Failed to reencrypt all secrets") afterReencrypt := getSecrets(t, secretsFns, env) verifyAllSecrets(t, env, beforeReencrypt, afterReencrypt) // Rollback from envelope to legacy encryption. ok, err = env.Server.HTTPServer.SecretsMigrator.RollBackSecrets(context.Background()) require.NoError(t, err) require.True(t, ok, "Failed to rollback all secrets") afterRollback := getSecrets(t, secretsFns, env) verifyAllSecrets(t, env, afterReencrypt, afterRollback) } func getSecrets(t *testing.T, secretsFunctions map[string]func(t *testing.T, env *server.TestEnv) map[int]secret, env *server.TestEnv) map[string]map[int]secret { secrets := map[string]map[int]secret{} for name, fn := range secretsFunctions { s := fn(t, env) require.NotEmpty(t, s, "Failed to get secrets from function %s", name) secrets[name] = s } return secrets } func getAlertmanagerSecrets(t *testing.T, store db.DB, secureSettingKey string) map[int]secret { var rows []struct { Id int AlertmanagerConfiguration string } err := store.WithDbSession(t.Context(), func(sess *db.Session) error { return sess.Table("alert_configuration").Cols("id", "alertmanager_configuration").Find(&rows) }) require.NoError(t, err) result := map[int]secret{} next: for _, r := range rows { postableUserConfig, err := notifier.Load([]byte(r.AlertmanagerConfiguration)) require.NoError(t, err) // Find first grafana-managed receiver config with secure settings with given key, and extract it. for _, receiver := range postableUserConfig.AlertmanagerConfig.Receivers { for _, gmr := range receiver.GrafanaManagedReceivers { v := gmr.SecureSettings[secureSettingKey] if v == "" { continue } decoded, err := base64.StdEncoding.DecodeString(v) require.NoError(t, err) result[r.Id] = secret{ id: r.Id, secret: append([]byte(nil), decoded...), } continue next } } } return result } func addAlertingConfig(t *testing.T, env *server.TestEnv) { // Receiver has the secure field "password". receiverWithSecrets := alertmodels.ReceiverGen(alertmodels.ReceiverMuts.WithName("receiver-1"), alertmodels.ReceiverMuts.WithValidIntegration(oncall.Type))() u := &user.SignedInUser{OrgID: 1, Permissions: map[int64]map[string][]string{ 1: {accesscontrol.ActionAlertingReceiversCreate: nil}, }} _, err := env.Server.HTTPServer.AlertNG.Api.ReceiverService.CreateReceiver(context.Background(), &receiverWithSecrets, 1, u) require.NoError(t, err) } type secret struct { id int secret []byte update time.Time } func verifyAllSecrets(t *testing.T, env *server.TestEnv, before, after map[string]map[int]secret) { require.Equal(t, len(before), len(after)) for k, bef := range before { aft, ok := after[k] require.True(t, ok) verifySecrets(t, env, bef, aft) } } func verifySecrets(t *testing.T, env *server.TestEnv, before, after map[int]secret) { require.Equal(t, len(before), len(after)) for k, bef := range before { aft, ok := after[k] require.True(t, ok, "key not found: %d", k) require.NotEmpty(t, bef.secret, "before secret is empty for key %d", k) require.NotEmpty(t, aft.secret, "after secret is empty for key %d", k) require.NotEqual(t, bef.secret, aft.secret, "secrets are equal after reencrypt for key %d", k) s1, err := env.Server.HTTPServer.SecretsService.Decrypt(context.Background(), bef.secret) require.NoError(t, err) s2, err := env.Server.HTTPServer.SecretsService.Decrypt(context.Background(), aft.secret) require.NoError(t, err) assert.Equal(t, string(s1), string(s2), "decrypted secrets are not equal for key %d", k) updatedDiff := aft.update.Sub(bef.update) // Since we're storing timestamps with seconds resolution, diff can be 0. require.True(t, 0 <= updatedDiff && updatedDiff <= time.Minute, "Updated time difference (%v) outside of allowed range for key %d", updatedDiff, k) } } func getSecureJsonSecrets(t *testing.T, store db.DB, table string, secureJsonDataKey string) map[int]secret { var rows []struct { Id int SecureJsonData map[string][]byte Updated time.Time } err := store.WithDbSession(t.Context(), func(sess *db.Session) error { return sess.Table(table).Cols("id", "secure_json_data", "updated").Find(&rows) }) require.NoError(t, err) result := map[int]secret{} for _, r := range rows { result[r.Id] = secret{ id: r.Id, secret: append([]byte(nil), r.SecureJsonData[secureJsonDataKey]...), update: r.Updated, } } return result } func getBase64Secrets(t *testing.T, store db.DB, table, column string, enc *base64.Encoding) map[int]secret { var rows []struct { Id int Secret string Updated time.Time } err := store.WithDbSession(t.Context(), func(sess *db.Session) error { return sess.Table(table).Select(fmt.Sprintf("id, %s as secret, updated", column)).Find(&rows) }) require.NoError(t, err) result := map[int]secret{} for _, r := range rows { d, err := enc.DecodeString(r.Secret) require.NoError(t, err) result[r.Id] = secret{ id: r.Id, secret: append([]byte(nil), d...), update: r.Updated, } } return result } func getSigningKeys(t *testing.T, store db.DB) map[int]secret { var rows []struct { Id int Pk string } err := store.WithDbSession(t.Context(), func(sess *db.Session) error { return sess.Table("signing_key").Select("id, private_key as pk").Find(&rows) }) require.NoError(t, err) result := map[int]secret{} for _, r := range rows { d, err := base64.RawStdEncoding.DecodeString(r.Pk) require.NoError(t, err) result[r.Id] = secret{ id: r.Id, secret: append([]byte(nil), d...), // there's no update time, leave it at 0 } } return result }