Secrets: Refactor data_key_id out of the encoded secure value payload (#111852)
* everything compiles * tests pass * remove file included by accident * add entry to gitignore * some scaffolding for the migration executor * remove file * implement and test the migration * use xkube.Namespace in our interfaces * add todo * update wire deps * add some logs * fix wire dependency ordering * create tests to validate error conditions during migrations
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
@@ -10,6 +12,7 @@ import (
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/sql"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
|
||||
)
|
||||
@@ -37,9 +40,9 @@ type encryptedValStorage struct {
|
||||
tracer trace.Tracer
|
||||
}
|
||||
|
||||
func (s *encryptedValStorage) Create(ctx context.Context, namespace, name string, version int64, encryptedData []byte) (ev *contracts.EncryptedValue, err error) {
|
||||
func (s *encryptedValStorage) Create(ctx context.Context, namespace xkube.Namespace, name string, version int64, encryptedData contracts.EncryptedPayload) (ev *contracts.EncryptedValue, err error) {
|
||||
ctx, span := s.tracer.Start(ctx, "EncryptedValueStorage.Create", trace.WithAttributes(
|
||||
attribute.String("namespace", namespace),
|
||||
attribute.String("namespace", namespace.String()),
|
||||
))
|
||||
defer span.End()
|
||||
|
||||
@@ -56,10 +59,11 @@ func (s *encryptedValStorage) Create(ctx context.Context, namespace, name string
|
||||
createdTime := time.Now().Unix()
|
||||
|
||||
encryptedValue := &EncryptedValue{
|
||||
Namespace: namespace,
|
||||
Namespace: namespace.String(),
|
||||
Name: name,
|
||||
Version: version,
|
||||
EncryptedData: encryptedData,
|
||||
EncryptedData: encryptedData.EncryptedData,
|
||||
DataKeyID: encryptedData.DataKeyID,
|
||||
Created: createdTime,
|
||||
Updated: createdTime,
|
||||
}
|
||||
@@ -88,18 +92,21 @@ func (s *encryptedValStorage) Create(ctx context.Context, namespace, name string
|
||||
}
|
||||
|
||||
return &contracts.EncryptedValue{
|
||||
Namespace: encryptedValue.Namespace,
|
||||
Name: encryptedValue.Name,
|
||||
Version: encryptedValue.Version,
|
||||
EncryptedData: encryptedValue.EncryptedData,
|
||||
Created: encryptedValue.Created,
|
||||
Updated: encryptedValue.Updated,
|
||||
Namespace: encryptedValue.Namespace,
|
||||
Name: encryptedValue.Name,
|
||||
Version: encryptedValue.Version,
|
||||
EncryptedPayload: contracts.EncryptedPayload{
|
||||
DataKeyID: encryptedValue.DataKeyID,
|
||||
EncryptedData: encryptedValue.EncryptedData,
|
||||
},
|
||||
Created: encryptedValue.Created,
|
||||
Updated: encryptedValue.Updated,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *encryptedValStorage) Update(ctx context.Context, namespace, name string, version int64, encryptedData []byte) error {
|
||||
func (s *encryptedValStorage) Update(ctx context.Context, namespace xkube.Namespace, name string, version int64, encryptedData contracts.EncryptedPayload) error {
|
||||
ctx, span := s.tracer.Start(ctx, "EncryptedValueStorage.Update", trace.WithAttributes(
|
||||
attribute.String("namespace", namespace),
|
||||
attribute.String("namespace", namespace.String()),
|
||||
attribute.String("name", name),
|
||||
attribute.Int64("version", version),
|
||||
))
|
||||
@@ -107,10 +114,11 @@ func (s *encryptedValStorage) Update(ctx context.Context, namespace, name string
|
||||
|
||||
req := updateEncryptedValue{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: namespace,
|
||||
Namespace: namespace.String(),
|
||||
Name: name,
|
||||
Version: version,
|
||||
EncryptedData: encryptedData,
|
||||
EncryptedData: encryptedData.EncryptedData,
|
||||
DataKeyID: encryptedData.DataKeyID,
|
||||
Updated: time.Now().Unix(),
|
||||
}
|
||||
|
||||
@@ -133,9 +141,9 @@ func (s *encryptedValStorage) Update(ctx context.Context, namespace, name string
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *encryptedValStorage) Get(ctx context.Context, namespace, name string, version int64) (*contracts.EncryptedValue, error) {
|
||||
func (s *encryptedValStorage) Get(ctx context.Context, namespace xkube.Namespace, name string, version int64) (*contracts.EncryptedValue, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "EncryptedValueStorage.Get", trace.WithAttributes(
|
||||
attribute.String("namespace", namespace),
|
||||
attribute.String("namespace", namespace.String()),
|
||||
attribute.String("name", name),
|
||||
attribute.Int64("version", version),
|
||||
))
|
||||
@@ -143,7 +151,7 @@ func (s *encryptedValStorage) Get(ctx context.Context, namespace, name string, v
|
||||
|
||||
req := &readEncryptedValue{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: namespace,
|
||||
Namespace: namespace.String(),
|
||||
Name: name,
|
||||
Version: version,
|
||||
}
|
||||
@@ -163,7 +171,7 @@ func (s *encryptedValStorage) Get(ctx context.Context, namespace, name string, v
|
||||
}
|
||||
|
||||
var encryptedValue EncryptedValue
|
||||
err = rows.Scan(&encryptedValue.Namespace, &encryptedValue.Name, &encryptedValue.Version, &encryptedValue.EncryptedData, &encryptedValue.Created, &encryptedValue.Updated)
|
||||
err = rows.Scan(&encryptedValue.Namespace, &encryptedValue.Name, &encryptedValue.Version, &encryptedValue.EncryptedData, &encryptedValue.DataKeyID, &encryptedValue.Created, &encryptedValue.Updated)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan encrypted value row: %w", err)
|
||||
}
|
||||
@@ -172,18 +180,21 @@ func (s *encryptedValStorage) Get(ctx context.Context, namespace, name string, v
|
||||
}
|
||||
|
||||
return &contracts.EncryptedValue{
|
||||
Namespace: encryptedValue.Namespace,
|
||||
Name: encryptedValue.Name,
|
||||
Version: encryptedValue.Version,
|
||||
EncryptedData: encryptedValue.EncryptedData,
|
||||
Created: encryptedValue.Created,
|
||||
Updated: encryptedValue.Updated,
|
||||
Namespace: encryptedValue.Namespace,
|
||||
Name: encryptedValue.Name,
|
||||
Version: encryptedValue.Version,
|
||||
EncryptedPayload: contracts.EncryptedPayload{
|
||||
DataKeyID: encryptedValue.DataKeyID,
|
||||
EncryptedData: encryptedValue.EncryptedData,
|
||||
},
|
||||
Created: encryptedValue.Created,
|
||||
Updated: encryptedValue.Updated,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *encryptedValStorage) Delete(ctx context.Context, namespace, name string, version int64) error {
|
||||
func (s *encryptedValStorage) Delete(ctx context.Context, namespace xkube.Namespace, name string, version int64) error {
|
||||
ctx, span := s.tracer.Start(ctx, "EncryptedValueStorage.Delete", trace.WithAttributes(
|
||||
attribute.String("namespace", namespace),
|
||||
attribute.String("namespace", namespace.String()),
|
||||
attribute.String("name", name),
|
||||
attribute.Int64("version", version),
|
||||
))
|
||||
@@ -191,7 +202,7 @@ func (s *encryptedValStorage) Delete(ctx context.Context, namespace, name string
|
||||
|
||||
req := deleteEncryptedValue{
|
||||
SQLTemplate: sqltemplate.New(s.dialect),
|
||||
Namespace: namespace,
|
||||
Namespace: namespace.String(),
|
||||
Name: name,
|
||||
Version: version,
|
||||
}
|
||||
@@ -264,6 +275,7 @@ func (s *globalEncryptedValStorage) ListAll(ctx context.Context, opts contracts.
|
||||
&row.Name,
|
||||
&row.Version,
|
||||
&row.EncryptedData,
|
||||
&row.DataKeyID,
|
||||
&row.Created,
|
||||
&row.Updated,
|
||||
)
|
||||
@@ -272,12 +284,15 @@ func (s *globalEncryptedValStorage) ListAll(ctx context.Context, opts contracts.
|
||||
}
|
||||
|
||||
encryptedValues = append(encryptedValues, &contracts.EncryptedValue{
|
||||
Namespace: row.Namespace,
|
||||
Name: row.Name,
|
||||
Version: row.Version,
|
||||
EncryptedData: row.EncryptedData,
|
||||
Created: row.Created,
|
||||
Updated: row.Updated,
|
||||
Namespace: row.Namespace,
|
||||
Name: row.Name,
|
||||
Version: row.Version,
|
||||
EncryptedPayload: contracts.EncryptedPayload{
|
||||
DataKeyID: row.DataKeyID,
|
||||
EncryptedData: row.EncryptedData,
|
||||
},
|
||||
Created: row.Created,
|
||||
Updated: row.Updated,
|
||||
})
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
@@ -329,3 +344,77 @@ func (s *globalEncryptedValStorage) CountAll(ctx context.Context, untilTime *int
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
type encryptedValMigrationExecutor struct {
|
||||
db contracts.Database
|
||||
dialect sqltemplate.Dialect
|
||||
tracer trace.Tracer
|
||||
encryptedValueStore contracts.EncryptedValueStorage
|
||||
globalStore contracts.GlobalEncryptedValueStorage
|
||||
}
|
||||
|
||||
func ProvideEncryptedValueMigrationExecutor(
|
||||
db contracts.Database,
|
||||
tracer trace.Tracer,
|
||||
encryptedValueStore contracts.EncryptedValueStorage,
|
||||
globalStore contracts.GlobalEncryptedValueStorage,
|
||||
) (contracts.EncryptedValueMigrationExecutor, error) {
|
||||
return &encryptedValMigrationExecutor{
|
||||
db: db,
|
||||
dialect: sqltemplate.DialectForDriver(db.DriverName()),
|
||||
tracer: tracer,
|
||||
encryptedValueStore: encryptedValueStore,
|
||||
globalStore: globalStore,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *encryptedValMigrationExecutor) Execute(ctx context.Context) (int, error) {
|
||||
ctx, span := s.tracer.Start(ctx, "EncryptedValueMigrationExecutor.Execute")
|
||||
defer span.End()
|
||||
|
||||
// 1. Retrieve all encrypted values
|
||||
encryptedValues, err := s.globalStore.ListAll(ctx, contracts.ListOpts{}, nil)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("listing all encrypted values: %w", err)
|
||||
}
|
||||
|
||||
// This doesn't need to be done in a single transaction because there's no risk to successful rows if other rows fail
|
||||
rowsAffected := 0
|
||||
for _, encryptedValue := range encryptedValues {
|
||||
// 2. If the value already has the data key id broken out, skip it
|
||||
if encryptedValue.DataKeyID != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// 3. Split the data key id and the encrypted data out from the encoded payload
|
||||
payload := encryptedValue.EncryptedData
|
||||
const keyIdDelimiter = '#'
|
||||
payload = payload[1:]
|
||||
endOfKey := bytes.Index(payload, []byte{keyIdDelimiter})
|
||||
if endOfKey == -1 {
|
||||
return rowsAffected, fmt.Errorf("could not find valid key id in encrypted payload with namespace %s and name %s and version %d", encryptedValue.Namespace, encryptedValue.Name, encryptedValue.Version)
|
||||
}
|
||||
b64Key := payload[:endOfKey]
|
||||
encryptedData := payload[endOfKey+1:]
|
||||
if len(encryptedData) == 0 {
|
||||
return rowsAffected, fmt.Errorf("encrypted data is empty with namespace %s and name %s and version %d", encryptedValue.Namespace, encryptedValue.Name, encryptedValue.Version)
|
||||
}
|
||||
keyId := make([]byte, base64.RawStdEncoding.DecodedLen(len(b64Key)))
|
||||
_, err := base64.RawStdEncoding.Decode(keyId, b64Key)
|
||||
if err != nil {
|
||||
return rowsAffected, fmt.Errorf("decoding key id with namespace %s and name %s and version %d: %w", encryptedValue.Namespace, encryptedValue.Name, encryptedValue.Version, err)
|
||||
}
|
||||
|
||||
// 4. Update the encrypted value with the data key id and the encrypted data
|
||||
err = s.encryptedValueStore.Update(ctx, xkube.Namespace(encryptedValue.Namespace), encryptedValue.Name, encryptedValue.Version, contracts.EncryptedPayload{
|
||||
DataKeyID: string(keyId),
|
||||
EncryptedData: encryptedData,
|
||||
})
|
||||
if err != nil {
|
||||
return rowsAffected, fmt.Errorf("updating encrypted value with namespace %s and name %s and version %d: %w", encryptedValue.Namespace, encryptedValue.Name, encryptedValue.Version, err)
|
||||
}
|
||||
rowsAffected++
|
||||
}
|
||||
|
||||
return rowsAffected, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user