Skip to content

Commit c3776d5

Browse files
committed
ObjectDiskPath shall be unencrypted cause restore requires direct copy and read from clickhouse server, fix after #1316
1 parent f5d9527 commit c3776d5

File tree

3 files changed

+66
-13
lines changed

3 files changed

+66
-13
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
ARG CLICKHOUSE_VERSION=latest
33
ARG CLICKHOUSE_IMAGE=clickhouse/clickhouse-server
44

5-
FROM --platform=$BUILDPLATFORM ${CLICKHOUSE_IMAGE}:${CLICKHOUSE_VERSION} AS builder-base
5+
FROM --platform=${TARGETPLATFORM} ${CLICKHOUSE_IMAGE}:${CLICKHOUSE_VERSION} AS builder-base
66
USER root
77
# TODO remove ugly workaround for musl, https://www.perplexity.ai/search/2ead4c04-060a-4d78-a75f-f26835238438
88
RUN rm -fv /etc/apt/sources.list.d/clickhouse.list && \

pkg/backup/create.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,10 @@ func (b *Backuper) createBackupLocal(ctx context.Context, backupName, diffFromRe
263263
if err = config.ValidateObjectDiskConfig(b.cfg); err != nil {
264264
return err
265265
}
266+
// Warn if encryption key is set for GCS - object disk files won't be encrypted
267+
if b.cfg.General.RemoteStorage == "gcs" && b.cfg.GCS.EncryptionKey != "" {
268+
log.Warn().Msg("GCS_ENCRYPTION_KEY is configured, but files in object_disk path will NOT be encrypted. ClickHouse needs direct unencrypted access to these files for BACKUP/RESTORE operations.")
269+
}
266270
}
267271

268272
if isObjectDiskContainsTables || (diffFromRemote != "" && b.cfg.General.RemoteStorage != "custom") {
@@ -434,6 +438,10 @@ func (b *Backuper) createBackupEmbedded(ctx context.Context, backupName, baseBac
434438
if err := config.ValidateObjectDiskConfig(b.cfg); err != nil {
435439
return err
436440
}
441+
// Warn if encryption key is set for GCS - object disk files won't be encrypted
442+
if b.cfg.General.RemoteStorage == "gcs" && b.cfg.GCS.EncryptionKey != "" {
443+
log.Warn().Msg("GCS_ENCRYPTION_KEY is configured, but files in object_disk path will NOT be encrypted. ClickHouse needs direct unencrypted access to these files for BACKUP/RESTORE operations.")
444+
}
437445
}
438446

439447
backupSQL, tablesSizeSQL, err := b.generateEmbeddedBackupSQL(ctx, backupName, schemaOnly, tables, tablesTitle, partitionsNameList, l, baseBackup, version)

pkg/storage/gcs.go

Lines changed: 57 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,14 @@ func (gcs *GCS) applyEncryption(obj *storage.ObjectHandle) *storage.ObjectHandle
222222
return obj
223223
}
224224

225+
// isNotEncryptedError checks if the error is "ResourceNotEncryptedWithCustomerEncryptionKey"
226+
func (gcs *GCS) isNotEncryptedError(err error) bool {
227+
if err == nil {
228+
return false
229+
}
230+
return strings.Contains(err.Error(), "ResourceNotEncryptedWithCustomerEncryptionKey")
231+
}
232+
225233
func (gcs *GCS) Walk(ctx context.Context, gcsPath string, recursive bool, process func(ctx context.Context, r RemoteFile) error) error {
226234
rootPath := path.Join(gcs.Config.Path, gcsPath)
227235
return gcs.WalkAbsolute(ctx, rootPath, recursive, process)
@@ -278,13 +286,28 @@ func (gcs *GCS) GetFileReaderAbsolute(ctx context.Context, key string) (io.ReadC
278286
return nil, err
279287
}
280288
pClient := pClientObj.(*clientObject).Client
281-
obj := gcs.applyEncryption(pClient.Bucket(gcs.Config.Bucket).Object(key))
289+
obj := pClient.Bucket(gcs.Config.Bucket).Object(key)
290+
// Do NOT apply encryption for object_disks files - they are not encrypted
291+
// because ClickHouse needs to read them directly without encryption key
292+
isObjectDiskPath := gcs.Config.ObjectDiskPath != "" && strings.HasPrefix(key, gcs.Config.ObjectDiskPath)
293+
if !isObjectDiskPath {
294+
obj = gcs.applyEncryption(obj)
295+
}
282296
reader, err := obj.NewReader(ctx)
283297
if err != nil {
284-
if pErr := gcs.clientPool.InvalidateObject(ctx, pClientObj); pErr != nil {
285-
log.Warn().Msgf("gcs.GetFileReader: gcs.clientPool.InvalidateObject error: %v ", pErr)
298+
// If the object is not encrypted but we tried to read it with encryption key,
299+
// retry without encryption (for backward compatibility with old backups)
300+
if !isObjectDiskPath && gcs.isNotEncryptedError(err) && gcs.encryptionKey != nil {
301+
log.Debug().Msgf("gcs.GetFileReader: object %s not encrypted, retrying without encryption key", key)
302+
obj = pClient.Bucket(gcs.Config.Bucket).Object(key)
303+
reader, err = obj.NewReader(ctx)
304+
}
305+
if err != nil {
306+
if pErr := gcs.clientPool.InvalidateObject(ctx, pClientObj); pErr != nil {
307+
log.Warn().Msgf("gcs.GetFileReader: gcs.clientPool.InvalidateObject error: %v ", pErr)
308+
}
309+
return nil, err
286310
}
287-
return nil, err
288311
}
289312
if pErr := gcs.clientPool.ReturnObject(ctx, pClientObj); pErr != nil {
290313
log.Warn().Msgf("gcs.GetFileReader: gcs.clientPool.ReturnObject error: %v ", pErr)
@@ -307,7 +330,12 @@ func (gcs *GCS) PutFileAbsolute(ctx context.Context, key string, r io.ReadCloser
307330
return err
308331
}
309332
pClient := pClientObj.(*clientObject).Client
310-
obj := gcs.applyEncryption(pClient.Bucket(gcs.Config.Bucket).Object(key))
333+
obj := pClient.Bucket(gcs.Config.Bucket).Object(key)
334+
// Do NOT apply encryption for object_disks files - they must be readable by ClickHouse
335+
// which doesn't have access to the encryption key
336+
if gcs.Config.ObjectDiskPath == "" || !strings.HasPrefix(key, gcs.Config.ObjectDiskPath) {
337+
obj = gcs.applyEncryption(obj)
338+
}
311339
// always retry transient errors to mitigate retry logic bugs.
312340
obj = obj.Retryer(storage.WithPolicy(storage.RetryAlways))
313341
writer := obj.NewWriter(ctx)
@@ -340,13 +368,28 @@ func (gcs *GCS) StatFile(ctx context.Context, key string) (RemoteFile, error) {
340368
}
341369

342370
func (gcs *GCS) StatFileAbsolute(ctx context.Context, key string) (RemoteFile, error) {
343-
obj := gcs.applyEncryption(gcs.client.Bucket(gcs.Config.Bucket).Object(key))
371+
obj := gcs.client.Bucket(gcs.Config.Bucket).Object(key)
372+
// Do NOT apply encryption for object_disks files - they are not encrypted
373+
// because ClickHouse needs to read them directly without encryption key
374+
isObjectDiskPath := gcs.Config.ObjectDiskPath != "" && strings.HasPrefix(key, gcs.Config.ObjectDiskPath)
375+
if !isObjectDiskPath {
376+
obj = gcs.applyEncryption(obj)
377+
}
344378
objAttr, err := obj.Attrs(ctx)
345379
if err != nil {
346-
if errors.Is(err, storage.ErrObjectNotExist) {
347-
return nil, ErrNotFound
380+
// If the object is not encrypted but we tried to read it with encryption key,
381+
// retry without encryption (for backward compatibility with old backups)
382+
if !isObjectDiskPath && gcs.isNotEncryptedError(err) && gcs.encryptionKey != nil {
383+
log.Debug().Msgf("gcs.StatFile: object %s not encrypted, retrying without encryption key", key)
384+
obj = gcs.client.Bucket(gcs.Config.Bucket).Object(key)
385+
objAttr, err = obj.Attrs(ctx)
386+
}
387+
if err != nil {
388+
if errors.Is(err, storage.ErrObjectNotExist) {
389+
return nil, ErrNotFound
390+
}
391+
return nil, err
348392
}
349-
return nil, err
350393
}
351394
return &gcsFile{
352395
size: objAttr.Size,
@@ -396,7 +439,9 @@ func (gcs *GCS) CopyObject(ctx context.Context, srcSize int64, srcBucket, srcKey
396439
}
397440
pClient := pClientObj.(*clientObject).Client
398441
src := pClient.Bucket(srcBucket).Object(srcKey)
399-
dst := gcs.applyEncryption(pClient.Bucket(gcs.Config.Bucket).Object(dstKey))
442+
// Do NOT apply encryption for object_disks files - they must be readable by ClickHouse
443+
// which doesn't have access to the encryption key
444+
dst := pClient.Bucket(gcs.Config.Bucket).Object(dstKey)
400445
// always retry transient errors to mitigate retry logic bugs.
401446
dst = dst.Retryer(storage.WithPolicy(storage.RetryAlways))
402447
attrs, err := src.Attrs(ctx)
@@ -407,8 +452,8 @@ func (gcs *GCS) CopyObject(ctx context.Context, srcSize int64, srcBucket, srcKey
407452
return 0, err
408453
}
409454
copier := dst.CopierFrom(src)
410-
// If encryption is enabled, the destination will be encrypted
411-
// Note: source objects from object disks are not encrypted by clickhouse-backup
455+
// Note: source and destination objects for object disks are not encrypted
456+
// because ClickHouse needs to read them directly without encryption key
412457
if _, err = copier.Run(ctx); err != nil {
413458
if pErr := gcs.clientPool.InvalidateObject(ctx, pClientObj); pErr != nil {
414459
log.Warn().Msgf("gcs.CopyObject: gcs.clientPool.InvalidateObject error: %+v", pErr)

0 commit comments

Comments
 (0)