* Revert change of order in newResourceDBProvider * Ignore error in provisioningSecrets when working with wrong DB. * Update workspace. * Add owner.
189 lines
5.5 KiB
Go
189 lines
5.5 KiB
Go
package migrator
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/db"
|
|
"github.com/grafana/grafana/pkg/services/encryption"
|
|
"github.com/grafana/grafana/pkg/services/secrets"
|
|
"github.com/grafana/grafana/pkg/services/secrets/manager"
|
|
)
|
|
|
|
var errNoEncryptedValue = errors.New("no encrypted value was found")
|
|
|
|
var _ SecretsRotator = provisioningSecrets{}
|
|
|
|
type provisioningSecrets struct{}
|
|
|
|
func (p provisioningSecrets) ReEncrypt(ctx context.Context, svc *manager.SecretsService, sqlStore db.DB) bool {
|
|
return p.reEncrypt(ctx, svc, func(ctx context.Context, payload []byte) ([]byte, error) {
|
|
return svc.Encrypt(ctx, payload, secrets.WithoutScope())
|
|
}, sqlStore, "rotation")
|
|
}
|
|
|
|
func (p provisioningSecrets) Rollback(ctx context.Context, svc *manager.SecretsService, internal encryption.Internal, sqlStore db.DB, secretKey string) bool {
|
|
return p.reEncrypt(ctx, svc, func(ctx context.Context, payload []byte) ([]byte, error) {
|
|
return internal.Encrypt(ctx, payload, secretKey)
|
|
}, sqlStore, "rollback")
|
|
}
|
|
|
|
func (p provisioningSecrets) reEncrypt(
|
|
ctx context.Context,
|
|
svc *manager.SecretsService,
|
|
encrypt func(context.Context, []byte) ([]byte, error),
|
|
sqlStore db.DB,
|
|
action string,
|
|
) bool {
|
|
var rows []struct {
|
|
Guid string
|
|
Value []byte
|
|
}
|
|
|
|
if err := sqlStore.WithDbSession(ctx, func(sess *db.Session) error {
|
|
return sess.Table("resource").Where("`group` = 'provisioning.grafana.app' AND `resource` = 'repositories'").
|
|
Select("guid, value").
|
|
Find(&rows)
|
|
}); err != nil {
|
|
logger.Warn("Could not find any provisioning secrets to re-encrypt", "error", err, "action", action)
|
|
// resource table may not exists (when not using unified storage and right db), so we don't report error here.
|
|
return true
|
|
}
|
|
|
|
var failures int
|
|
|
|
for _, row := range rows {
|
|
var resource map[string]any
|
|
if err := json.Unmarshal(row.Value, &resource); err != nil {
|
|
logger.Error("Failed to decode resource", "guid", row.Guid, "error", err, "action", action)
|
|
failures++
|
|
continue
|
|
}
|
|
|
|
err := p.reEncryptGitHubToken(ctx, svc, encrypt, sqlStore, resource)
|
|
if err != nil && !errors.Is(err, errNoEncryptedValue) {
|
|
logger.Error("Failed to rotate GitHub token", "guid", row.Guid, "error", err, "action", action)
|
|
failures++
|
|
}
|
|
update := err == nil
|
|
|
|
err = p.reEncryptWebhookSecret(ctx, svc, encrypt, sqlStore, resource)
|
|
if err != nil && !errors.Is(err, errNoEncryptedValue) {
|
|
logger.Error("Failed to rotate webhook secret", "guid", row.Guid, "error", err, "action", action)
|
|
failures++
|
|
}
|
|
update = update || err == nil
|
|
|
|
if update {
|
|
// Do it...
|
|
encoded, err := json.Marshal(resource)
|
|
if err != nil {
|
|
logger.Error("Failed to marshal resource to JSON", "guid", row.Guid, "error", err, "action", action)
|
|
failures++
|
|
continue
|
|
}
|
|
|
|
if err := sqlStore.WithDbSession(ctx, func(sess *db.Session) error {
|
|
// TODO: Figure out what resource_version is supposed to contain, and update it.
|
|
_, err = sess.Exec("UPDATE resource SET value = ? WHERE guid = ?", string(encoded), row.Guid)
|
|
return err
|
|
}); err != nil {
|
|
logger.Error("Failed to update resource with re-encrypted values", "guid", row.Guid, "error", err, "action", action)
|
|
failures++
|
|
}
|
|
}
|
|
}
|
|
|
|
if failures > 0 {
|
|
logger.Warn("Failed to rotate provisioning secrets", "failures", failures, "action", action)
|
|
} else {
|
|
logger.Info("Successfully rotated provisioning secrets", "action", action)
|
|
}
|
|
return failures == 0
|
|
}
|
|
|
|
func (provisioningSecrets) reEncryptGitHubToken(
|
|
ctx context.Context,
|
|
svc *manager.SecretsService,
|
|
encrypt func(context.Context, []byte) ([]byte, error),
|
|
sqlStore db.DB,
|
|
obj map[string]any,
|
|
) error {
|
|
spec, _ := getCast[map[string]any](obj, "spec")
|
|
github, _ := getCast[map[string]any](spec, "github")
|
|
b64Token, ok := getCast[string](github, "encryptedToken")
|
|
if !ok {
|
|
return errNoEncryptedValue
|
|
}
|
|
|
|
token, err := base64.StdEncoding.DecodeString(b64Token)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decode base64 encrypted token: %w", err)
|
|
}
|
|
|
|
decrypted, err := svc.Decrypt(ctx, token)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decrypt existing encrypted token: %w", err)
|
|
}
|
|
|
|
encrypted, err := encrypt(ctx, decrypted)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to encrypt decrypted token: %w", err)
|
|
}
|
|
|
|
b64Token = base64.StdEncoding.EncodeToString(encrypted)
|
|
github["encryptedToken"] = b64Token
|
|
return nil
|
|
}
|
|
|
|
func (provisioningSecrets) reEncryptWebhookSecret(
|
|
ctx context.Context,
|
|
svc *manager.SecretsService,
|
|
encrypt func(context.Context, []byte) ([]byte, error),
|
|
sqlStore db.DB,
|
|
obj map[string]any,
|
|
) error {
|
|
status, _ := getCast[map[string]any](obj, "status")
|
|
webhook, _ := getCast[map[string]any](status, "webhook")
|
|
b64Token, ok := getCast[string](webhook, "encryptedSecret")
|
|
if !ok {
|
|
return errNoEncryptedValue
|
|
}
|
|
|
|
token, err := base64.StdEncoding.DecodeString(b64Token)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decode base64 encrypted secret: %w", err)
|
|
}
|
|
|
|
decrypted, err := svc.Decrypt(ctx, token)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to decrypt existing encrypted secret: %w", err)
|
|
}
|
|
|
|
encrypted, err := encrypt(ctx, decrypted)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to encrypt decrypted secret: %w", err)
|
|
}
|
|
|
|
b64Token = base64.StdEncoding.EncodeToString(encrypted)
|
|
webhook["encryptedSecret"] = b64Token
|
|
return nil
|
|
}
|
|
|
|
func getCast[T any](m map[string]any, name string) (T, bool) {
|
|
if m == nil {
|
|
var zero T
|
|
return zero, false
|
|
}
|
|
anyV, ok := m[name]
|
|
if ok {
|
|
t, ok := anyV.(T)
|
|
return t, ok
|
|
}
|
|
var zero T
|
|
return zero, false
|
|
}
|