From 5b206ceecc57c7d1361d264b7ca74b6f56855c86 Mon Sep 17 00:00:00 2001 From: Tomas Dvorak Date: Sat, 19 Sep 2026 01:03:35 +0200 Subject: [PATCH] fix(slave): serve encrypted thumbnails on slave nodes (#3164) The slave thumb endpoint only received a filesystem path, so entities encrypted at rest were served as raw ciphertext. The master now appends the entity ID (eid) to the signed slave thumb URL; the slave resolves the entity from the shared DB, recovering EncryptMetadata for decryption. Thumbnail-type IDs serve the blob directly; other IDs become the source for on-demand generation. Path-only URLs keep working as a fallback. Authored By: TDvorak Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- pkg/cluster/routes/routes.go | 7 +++++- pkg/filemanager/driver/remote/remote.go | 2 +- service/explorer/slave.go | 33 +++++++++++++++++++++---- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/pkg/cluster/routes/routes.go b/pkg/cluster/routes/routes.go index 79b18f1b..f4dc2640 100644 --- a/pkg/cluster/routes/routes.go +++ b/pkg/cluster/routes/routes.go @@ -203,11 +203,16 @@ func SlaveFileListRoute(srcPath string, recursive bool) string { return fmt.Sprintf("%s?%s", base, query.Encode()) } -func SlaveThumbUrl(base *url.URL, srcPath, ext string) *url.URL { +func SlaveThumbUrl(base *url.URL, srcPath, ext string, entityID int) *url.URL { srcPath = url.PathEscape(base64.URLEncoding.EncodeToString([]byte(srcPath))) ext = url.PathEscape(ext) route, _ := url.Parse(constants.APIPrefixSlave + fmt.Sprintf("/file/thumb/%s/%s", srcPath, ext)) base = base.ResolveReference(route) + if entityID > 0 { + query := base.Query() + query.Set("eid", strconv.Itoa(entityID)) + base.RawQuery = query.Encode() + } return base } diff --git a/pkg/filemanager/driver/remote/remote.go b/pkg/filemanager/driver/remote/remote.go index b7a762df..878d4352 100644 --- a/pkg/filemanager/driver/remote/remote.go +++ b/pkg/filemanager/driver/remote/remote.go @@ -98,7 +98,7 @@ func (handler *Driver) Thumb(ctx context.Context, expire *time.Time, ext string, return "", fmt.Errorf("parse server url failed: %w", err) } - thumbURL := routes.SlaveThumbUrl(serverURL, e.Source(), ext) + thumbURL := routes.SlaveThumbUrl(serverURL, e.Source(), ext, e.ID()) signedThumbURL, err := auth.SignURI(ctx, handler.AuthInstance, thumbURL.String(), expire) if err != nil { return "", err diff --git a/service/explorer/slave.go b/service/explorer/slave.go index c4335aff..40e26367 100644 --- a/service/explorer/slave.go +++ b/service/explorer/slave.go @@ -151,6 +151,9 @@ type ( SlaveThumbService struct { Src string `uri:"src" binding:"required"` Ext string `uri:"ext" binding:"required"` + // Eid optionally carries the entity ID of the requested blob so the + // slave can resolve encryption metadata from the shared DB (#3164). + Eid int `form:"eid"` } ) @@ -165,12 +168,32 @@ func (s *SlaveThumbService) Thumb(c *gin.Context) error { } settings := dep.SettingProvider() - var entity fs.Entity - entity, err = local.NewLocalFileEntity(types.EntityTypeThumbnail, string(src)+settings.ThumbSlaveSidecarSuffix(c)) + var ( + entity fs.Entity + srcEntity fs.Entity + ) + // Resolve the DB entity when the caller knows its ID: it carries the + // encryption metadata needed to serve/generate encrypted thumbnails. + if s.Eid > 0 { + if e, err := dep.FileClient().GetEntityByID(c, s.Eid); err == nil { + dbEntity := fs.NewEntity(e) + if dbEntity.Type() == types.EntityTypeThumbnail { + entity = dbEntity + } else { + srcEntity = dbEntity + } + } + } + + if entity == nil { + entity, err = local.NewLocalFileEntity(types.EntityTypeThumbnail, string(src)+settings.ThumbSlaveSidecarSuffix(c)) + } if err != nil { - srcEntity, err := local.NewLocalFileEntity(types.EntityTypeVersion, string(src)) - if err != nil { - return fs.ErrPathNotExist.WithError(err) + if srcEntity == nil { + srcEntity, err = local.NewLocalFileEntity(types.EntityTypeVersion, string(src)) + if err != nil { + return fs.ErrPathNotExist.WithError(err) + } } entity, err = m.SubmitAndAwaitThumbnailTask(c, nil, s.Ext, srcEntity)