Skip to content

Commit cd20e8c

Browse files
committed
feat(backup_service): support backup encryption
1 parent fef87ae commit cd20e8c

File tree

24 files changed

+432
-97
lines changed

24 files changed

+432
-97
lines changed

.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ S3_BUCKET = "test-bucket"
55
S3_REGION = "us-east-1"
66
YDB_NAME = "local-ydb"
77
ENABLE_NEW_PATHS_FORMAT = true
8+
ENABLE_BACKUPS_ENCRYPTION = true
89
# local-ydb image that was built from main
910
# Image: https://github.com/ydb-platform/ydb/pkgs/container/local-ydb/551703770
1011
# Built from revision 07aeccc41b43c9fc0b7da7680340fbac01b81427

.github/workflows/unit-test.yml

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,16 @@ jobs:
5454
runs-on: ubuntu-latest
5555
strategy:
5656
matrix:
57-
enable_new_paths_format: [ true, false ]
57+
include:
58+
- enable_new_paths_format: false
59+
enable_backups_encryption: false
60+
- enable_new_paths_format: true
61+
enable_backups_encryption: false
62+
- enable_new_paths_format: true
63+
enable_backups_encryption: true
5864
env:
5965
ENABLE_NEW_PATHS_FORMAT: ${{ matrix.enable_new_paths_format }}
66+
ENABLE_BACKUPS_ENCRYPTION: ${{ matrix.enable_backups_encryption }}
6067
steps:
6168
- uses: actions/checkout@v4
6269
- name: supply with s3 access keys
@@ -110,3 +117,20 @@ jobs:
110117
if: ${{ matrix.enable_new_paths_format }}
111118
run: |
112119
docker compose down
120+
- name: docker compose up
121+
if: ${{ matrix.enable_backups_encryption }}
122+
run: |
123+
docker compose up -d
124+
- name: run encrypted_backups tests
125+
if: ${{ matrix.enable_backups_encryption }}
126+
run: |
127+
while [ "$(docker inspect -f {{.State.Health.Status}} local-ydbcp)" != "healthy" ]; do
128+
echo "Waiting for container to become healthy..."
129+
sleep 1
130+
done
131+
echo "Starting encrypted_backups tests!"
132+
docker exec local-ydbcp sh -c './test_encrypted_backups'
133+
- name: docker compose down
134+
if: ${{ matrix.enable_backups_encryption }}
135+
run: |
136+
docker compose down

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ RUN go build -o ./make_backup ./cmd/integration/make_backup/main.go
2121
RUN go build -o ./list_entities ./cmd/integration/list_entities/main.go
2222
RUN go build -o ./orm ./cmd/integration/orm/main.go
2323
RUN go build -o ./test_new_paths_format ./cmd/integration/new_paths_format/main.go
24+
RUN go build -o ./test_encrypted_backups ./cmd/integration/encrypted_backups/main.go
2425

2526
# Command to run the executable
2627
CMD ["./main", "--config=local_config.yaml"]

cmd/ydbcp/main.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"flag"
66
"fmt"
7-
"github.com/ydb-platform/ydb-go-sdk/v3/log"
87
"net/http"
98
_ "net/http/pprof"
109
"os"
@@ -19,6 +18,7 @@ import (
1918
"ydbcp/internal/connectors/db/yql/queries"
2019
"ydbcp/internal/connectors/s3"
2120
"ydbcp/internal/handlers"
21+
"ydbcp/internal/kms"
2222
"ydbcp/internal/metrics"
2323
"ydbcp/internal/processor"
2424
"ydbcp/internal/server"
@@ -32,6 +32,9 @@ import (
3232
"ydbcp/internal/watchers/schedule_watcher"
3333
"ydbcp/internal/watchers/ttl_watcher"
3434
ap "ydbcp/pkg/plugins/auth"
35+
kp "ydbcp/pkg/plugins/kms"
36+
37+
"github.com/ydb-platform/ydb-go-sdk/v3/log"
3538

3639
"github.com/jonboulle/clockwork"
3740

@@ -114,6 +117,24 @@ func main() {
114117
}
115118
}()
116119
xlog.Info(ctx, "Initialized AuthProvider")
120+
121+
var kmsProvider kp.KmsProvider
122+
if len(configInstance.KMS.PluginPath) == 0 {
123+
kmsProvider, err = kms.NewDummyKmsProvider(ctx)
124+
} else {
125+
kmsProvider, err = kms.NewKmsProvider(ctx, configInstance.KMS)
126+
}
127+
if err != nil {
128+
xlog.Error(ctx, "Error init KmsProvider", zap.Error(err))
129+
os.Exit(1)
130+
}
131+
defer func() {
132+
if err := kmsProvider.Close(ctx); err != nil {
133+
xlog.Error(ctx, "Error close kms provider", zap.Error(err))
134+
}
135+
}()
136+
xlog.Info(ctx, "Initialized KmsProvider")
137+
117138
metrics.InitializeMetricsRegistry(ctx, &wg, &configInstance.MetricsServer, clockwork.NewRealClock())
118139
xlog.Info(ctx, "Initialized metrics registry")
119140
audit.EventsDestination = configInstance.Audit.EventsDestination
@@ -144,6 +165,7 @@ func main() {
144165
dbConnector,
145166
clientConnector,
146167
authProvider,
168+
kmsProvider,
147169
*configInstance,
148170
).Register(server)
149171
operation.NewOperationService(dbConnector, authProvider).Register(server)
@@ -192,6 +214,7 @@ func main() {
192214
queries.NewWriteTableQuery,
193215
clockwork.NewRealClock(),
194216
*configInstance,
217+
kmsProvider,
195218
),
196219
); err != nil {
197220
xlog.Error(ctx, "failed to register TBWR handler", zap.Error(err))
@@ -210,7 +233,7 @@ func main() {
210233
xlog.Info(ctx, "Created TtlWatcher")
211234
}
212235

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

215238
schedule_watcher.NewScheduleWatcher(
216239
ctx, &wg, configInstance.OperationProcessor.ProcessorIntervalSeconds, dbConnector,

docker-compose.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ services:
109109
S3_SECRET_KEY: ${S3_SECRET_KEY}
110110
YDB_NAME: ${YDB_NAME}
111111
ENABLE_NEW_PATHS_FORMAT: ${ENABLE_NEW_PATHS_FORMAT}
112+
ENABLE_BACKUPS_ENCRYPTION: ${ENABLE_BACKUPS_ENCRYPTION}
112113
depends_on:
113114
setup_ydb:
114115
condition: service_completed_successfully

internal/backup_operations/make_backup.go

Lines changed: 72 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ package backup_operations
22

33
import (
44
"context"
5+
"crypto/rand"
56
"errors"
67
"fmt"
7-
"github.com/jonboulle/clockwork"
8-
"github.com/ydb-platform/ydb-go-sdk/v3"
98
"path"
109
"regexp"
1110
"strings"
@@ -14,8 +13,12 @@ import (
1413
"ydbcp/internal/connectors/client"
1514
"ydbcp/internal/types"
1615
"ydbcp/internal/util/xlog"
16+
kp "ydbcp/pkg/plugins/kms"
1717
pb "ydbcp/pkg/proto/ydbcp/v1alpha1"
1818

19+
"github.com/jonboulle/clockwork"
20+
"github.com/ydb-platform/ydb-go-sdk/v3"
21+
1922
"go.uber.org/zap"
2023
"google.golang.org/grpc/codes"
2124
"google.golang.org/grpc/status"
@@ -36,6 +39,7 @@ type MakeBackupInternalRequest struct {
3639
ScheduleID *string
3740
Ttl *time.Duration
3841
ParentOperationID *string
42+
EncryptionSettings *pb.EncryptionSettings
3943
}
4044

4145
func FromBackupSchedule(schedule *types.BackupSchedule) MakeBackupInternalRequest {
@@ -65,6 +69,7 @@ func FromTBWROperation(tbwr *types.TakeBackupWithRetryOperation) MakeBackupInter
6569
ScheduleID: tbwr.ScheduleID,
6670
Ttl: tbwr.Ttl,
6771
ParentOperationID: &tbwr.ID,
72+
EncryptionSettings: tbwr.EncryptionSettings,
6873
}
6974
}
7075

@@ -282,6 +287,31 @@ func IsEmptyBackup(backup *types.Backup) bool {
282287
return backup.Size == 0 && backup.S3Endpoint == ""
283288
}
284289

290+
func GetEncryptionParams(settings *pb.EncryptionSettings) ([]byte, string, error) {
291+
var algorithm string
292+
var length int
293+
294+
switch settings.Algorithm {
295+
case pb.EncryptionSettings_UNSPECIFIED:
296+
case pb.EncryptionSettings_AES_128_GCM:
297+
algorithm = "AES-128-GCM"
298+
length = 16
299+
case pb.EncryptionSettings_AES_256_GCM:
300+
algorithm = "AES-256-GCM"
301+
length = 32
302+
case pb.EncryptionSettings_CHACHA20_POLY1305:
303+
algorithm = "ChaCha20-Poly1305"
304+
length = 32
305+
}
306+
307+
dek := make([]byte, length)
308+
_, err := rand.Read(dek)
309+
if err != nil {
310+
return nil, "", err
311+
}
312+
return dek, algorithm, nil
313+
}
314+
285315
func MakeBackup(
286316
ctx context.Context,
287317
clientConn client.ClientConnector,
@@ -292,6 +322,7 @@ func MakeBackup(
292322
subject string,
293323
clock clockwork.Clock,
294324
featureFlags config.FeatureFlagsConfig,
325+
kmsProvider kp.KmsProvider,
295326
) (*types.Backup, *types.TakeBackupOperation, error) {
296327
if req.ScheduleID != nil {
297328
ctx = xlog.With(ctx, zap.String("ScheduleID", *req.ScheduleID))
@@ -359,6 +390,37 @@ func MakeBackup(
359390
S3ForcePathStyle: s3.S3ForcePathStyle,
360391
}
361392

393+
if req.EncryptionSettings != nil && featureFlags.EnableBackupEncryption {
394+
dek, algorithm, err := GetEncryptionParams(req.EncryptionSettings)
395+
if err != nil {
396+
return nil, nil, err
397+
}
398+
399+
s3Settings.EncryptionKey = dek
400+
s3Settings.EncryptionAlgorithm = algorithm
401+
402+
kmsKey := req.EncryptionSettings.GetKmsKey()
403+
if kmsKey == nil {
404+
xlog.Error(ctx, "kms key is not specified")
405+
return nil, nil, status.Errorf(codes.InvalidArgument, "kms key is not specified")
406+
}
407+
408+
_, err = kmsProvider.Encrypt(
409+
ctx,
410+
&kp.EncryptRequest{
411+
KeyID: kmsKey.GetKeyId(),
412+
Plaintext: dek,
413+
},
414+
)
415+
416+
if err != nil {
417+
xlog.Error(ctx, "can't encrypt data encryption key", zap.Error(err))
418+
return nil, nil, err
419+
}
420+
421+
// TODO: save encrypted key to s3
422+
}
423+
362424
clientOperationID, err := clientConn.ExportToS3(ctx, client, s3Settings, featureFlags)
363425
if err != nil {
364426
xlog.Error(ctx, "can't start export operation", zap.Error(err))
@@ -388,9 +450,10 @@ func MakeBackup(
388450
CreatedAt: now,
389451
Creator: subject,
390452
},
391-
ScheduleID: req.ScheduleID,
392-
ExpireAt: expireAt,
393-
SourcePaths: pathsForExport,
453+
ScheduleID: req.ScheduleID,
454+
ExpireAt: expireAt,
455+
SourcePaths: pathsForExport,
456+
EncryptionSettings: req.EncryptionSettings,
394457
}
395458

396459
op := &types.TakeBackupOperation{
@@ -409,9 +472,10 @@ func MakeBackup(
409472
CreatedAt: now,
410473
Creator: subject,
411474
},
412-
YdbOperationId: clientOperationID,
413-
UpdatedAt: now,
414-
ParentOperationID: req.ParentOperationID,
475+
YdbOperationId: clientOperationID,
476+
UpdatedAt: now,
477+
ParentOperationID: req.ParentOperationID,
478+
EncryptionSettings: req.EncryptionSettings,
415479
}
416480

417481
return backup, op, nil

internal/config/config.go

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

6767
type FeatureFlagsConfig struct {
68-
DisableTTLDeletion bool `yaml:"disable_ttl_deletion" default:"false"`
69-
EnableNewPathsFormat bool `yaml:"enable_new_paths_format" default:"false"`
68+
DisableTTLDeletion bool `yaml:"disable_ttl_deletion" default:"false"`
69+
EnableNewPathsFormat bool `yaml:"enable_new_paths_format" default:"false"`
70+
EnableBackupEncryption bool `yaml:"enable_backup_encryption" default:"false"`
7071
}
7172

7273
type LogConfig struct {
@@ -96,6 +97,7 @@ type Config struct {
9697
ClientConnection ClientConnectionConfig `yaml:"client_connection"`
9798
S3 S3Config `yaml:"s3"`
9899
Auth PluginConfig `yaml:"auth"`
100+
KMS PluginConfig `yaml:"kms"`
99101
GRPCServer GRPCServerConfig `yaml:"grpc_server"`
100102
MetricsServer MetricsServerConfig `yaml:"metrics_server"`
101103
OperationProcessor OperationProcessorConfig `yaml:"operation_processor"`

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.DestinationPath)
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 {

0 commit comments

Comments
 (0)