Skip to content

Commit 149ffb2

Browse files
committed
feat(backup_service): support backup encryption
1 parent ebd702c commit 149ffb2

File tree

16 files changed

+256
-84
lines changed

16 files changed

+256
-84
lines changed

cmd/ydbcp/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ func main() {
210210
xlog.Info(ctx, "Created TtlWatcher")
211211
}
212212

213-
backupScheduleHandler := handlers.NewBackupScheduleHandler(queries.NewWriteTableQuery, clockwork.NewRealClock())
213+
backupScheduleHandler := handlers.NewBackupScheduleHandler(queries.NewWriteTableQuery, clockwork.NewRealClock(), configInstance.FeatureFlags)
214214

215215
schedule_watcher.NewScheduleWatcher(
216216
ctx, &wg, configInstance.GetProcessorIntervalSeconds(), dbConnector,

internal/backup_operations/make_backup.go

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package backup_operations
22

33
import (
44
"context"
5+
"crypto/rand"
56
"errors"
67
"fmt"
78
"github.com/jonboulle/clockwork"
@@ -35,6 +36,7 @@ type MakeBackupInternalRequest struct {
3536
ScheduleID *string
3637
Ttl *time.Duration
3738
ParentOperationID *string
39+
EncryptionSettings *pb.EncryptionSettings
3840
}
3941

4042
func FromBackupSchedule(schedule *types.BackupSchedule) MakeBackupInternalRequest {
@@ -62,6 +64,7 @@ func FromTBWROperation(tbwr *types.TakeBackupWithRetryOperation) MakeBackupInter
6264
ScheduleID: tbwr.ScheduleID,
6365
Ttl: tbwr.Ttl,
6466
ParentOperationID: &tbwr.ID,
67+
EncryptionSettings: tbwr.EncryptionSettings,
6568
}
6669
}
6770

@@ -274,6 +277,34 @@ func IsEmptyBackup(backup *types.Backup) bool {
274277
return backup.Size == 0 && backup.S3Endpoint == ""
275278
}
276279

280+
func GetEncryptionParams(settings *pb.EncryptionSettings) ([]byte, string, error) {
281+
var algorithm string
282+
var length int
283+
284+
switch settings.Algorithm {
285+
case pb.EncryptionSettings_UNSPECIFIED:
286+
case pb.EncryptionSettings_AES_128_GCM:
287+
algorithm = "AES-128-GCM"
288+
length = 16
289+
break
290+
case pb.EncryptionSettings_AES_256_GCM:
291+
algorithm = "AES-256-GCM"
292+
length = 32
293+
break
294+
case pb.EncryptionSettings_CHACHA20_POLY1305:
295+
algorithm = "ChaCha20-Poly1305"
296+
length = 32
297+
break
298+
}
299+
300+
dek := make([]byte, length)
301+
_, err := rand.Read(dek)
302+
if err != nil {
303+
return nil, "", err
304+
}
305+
return dek, algorithm, nil
306+
}
307+
277308
func MakeBackup(
278309
ctx context.Context,
279310
clientConn client.ClientConnector,
@@ -350,6 +381,18 @@ func MakeBackup(
350381
S3ForcePathStyle: s3.S3ForcePathStyle,
351382
}
352383

384+
if req.EncryptionSettings != nil && featureFlags.EnableBackupEncryption {
385+
dek, algorithm, err := GetEncryptionParams(req.EncryptionSettings)
386+
if err != nil {
387+
return nil, nil, err
388+
}
389+
390+
s3Settings.EncryptionKey = dek
391+
s3Settings.EncryptionAlgorithm = algorithm
392+
// TODO: encrypt the DEK using the specified KEK
393+
// TODO: stores the encrypted DEK in S3
394+
}
395+
353396
clientOperationID, err := clientConn.ExportToS3(ctx, client, s3Settings, featureFlags)
354397
if err != nil {
355398
xlog.Error(ctx, "can't start export operation", zap.Error(err))
@@ -379,9 +422,10 @@ func MakeBackup(
379422
CreatedAt: now,
380423
Creator: subject,
381424
},
382-
ScheduleID: req.ScheduleID,
383-
ExpireAt: expireAt,
384-
SourcePaths: pathsForExport,
425+
ScheduleID: req.ScheduleID,
426+
ExpireAt: expireAt,
427+
SourcePaths: pathsForExport,
428+
EncryptionSettings: req.EncryptionSettings,
385429
}
386430

387431
op := &types.TakeBackupOperation{
@@ -399,9 +443,10 @@ func MakeBackup(
399443
CreatedAt: now,
400444
Creator: subject,
401445
},
402-
YdbOperationId: clientOperationID,
403-
UpdatedAt: now,
404-
ParentOperationID: req.ParentOperationID,
446+
YdbOperationId: clientOperationID,
447+
UpdatedAt: now,
448+
ParentOperationID: req.ParentOperationID,
449+
EncryptionSettings: req.EncryptionSettings,
405450
}
406451

407452
return backup, op, nil

internal/config/config.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,9 @@ type MetricsServerConfig struct {
6666
}
6767

6868
type FeatureFlagsConfig struct {
69-
DisableTTLDeletion bool `yaml:"disable_ttl_deletion" default:"false"`
70-
EnableNewPathsFormat bool `yaml:"enable_new_paths_format" default:"false"`
69+
DisableTTLDeletion bool `yaml:"disable_ttl_deletion" default:"false"`
70+
EnableNewPathsFormat bool `yaml:"enable_new_paths_format" default:"false"`
71+
EnableBackupEncryption bool `yaml:"enable_backup_encryption" default:"false"`
7172
}
7273

7374
type LogConfig struct {

internal/connectors/client/connector.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,17 @@ func (d *ClientYdbConnector) ExportToS3(
272272
exportRequest.Settings.DestinationPrefix = s3Settings.DestinationPrefix
273273
}
274274

275+
if featureFlags.EnableBackupEncryption && len(s3Settings.EncryptionKey) > 0 {
276+
exportRequest.Settings.EncryptionSettings = &Ydb_Export.EncryptionSettings{
277+
EncryptionAlgorithm: s3Settings.EncryptionAlgorithm,
278+
Key: &Ydb_Export.EncryptionSettings_SymmetricKey_{
279+
SymmetricKey: &Ydb_Export.EncryptionSettings_SymmetricKey{
280+
Key: s3Settings.EncryptionKey,
281+
},
282+
},
283+
}
284+
}
285+
275286
response, err := exportClient.ExportToS3(ctx, exportRequest)
276287

277288
if err != nil {
@@ -425,6 +436,17 @@ func (d *ClientYdbConnector) ImportFromS3(
425436
importRequest.Settings.DestinationPath = path.Join(clientDb.Name(), s3Settings.DestinationPrefix)
426437
}
427438

439+
if len(s3Settings.EncryptionKey) > 0 {
440+
importRequest.Settings.EncryptionSettings = &Ydb_Export.EncryptionSettings{
441+
EncryptionAlgorithm: s3Settings.EncryptionAlgorithm,
442+
Key: &Ydb_Export.EncryptionSettings_SymmetricKey_{
443+
SymmetricKey: &Ydb_Export.EncryptionSettings_SymmetricKey{
444+
Key: s3Settings.EncryptionKey,
445+
},
446+
},
447+
}
448+
}
449+
428450
response, err := importClient.ImportFromS3(ctx, importRequest)
429451

430452
if err != nil {

internal/connectors/db/yql/queries/write.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,16 @@ func BuildCreateOperationQuery(operation types.Operation, index int) WriteSingle
129129
if tb.ParentOperationID != nil {
130130
d.AddValueParam("$parent_operation_id", table_types.StringValueFromString(*tb.ParentOperationID))
131131
}
132+
if tb.EncryptionSettings != nil {
133+
d.AddValueParam(
134+
"$encryption_algorithm",
135+
table_types.StringValueFromString(tb.EncryptionSettings.GetAlgorithm().String()),
136+
)
137+
d.AddValueParam(
138+
"$kms_key_id",
139+
table_types.StringValueFromString(tb.EncryptionSettings.GetKmsKey().GetKeyId()),
140+
)
141+
}
132142
} else if operation.GetType() == types.OperationTypeTBWR {
133143
tbwr, ok := operation.(*types.TakeBackupWithRetryOperation)
134144
if !ok {
@@ -178,6 +188,16 @@ func BuildCreateOperationQuery(operation types.Operation, index int) WriteSingle
178188
if tbwr.Ttl != nil {
179189
d.AddValueParam("$ttl", table_types.IntervalValueFromDuration(*tbwr.Ttl))
180190
}
191+
if tbwr.EncryptionSettings != nil {
192+
d.AddValueParam(
193+
"$encryption_algorithm",
194+
table_types.StringValueFromString(tbwr.EncryptionSettings.GetAlgorithm().String()),
195+
)
196+
d.AddValueParam(
197+
"$kms_key_id",
198+
table_types.StringValueFromString(tbwr.EncryptionSettings.GetKmsKey().GetKeyId()),
199+
)
200+
}
181201
} else if operation.GetType() == types.OperationTypeRB {
182202
rb, ok := operation.(*types.RestoreBackupOperation)
183203
if !ok {
@@ -330,6 +350,17 @@ func BuildCreateBackupQuery(b types.Backup, index int) WriteSingleTableQueryImpl
330350
if b.ExpireAt != nil {
331351
d.AddValueParam("$expire_at", table_types.TimestampValueFromTime(*b.ExpireAt))
332352
}
353+
354+
if b.EncryptionSettings != nil {
355+
d.AddValueParam(
356+
"$encryption_algorithm",
357+
table_types.StringValueFromString(b.EncryptionSettings.GetAlgorithm().String()),
358+
)
359+
d.AddValueParam(
360+
"$kms_key_id",
361+
table_types.StringValueFromString(b.EncryptionSettings.GetKmsKey().GetKeyId()),
362+
)
363+
}
333364
return d
334365
}
335366

@@ -380,6 +411,17 @@ func BuildCreateBackupScheduleQuery(schedule types.BackupSchedule, index int) Wr
380411
"$recovery_point_objective",
381412
table_types.IntervalValueFromDuration(schedule.ScheduleSettings.RecoveryPointObjective.AsDuration()),
382413
)
414+
415+
if schedule.ScheduleSettings.EncryptionSettings != nil {
416+
d.AddValueParam(
417+
"$encryption_algorithm",
418+
table_types.StringValueFromString(schedule.ScheduleSettings.EncryptionSettings.GetAlgorithm().String()),
419+
)
420+
d.AddValueParam(
421+
"$kms_key_id",
422+
table_types.StringValueFromString(schedule.ScheduleSettings.EncryptionSettings.GetKmsKey().GetKeyId()),
423+
)
424+
}
383425
}
384426
if schedule.NextLaunch != nil {
385427
d.AddValueParam("$next_launch", table_types.TimestampValueFromTime(*schedule.NextLaunch))

internal/connectors/db/yql/schema/create_tables.yql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ CREATE TABLE Backups (
2222

2323
schedule_id String,
2424

25+
encryption_algorithm String,
26+
kms_key_id String,
27+
2528
INDEX idx_container_id GLOBAL ON (container_id),
2629
INDEX idx_created_at GLOBAL ON (created_at),
2730
INDEX idx_expire_at GLOBAL ON (status, expire_at),
@@ -55,6 +58,8 @@ CREATE TABLE Operations (
5558
paths_to_exclude String,
5659
operation_id String,
5760
parent_operation_id String,
61+
encryption_algorithm String,
62+
kms_key_id String,
5863
--used only in TBWR
5964
schedule_id String,
6065
ttl Interval,
@@ -85,6 +90,9 @@ CREATE TABLE BackupSchedules (
8590
initiated String,
8691
created_at Timestamp,
8792

93+
encryption_algorithm String,
94+
kms_key_id String,
95+
8896
recovery_point_objective Interval,
8997

9098
next_launch Timestamp,

internal/handlers/schedule_backup.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"google.golang.org/protobuf/types/known/durationpb"
99
"google.golang.org/protobuf/types/known/timestamppb"
1010
"ydbcp/internal/audit"
11+
"ydbcp/internal/config"
1112
"ydbcp/internal/connectors/db"
1213
"ydbcp/internal/connectors/db/yql/queries"
1314
"ydbcp/internal/types"
@@ -20,11 +21,13 @@ type BackupScheduleHandlerType func(context.Context, db.DBConnector, *types.Back
2021
func NewBackupScheduleHandler(
2122
queryBuilderFactory queries.WriteQueryBuilderFactory,
2223
clock clockwork.Clock,
24+
featureFlags config.FeatureFlagsConfig,
2325
) BackupScheduleHandlerType {
2426
return func(ctx context.Context, driver db.DBConnector, schedule *types.BackupSchedule) error {
2527
return BackupScheduleHandler(
2628
ctx, driver, schedule,
2729
queryBuilderFactory, clock,
30+
featureFlags,
2831
)
2932
}
3033
}
@@ -46,6 +49,7 @@ func BackupScheduleHandler(
4649
schedule *types.BackupSchedule,
4750
queryBuilderFactory queries.WriteQueryBuilderFactory,
4851
clock clockwork.Clock,
52+
featureFlags config.FeatureFlagsConfig,
4953
) error {
5054
if schedule.Status != types.BackupScheduleStateActive {
5155
xlog.Error(ctx, "backup schedule is not active", zap.String("ScheduleID", schedule.ID))
@@ -85,6 +89,10 @@ func BackupScheduleHandler(
8589
d := schedule.ScheduleSettings.Ttl.AsDuration()
8690
tbwr.Ttl = &d
8791
}
92+
93+
if schedule.ScheduleSettings.EncryptionSettings != nil && featureFlags.EnableBackupEncryption {
94+
tbwr.EncryptionSettings = schedule.ScheduleSettings.EncryptionSettings
95+
}
8896
}
8997

9098
xlog.Info(

internal/handlers/schedule_backup_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/stretchr/testify/assert"
77
"testing"
88
"time"
9+
"ydbcp/internal/config"
910
"ydbcp/internal/connectors/db"
1011
"ydbcp/internal/connectors/db/yql/queries"
1112
"ydbcp/internal/types"
@@ -40,7 +41,7 @@ func TestBackupScheduleHandler(t *testing.T) {
4041
)
4142

4243
handler := NewBackupScheduleHandler(
43-
queries.NewWriteTableQueryMock, clock,
44+
queries.NewWriteTableQueryMock, clock, config.FeatureFlagsConfig{},
4445
)
4546
err := handler(ctx, dbConnector, &schedule)
4647
assert.Empty(t, err)

internal/server/services/backup/backup_service.go

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package backup
33
import (
44
"context"
55
"github.com/jonboulle/clockwork"
6+
"google.golang.org/protobuf/proto"
67
"strconv"
78
"time"
89
"ydbcp/internal/audit"
@@ -110,9 +111,13 @@ func (s *BackupService) MakeBackup(ctx context.Context, req *pb.MakeBackupReques
110111
ctx = xlog.With(ctx, zap.String("SubjectID", subject))
111112
now := timestamppb.Now()
112113

114+
var encryptionSettings *pb.EncryptionSettings
113115
if req.EncryptionSettings != nil {
114-
s.IncApiCallsCounter(methodName, codes.Unimplemented)
115-
return nil, status.Error(codes.Unimplemented, "backup encryption is not supported yet")
116+
if !s.featureFlags.EnableBackupEncryption {
117+
s.IncApiCallsCounter(methodName, codes.Unimplemented)
118+
return nil, status.Error(codes.Unimplemented, "backup encryption is not supported yet")
119+
}
120+
encryptionSettings = proto.Clone(req.EncryptionSettings).(*pb.EncryptionSettings)
116121
}
117122

118123
tbwr := &types.TakeBackupWithRetryOperation{
@@ -130,7 +135,8 @@ func (s *BackupService) MakeBackup(ctx context.Context, req *pb.MakeBackupReques
130135
Creator: subject,
131136
CreatedAt: now,
132137
},
133-
UpdatedAt: now,
138+
UpdatedAt: now,
139+
EncryptionSettings: encryptionSettings,
134140
},
135141
Retries: 0,
136142
RetryConfig: &pb.RetryConfig{
@@ -383,6 +389,14 @@ func (s *BackupService) MakeRestore(ctx context.Context, req *pb.MakeRestoreRequ
383389
DestinationPrefix: req.GetDestinationPrefix(),
384390
}
385391

392+
if backup.EncryptionSettings != nil {
393+
// TODO: read encrypted DEK from s3
394+
// TODO: decrypt DEC via KMS
395+
// TODO: set EncryptionKey
396+
_, algorithm, _ := backup_operations.GetEncryptionParams(backup.EncryptionSettings)
397+
s3Settings.EncryptionAlgorithm = algorithm
398+
}
399+
386400
clientOperationID, err := s.clientConn.ImportFromS3(ctx, clientDriver, s3Settings, s.featureFlags)
387401
if err != nil {
388402
xlog.Error(ctx, "can't start import operation", zap.Error(err))

internal/server/services/backup_schedule/backup_schedule_service.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ func (s *BackupScheduleService) CreateBackupSchedule(
119119
return nil, status.Error(codes.FailedPrecondition, "no backup schedule settings for CreateBackupSchedule")
120120
}
121121

122-
if request.ScheduleSettings.EncryptionSettings != nil {
122+
if !s.config.FeatureFlags.EnableBackupEncryption && request.ScheduleSettings.EncryptionSettings != nil {
123123
s.IncApiCallsCounter(methodName, codes.Unimplemented)
124124
return nil, status.Error(codes.Unimplemented, "backup encryption is not supported yet")
125125
}

0 commit comments

Comments
 (0)