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 <info@tdvorak.dev>

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
pull/3582/head
Tomas Dvorak 2 weeks ago
parent 03d6d49243
commit 5b206ceecc

@ -203,11 +203,16 @@ func SlaveFileListRoute(srcPath string, recursive bool) string {
return fmt.Sprintf("%s?%s", base, query.Encode()) 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))) srcPath = url.PathEscape(base64.URLEncoding.EncodeToString([]byte(srcPath)))
ext = url.PathEscape(ext) ext = url.PathEscape(ext)
route, _ := url.Parse(constants.APIPrefixSlave + fmt.Sprintf("/file/thumb/%s/%s", srcPath, ext)) route, _ := url.Parse(constants.APIPrefixSlave + fmt.Sprintf("/file/thumb/%s/%s", srcPath, ext))
base = base.ResolveReference(route) base = base.ResolveReference(route)
if entityID > 0 {
query := base.Query()
query.Set("eid", strconv.Itoa(entityID))
base.RawQuery = query.Encode()
}
return base return base
} }

@ -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) 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) signedThumbURL, err := auth.SignURI(ctx, handler.AuthInstance, thumbURL.String(), expire)
if err != nil { if err != nil {
return "", err return "", err

@ -151,6 +151,9 @@ type (
SlaveThumbService struct { SlaveThumbService struct {
Src string `uri:"src" binding:"required"` Src string `uri:"src" binding:"required"`
Ext string `uri:"ext" 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,13 +168,33 @@ func (s *SlaveThumbService) Thumb(c *gin.Context) error {
} }
settings := dep.SettingProvider() settings := dep.SettingProvider()
var entity fs.Entity 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)) entity, err = local.NewLocalFileEntity(types.EntityTypeThumbnail, string(src)+settings.ThumbSlaveSidecarSuffix(c))
}
if err != nil { if err != nil {
srcEntity, err := local.NewLocalFileEntity(types.EntityTypeVersion, string(src)) if srcEntity == nil {
srcEntity, err = local.NewLocalFileEntity(types.EntityTypeVersion, string(src))
if err != nil { if err != nil {
return fs.ErrPathNotExist.WithError(err) return fs.ErrPathNotExist.WithError(err)
} }
}
entity, err = m.SubmitAndAwaitThumbnailTask(c, nil, s.Ext, srcEntity) entity, err = m.SubmitAndAwaitThumbnailTask(c, nil, s.Ext, srcEntity)
if err != nil { if err != nil {

Loading…
Cancel
Save