Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ require (
github.com/open-policy-agent/opa v1.21.1
github.com/opencloud-eu/icap-client v0.0.0-20250930132611-28a2afe62d89
github.com/opencloud-eu/libre-graph-api-go v1.0.9-0.20261016073229-17ed3d5dd346
github.com/opencloud-eu/reva/v2 v2.51.1-0.20261023103014-40a03d16bc92
github.com/opencloud-eu/reva/v2 v2.51.1-0.20261026185238-70f0810cb115
github.com/opensearch-project/opensearch-go/v4 v4.7.3
github.com/orcaman/concurrent-map v1.0.0
github.com/pkg/errors v0.9.1
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,10 @@ github.com/opencloud-eu/libre-graph-api-go v1.0.9-0.20261016073229-17ed3d5dd346
github.com/opencloud-eu/libre-graph-api-go v1.0.9-0.20261016073229-17ed3d5dd346/go.mod h1:lTM8JeGblNpoMySTW7Lui2+c5TTLI95mwxtdUIHHrhU=
github.com/opencloud-eu/reva/v2 v2.51.1-0.20261023103014-40a03d16bc92 h1:vGNerJ4b6uAmD8vBK9z1VYOYei+Xxs+K46GIFkQyZxM=
github.com/opencloud-eu/reva/v2 v2.51.1-0.20261023103014-40a03d16bc92/go.mod h1:rAwuI3PX8n3RxBXHfpMtAKqfwxTIKhZol1Bi3Qj39p8=
github.com/opencloud-eu/reva/v2 v2.51.1-0.20261026175458-033aed6aa5e5 h1:0FIQUB216xbCWHwmobuWBgKuAuQYPJ0OdLLA56g4Cr0=
github.com/opencloud-eu/reva/v2 v2.51.1-0.20261026175458-033aed6aa5e5/go.mod h1:rAwuI3PX8n3RxBXHfpMtAKqfwxTIKhZol1Bi3Qj39p8=
github.com/opencloud-eu/reva/v2 v2.51.1-0.20261026185238-70f0810cb115 h1:yvjNZ5i8zLxtU7cpcZr9/G2R8D0WQDRLkAKThMhcPkw=
github.com/opencloud-eu/reva/v2 v2.51.1-0.20261026185238-70f0810cb115/go.mod h1:rAwuI3PX8n3RxBXHfpMtAKqfwxTIKhZol1Bi3Qj39p8=
github.com/opencloud-eu/secure v0.0.0-20260312082735-b6f5cb2244e4 h1:l2oB/RctH+t8r7QBj5p8thfEHCM/jF35aAY3WQ3hADI=
github.com/opencloud-eu/secure v0.0.0-20260312082735-b6f5cb2244e4/go.mod h1:BmF5hyM6tXczk3MpQkFf1hpKSRqCyhqcbiQtiAF7+40=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
Expand Down
1 change: 1 addition & 0 deletions opencloud/pkg/command/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func ConsistencyCommand(cfg *config.Config) *cobra.Command {
cfg.StorageUsers.Drivers.DecomposedS3.Bucket,
cfg.StorageUsers.Drivers.DecomposedS3.AccessKey,
cfg.StorageUsers.Drivers.DecomposedS3.SecretKey,
cfg.StorageUsers.Drivers.DecomposedS3.SSECKey,
decomposeds3bs.Options{},
)
case "decomposed":
Expand Down
1 change: 1 addition & 0 deletions opencloud/pkg/command/revisions.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ func PurgeRevisionsCommand(cfg *config.Config) *cobra.Command {
cfg.StorageUsers.Drivers.DecomposedS3.Bucket,
cfg.StorageUsers.Drivers.DecomposedS3.AccessKey,
cfg.StorageUsers.Drivers.DecomposedS3.SecretKey,
cfg.StorageUsers.Drivers.DecomposedS3.SSECKey,
decomposeds3bs.Options{},
)
case "decomposed":
Expand Down
31 changes: 31 additions & 0 deletions services/storage-users/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,3 +325,34 @@ Store specific notes:
- When using `redis-sentinel`, the Redis master to use is configured via e.g. `OC_CACHE_STORE_NODES` in the form of `<sentinel-host>:<sentinel-port>/<redis-master>` like `10.10.0.200:26379/mymaster`.
- When using `nats-js-kv` it is recommended to set `OC_CACHE_STORE_NODES` to the same value as `OC_EVENTS_ENDPOINT`. That way the cache uses the same nats instance as the event bus.
- When using the `nats-js-kv` store, it is possible to set `OC_CACHE_DISABLE_PERSISTENCE` to instruct nats to not persist cache data on disc.

## S3 customer-provided encryption keys (SSE-C)

For the `decomposeds3` driver (also exposed as the legacy `s3ng` alias), set an
optional encryption key beside the bucket declaration:

```yaml
storage_users:
driver: decomposeds3
drivers:
decomposeds3:
endpoint: https://s3.example.com
region: us-east-1
bucket: files
access_key: <access-key>
secret_key: <secret-key>
sse_c_key: <base64-encoded-32-byte-key>
```

The equivalent environment variable is `STORAGE_USERS_DECOMPOSEDS3_SSE_C_KEY`.
Generate a random key with `openssl rand -base64 32` and store it as a secret.
An empty key preserves existing behavior. A configured key enables SSE-C for
uploads, including multipart uploads, and is supplied when reading blobs and
their metadata. The blobstore maintenance commands use the same setting.
HTTPS is required, and the S3 service must allow SSE-C uploads.

Back up and retain the encryption key: it is required to read objects and file
versions encrypted with it. Changing the setting does not re-encrypt existing
objects. Migration and key rotation must be handled separately before changing
the key or enabling SSE-C on an existing bucket. Missing or incorrect keys cause
reads to fail; OpenCloud does not retry without encryption.
1 change: 1 addition & 0 deletions services/storage-users/pkg/command/blobstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ func initBlobstore(cfg *config.Config) (tree.Blobstore, error) {
cfg.Drivers.DecomposedS3.Bucket,
cfg.Drivers.DecomposedS3.AccessKey,
cfg.Drivers.DecomposedS3.SecretKey,
cfg.Drivers.DecomposedS3.SSECKey,
s3bs.Options{
DisableContentSha256: cfg.Drivers.DecomposedS3.DisableContentSha256,
DisableMultipart: cfg.Drivers.DecomposedS3.DisableMultipart,
Expand Down
22 changes: 22 additions & 0 deletions services/storage-users/pkg/command/blobstore_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package command

import (
"github.com/opencloud-eu/opencloud/services/storage-users/pkg/config"
"strings"
"testing"
)

Expand Down Expand Up @@ -102,3 +104,23 @@ func Test_parseBlobPath(t *testing.T) {
})
}
}

func TestInitBlobstoreValidatesSSEC(t *testing.T) {
for _, driver := range []string{"s3ng", "decomposeds3"} {
t.Run(driver, func(t *testing.T) {
cfg := &config.Config{Driver: driver}
cfg.Drivers.DecomposedS3 = config.DecomposedS3Driver{Endpoint: "https://localhost:9000", Region: "us-east-1", Bucket: "bucket", AccessKey: "ak", SecretKey: "sk", SSECKey: "invalid-secret!"}
_, err := initBlobstore(cfg)
if err == nil || !strings.Contains(err.Error(), "SSE-C") {
t.Fatalf("CLI ignored invalid SSE-C key: %v", err)
}
if strings.Contains(err.Error(), cfg.Drivers.DecomposedS3.SSECKey) {
t.Fatal("error exposes key")
}
cfg.Drivers.DecomposedS3.SSECKey = ""
if _, err := initBlobstore(cfg); err != nil {
t.Fatal(err)
}
})
}
}
1 change: 1 addition & 0 deletions services/storage-users/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ type DecomposedS3Driver struct {
SecretKey string `yaml:"secret_key" env:"STORAGE_USERS_DECOMPOSEDS3_SECRET_KEY" desc:"Secret key for the S3 bucket." introductionVersion:"1.0.0"`
Endpoint string `yaml:"endpoint" env:"STORAGE_USERS_DECOMPOSEDS3_ENDPOINT" desc:"Endpoint for the S3 bucket." introductionVersion:"1.0.0"`
Bucket string `yaml:"bucket" env:"STORAGE_USERS_DECOMPOSEDS3_BUCKET" desc:"Name of the S3 bucket." introductionVersion:"1.0.0"`
SSECKey string `yaml:"sse_c_key" env:"STORAGE_USERS_DECOMPOSEDS3_SSE_C_KEY" desc:"Optional base64-encoded 32-byte SSE-C encryption key for this S3 bucket. Requires HTTPS. Retain this key to read existing objects; changing it does not re-encrypt them." introductionVersion:"%%NEXT%%"`
DisableContentSha256 bool `yaml:"put_object_disable_content_sha254" env:"STORAGE_USERS_DECOMPOSEDS3_PUT_OBJECT_DISABLE_CONTENT_SHA256" desc:"Disable sending content sha256 when copying objects to S3." introductionVersion:"1.0.0"`
DisableMultipart bool `yaml:"put_object_disable_multipart" env:"STORAGE_USERS_DECOMPOSEDS3_PUT_OBJECT_DISABLE_MULTIPART" desc:"Disable multipart uploads when copying objects to S3." introductionVersion:"1.0.0"`
SendContentMd5 bool `yaml:"put_object_send_content_md5" env:"STORAGE_USERS_DECOMPOSEDS3_PUT_OBJECT_SEND_CONTENT_MD5" desc:"Send a Content-MD5 header when copying objects to S3." introductionVersion:"1.0.0"`
Expand Down
2 changes: 2 additions & 0 deletions services/storage-users/pkg/revaconfig/drivers.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ func DecomposedS3(cfg *config.Config) map[string]any {
"s3.secret_key": cfg.Drivers.DecomposedS3.SecretKey,
"s3.endpoint": cfg.Drivers.DecomposedS3.Endpoint,
"s3.bucket": cfg.Drivers.DecomposedS3.Bucket,
"s3.sse_c_key": cfg.Drivers.DecomposedS3.SSECKey,
"s3.disable_content_sha254": cfg.Drivers.DecomposedS3.DisableContentSha256,
"s3.disable_multipart": cfg.Drivers.DecomposedS3.DisableMultipart,
"s3.send_content_md5": cfg.Drivers.DecomposedS3.SendContentMd5,
Expand Down Expand Up @@ -388,6 +389,7 @@ func DecomposedS3NoEvents(cfg *config.Config) map[string]any {
"s3.secret_key": cfg.Drivers.DecomposedS3.SecretKey,
"s3.endpoint": cfg.Drivers.DecomposedS3.Endpoint,
"s3.bucket": cfg.Drivers.DecomposedS3.Bucket,
"s3.sse_c_key": cfg.Drivers.DecomposedS3.SSECKey,
"max_acquire_lock_cycles": cfg.Drivers.DecomposedS3.MaxAcquireLockCycles,
"max_concurrency": cfg.Drivers.DecomposedS3.MaxConcurrency,
"disable_versioning": cfg.Drivers.DecomposedS3.DisableVersioning,
Expand Down
12 changes: 12 additions & 0 deletions services/storage-users/pkg/revaconfig/drivers_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package revaconfig

import (
"bytes"
"encoding/base64"
"testing"

"github.com/test-go/testify/require"
Expand All @@ -26,3 +28,13 @@ func TestMetadataPrefixReachesAllDrivers(t *testing.T) {
require.Equal(t, "user.foreign.", driver["metadata_prefix"], name)
}
}

func TestSSECKeyReachesS3Drivers(t *testing.T) {
key := base64.StdEncoding.EncodeToString(bytes.Repeat([]byte{42}, 32))
t.Setenv("STORAGE_USERS_DECOMPOSEDS3_SSE_C_KEY", key)
cfg := defaults.FullDefaultConfig()
cfg.Commons = &shared.Commons{GRPCClientTLS: &shared.GRPCClientTLS{}}
require.NoError(t, envdecode.Decode(cfg))
require.Equal(t, key, DecomposedS3(cfg)["s3.sse_c_key"])
require.Equal(t, key, DecomposedS3NoEvents(cfg)["s3.sse_c_key"])
}
29 changes: 29 additions & 0 deletions services/webdav/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,32 @@ See the [search](https://github.com/opencloud-eu/opencloud/tree/main/services/se
## Scalability

The webdav service persists generated thumbnails to its thumbnail cache (file backend by default). When running multiple instances behind a load balancer, point `WEBDAV_THUMBNAILS_CACHE_BACKEND` at a shared `s3` bucket so all instances read and write the same cache; otherwise each instance keeps its own on-disk cache.

### SSE-C for the S3 thumbnail cache

The S3 thumbnail cache accepts a customer-provided encryption key beside its
bucket declaration:

```yaml
webdav:
thumbnails:
cache_backend: s3
cache_s3_endpoint: https://s3.example.com
cache_s3_region: us-east-1
cache_s3_bucket: thumbnails
cache_s3_access_key: <access-key>
cache_s3_secret_key: <secret-key>
cache_s3_sse_c_key: <base64-encoded-32-byte-key>
```

Alternatively, set `WEBDAV_THUMBNAILS_CACHE_S3_SSE_C_KEY`. This key is independent
of the user-file bucket key. Generate one with `openssl rand -base64 32` and
retain a secure backup. All instances sharing the cache bucket must use the
same key. The endpoint must use HTTPS and the S3 service must permit SSE-C.

An empty setting preserves current behavior. A configured key is sent on reads
and writes, including multipart uploads. Invalid keys are rejected when the S3
cache is configured. Missing or incorrect keys are errors, not cache misses.
Changing the setting does not migrate or re-encrypt cached objects; plan cache
recreation or migration separately before switching keys. Non-S3 cache backends
ignore this setting.
1 change: 1 addition & 0 deletions services/webdav/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type Thumbnails struct {
FilesystemStorageRoot string `yaml:"filesystemstorage_root" env:"THUMBNAILS_FILESYSTEMSTORAGE_ROOT" desc:"Directory the thumbnails service stored its files in. Read as a fallback for WEBDAV_THUMBNAILS_CACHE_DIR, which is then its cache subdirectory." introductionVersion:"1.0.0" deprecationVersion:"%%NEXT%%" removalVersion:"%%NEXT_PRODUCTION_VERSION%%" deprecationInfo:"THUMBNAILS_FILESYSTEMSTORAGE_ROOT is deprecated. The webdav service owns the thumbnail cache now, so use WEBDAV_THUMBNAILS_CACHE_DIR instead." deprecationReplacement:"WEBDAV_THUMBNAILS_CACHE_DIR"`
Resolutions []string `yaml:"resolutions" env:"THUMBNAILS_RESOLUTIONS;WEBDAV_THUMBNAILS_RESOLUTIONS" desc:"Allowed thumbnail edge lengths, like 512. Each axis of a requested size is rounded up to the next one of these before the box is sent to the generator. Legacy WidthxHeight entries are still accepted and contribute both of their sides, each capped at 2048, the largest box the built-in generator accepts by default." introductionVersion:"%%NEXT%%"`
CacheS3Bucket string `yaml:"cache_s3_bucket" env:"WEBDAV_THUMBNAILS_CACHE_S3_BUCKET" desc:"S3 bucket name for thumbnail cache when WEBDAV_THUMBNAILS_CACHE_BACKEND is 's3'." introductionVersion:"%%NEXT%%"`
CacheS3SSECKey string `yaml:"cache_s3_sse_c_key" env:"WEBDAV_THUMBNAILS_CACHE_S3_SSE_C_KEY" desc:"Optional base64-encoded 32-byte SSE-C encryption key for the thumbnail cache bucket. Requires HTTPS. Retain this key to read existing objects; changing it does not re-encrypt them." introductionVersion:"%%NEXT%%"`
CacheS3Region string `yaml:"cache_s3_region" env:"WEBDAV_THUMBNAILS_CACHE_S3_REGION" desc:"S3 region for thumbnail cache when WEBDAV_THUMBNAILS_CACHE_BACKEND is 's3'." introductionVersion:"%%NEXT%%"`
CacheS3Endpoint string `yaml:"cache_s3_endpoint" env:"WEBDAV_THUMBNAILS_CACHE_S3_ENDPOINT" desc:"S3 endpoint URL (for MinIO or compatible services) when WEBDAV_THUMBNAILS_CACHE_BACKEND is 's3'." introductionVersion:"%%NEXT%%"`
CacheS3AccessKey string `yaml:"cache_s3_access_key" env:"WEBDAV_THUMBNAILS_CACHE_S3_ACCESS_KEY" desc:"S3 access key for thumbnail cache authentication." introductionVersion:"%%NEXT%%"`
Expand Down
23 changes: 23 additions & 0 deletions services/webdav/pkg/service/v0/previews_test.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package svc

import (
"github.com/opencloud-eu/opencloud/pkg/config/envdecode"
"github.com/opencloud-eu/opencloud/services/webdav/pkg/config/defaults"
"net/http"
"net/http/httptest"
"strings"
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"github.com/opencloud-eu/opencloud/pkg/log"
"github.com/opencloud-eu/opencloud/services/webdav/pkg/config"
"github.com/opencloud-eu/opencloud/services/webdav/pkg/thumbnail"
)

var _ = Describe("with previews disabled", func() {
Expand All @@ -25,3 +30,21 @@ var _ = Describe("with previews disabled", func() {
Expect(rec.Code).To(Equal(http.StatusNotFound))
})
})

func TestThumbnailWorkflowValidatesSSEC(t *testing.T) {
t.Setenv("WEBDAV_THUMBNAILS_CACHE_S3_SSE_C_KEY", "invalid-secret!")
cfg := defaults.FullDefaultConfig()
cfg.Thumbnails.CacheBackend = "s3"
cfg.Thumbnails.CacheS3Bucket = "bucket"
cfg.Thumbnails.CacheS3Region = "us-east-1"
cfg.Thumbnails.CacheS3Endpoint = "https://localhost:9000"
cfg.Thumbnails.CacheS3AccessKey = "ak"
cfg.Thumbnails.CacheS3SecretKey = "sk"
if err := envdecode.Decode(cfg); err != nil {
t.Fatal(err)
}
_, err := newThumbnailWorkflow(cfg, thumbnail.NewSourceTypes(nil), log.NopLogger(), nil)
if err == nil || !strings.Contains(err.Error(), "SSE-C key must be base64-encoded") {
t.Fatalf("thumbnail workflow ignored invalid SSE-C key: %v", err)
}
}
1 change: 1 addition & 0 deletions services/webdav/pkg/service/v0/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ func newThumbnailWorkflow(conf *config.Config, sourceTypes thumbnail.SourceTypes
conf.Thumbnails.CacheS3Endpoint,
conf.Thumbnails.CacheS3AccessKey,
conf.Thumbnails.CacheS3SecretKey,
conf.Thumbnails.CacheS3SSECKey,
)

// the new setting wins, the deprecated one is the fallback, and an empty
Expand Down
9 changes: 7 additions & 2 deletions services/webdav/pkg/thumbnail/cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,23 @@ func NewThumbnailCache(backend string, cacheDir string, s3cfg *S3CacheConfig) (T
if s3cfg == nil || !s3cfg.IsComplete() {
return nil, errors.New("s3 thumbnail cache needs a bucket, a region and credentials")
}
return NewS3Cache(*s3cfg), nil
c := NewS3Cache(*s3cfg)
if c.initErr != nil {
return nil, c.initErr
}
return c, nil
default:
return nil, fmt.Errorf("unknown thumbnail cache backend %q, expected none, memory, file or s3", backend)
}
}

// BuildS3CacheConfig creates an S3CacheConfig from raw config fields.
func BuildS3CacheConfig(bucket, region, endpoint, accessKey, secretKey string) *S3CacheConfig {
func BuildS3CacheConfig(bucket, region, endpoint, accessKey, secretKey, ssecKey string) *S3CacheConfig {
cfg := &S3CacheConfig{
Bucket: bucket,
Region: region,
Endpoint: endpoint,
SSECKey: ssecKey,
Secure: true,
}
if accessKey != "" && secretKey != "" {
Expand Down
5 changes: 3 additions & 2 deletions services/webdav/pkg/thumbnail/cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,16 +162,17 @@ var _ = Describe("NewThumbnailCache", func() {
})

It("should build S3CacheConfig from env var fields via BuildS3CacheConfig", func() {
cfg := BuildS3CacheConfig("my-bucket", "us-east-1", "http://minio:9000", "ak", "sk")
cfg := BuildS3CacheConfig("my-bucket", "us-east-1", "http://minio:9000", "ak", "sk", "sse-c-key")
Expect(cfg.Bucket).To(Equal("my-bucket"))
Expect(cfg.Region).To(Equal("us-east-1"))
Expect(cfg.Endpoint).To(Equal("http://minio:9000"))
Expect(cfg.SSECKey).To(Equal("sse-c-key"))
Expect(cfg.Secure).To(BeTrue())
Expect(cfg.Credentials).ToNot(BeNil())
})

It("should build incomplete S3CacheConfig when credentials are missing", func() {
cfg := BuildS3CacheConfig("my-bucket", "us-east-1", "", "", "")
cfg := BuildS3CacheConfig("my-bucket", "us-east-1", "", "", "", "")
Expect(cfg.IsComplete()).To(BeFalse())
})
})
Expand Down
44 changes: 36 additions & 8 deletions services/webdav/pkg/thumbnail/cache/s3cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,24 @@ package cache
import (
"bytes"
"context"
"fmt"
"io"
"mime"
"net/http"
"net/url"
"path/filepath"
"strings"
"sync"

minio "github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/minio-go/v7/pkg/encrypt"
"github.com/opencloud-eu/reva/v2/pkg/storage/utils/s3utils"
)

// S3CacheConfig holds the configuration for an S3 cache backend.
type S3CacheConfig struct {
SSECKey string
Bucket string
Region string
Endpoint string
Expand Down Expand Up @@ -49,15 +55,33 @@ func (r *realS3Client) GetObject(ctx context.Context, bucketName, objectName str

// S3Cache stores thumbnails in an S3-compatible bucket.
type S3Cache struct {
config S3CacheConfig
client s3Client
once sync.Once
initErr error
newFunc func(cfg S3CacheConfig) (s3Client, error)
encryption encrypt.ServerSide
config S3CacheConfig
client s3Client
once sync.Once
initErr error
newFunc func(cfg S3CacheConfig) (s3Client, error)
}

func NewS3Cache(cfg S3CacheConfig) *S3Cache {
c := &S3Cache{config: cfg}
// MinIO expects a host:port endpoint and a separate TLS flag. Accept the
// URL form documented by the service as well as an SDK-style bare host.
var configErr error
if strings.Contains(cfg.Endpoint, "://") {
u, err := url.Parse(cfg.Endpoint)
if err != nil || (u.Scheme != "http" && u.Scheme != "https") || u.Host == "" || (u.Path != "" && u.Path != "/") || u.RawQuery != "" || u.Fragment != "" || u.User != nil {
configErr = fmt.Errorf("invalid S3 thumbnail cache endpoint")
} else {
cfg.Endpoint = u.Host
cfg.Secure = u.Scheme == "https"
}
}
encryption, err := s3utils.NewSSEC(cfg.SSECKey, cfg.Secure)
if configErr == nil {
configErr = err
}
cfg.SSECKey = ""
c := &S3Cache{config: cfg, encryption: encryption, initErr: configErr}
c.newFunc = func(cfg S3CacheConfig) (s3Client, error) {
cl, err := minio.New(cfg.Endpoint, &minio.Options{
Creds: cfg.Credentials,
Expand All @@ -75,6 +99,9 @@ func NewS3Cache(cfg S3CacheConfig) *S3Cache {
// initClient lazily initializes the S3 client on first access.
func (c *S3Cache) initClient() error {
c.once.Do(func() {
if c.initErr != nil {
return
}
cl, err := c.newFunc(c.config)
if err != nil {
c.initErr = err
Expand All @@ -90,7 +117,7 @@ func (c *S3Cache) Get(key string) ([]byte, error) {
return nil, err
}

obj, err := c.client.GetObject(context.Background(), c.config.Bucket, key, minio.GetObjectOptions{})
obj, err := c.client.GetObject(context.Background(), c.config.Bucket, key, minio.GetObjectOptions{ServerSideEncryption: c.encryption})
if err != nil {
return nil, asCacheError(err)
}
Expand Down Expand Up @@ -139,7 +166,8 @@ func (c *S3Cache) Put(key string, data []byte) error {
bytes.NewReader(data),
int64(len(data)),
minio.PutObjectOptions{
ContentType: contentType,
ContentType: contentType,
ServerSideEncryption: c.encryption,
},
)
return err
Expand Down
Loading