From b64012eb30ef6adc738cd175b9105bee33e21e57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 7 Feb 2024 15:40:56 +0100 Subject: [PATCH 1/4] bump tusd and make CORS configurable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../unreleased/make-tusd-cors-configurable.md | 5 +++ pkg/rhttp/datatx/manager/tus/tus.go | 37 +++++++++++++++++-- pkg/storage/fs/s3ng/blobstore/blobstore.go | 2 +- 3 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 changelog/unreleased/make-tusd-cors-configurable.md diff --git a/changelog/unreleased/make-tusd-cors-configurable.md b/changelog/unreleased/make-tusd-cors-configurable.md new file mode 100644 index 0000000000..3d808ecf41 --- /dev/null +++ b/changelog/unreleased/make-tusd-cors-configurable.md @@ -0,0 +1,5 @@ +Bugfix: make tusd CORS headers configurable + +We bumped tusd to 1.13.0 and made CORS headers configurable via mapstructure. + +https://github.com/cs3org/reva/pull/4507 diff --git a/pkg/rhttp/datatx/manager/tus/tus.go b/pkg/rhttp/datatx/manager/tus/tus.go index 6da1740154..e9b2a07b03 100644 --- a/pkg/rhttp/datatx/manager/tus/tus.go +++ b/pkg/rhttp/datatx/manager/tus/tus.go @@ -23,6 +23,7 @@ import ( "log" "net/http" "path" + "regexp" "github.com/pkg/errors" tusd "github.com/tus/tusd/pkg/handler" @@ -45,14 +46,25 @@ func init() { registry.Register("tus", New) } +type TusConfig struct { + cache.Config + CorsEnabled bool `mapstructure:"cors_enabled"` + CorsAllowOrigin string `mapstructure:"cors_allow_origin"` + CorsAllowCredentials bool `mapstructure:"cors_allow_credentials"` + CorsAllowMethods string `mapstructure:"cors_allow_methods"` + CorsAllowHeaders string `mapstructure:"cors_allow_headers"` + CorsMaxAge string `mapstructure:"cors_max_age"` + CorsExposeHeaders string `mapstructure:"cors_expose_headers"` +} + type manager struct { - conf *cache.Config + conf *TusConfig publisher events.Publisher statCache cache.StatCache } -func parseConfig(m map[string]interface{}) (*cache.Config, error) { - c := &cache.Config{} +func parseConfig(m map[string]interface{}) (*TusConfig, error) { + c := &TusConfig{} if err := mapstructure.Decode(m, c); err != nil { err = errors.Wrap(err, "error decoding conf") return nil, err @@ -69,7 +81,7 @@ func New(m map[string]interface{}, publisher events.Publisher) (datatx.DataTX, e return &manager{ conf: c, publisher: publisher, - statCache: cache.GetStatCache(*c), + statCache: cache.GetStatCache(c.Config), }, nil } @@ -94,6 +106,23 @@ func (m *manager) Handler(fs storage.FS) (http.Handler, error) { Logger: log.New(appctx.GetLogger(context.Background()), "", 0), } + if m.conf.CorsEnabled { + allowOrigin, err := regexp.Compile(m.conf.CorsAllowOrigin) + if m.conf.CorsAllowOrigin != "" && err != nil { + return nil, err + } + + config.Cors = &tusd.CorsConfig{ + Disable: false, + AllowOrigin: allowOrigin, + AllowCredentials: m.conf.CorsAllowCredentials, + AllowMethods: m.conf.CorsAllowMethods, + AllowHeaders: m.conf.CorsAllowHeaders, + MaxAge: m.conf.CorsMaxAge, + ExposeHeaders: m.conf.CorsExposeHeaders, + } + } + handler, err := tusd.NewUnroutedHandler(config) if err != nil { return nil, err diff --git a/pkg/storage/fs/s3ng/blobstore/blobstore.go b/pkg/storage/fs/s3ng/blobstore/blobstore.go index 9c744e7540..4fa391ebbb 100644 --- a/pkg/storage/fs/s3ng/blobstore/blobstore.go +++ b/pkg/storage/fs/s3ng/blobstore/blobstore.go @@ -71,7 +71,7 @@ func (bs *Blobstore) Upload(node *node.Node, source string) error { } defer reader.Close() - _, err = bs.client.PutObject(context.Background(), bs.bucket, bs.path(node), reader, node.Blobsize, minio.PutObjectOptions{ContentType: "application/octet-stream", SendContentMd5: true}) + _, err = bs.client.PutObject(context.Background(), bs.bucket, bs.path(node), reader, node.Blobsize, minio.PutObjectOptions{ContentType: "application/octet-stream", SendContentMd5: false}) if err != nil { return errors.Wrapf(err, "could not store object '%s' into bucket '%s'", bs.path(node), bs.bucket) From b6c9faa35be26e6b05625d4afed1674143f9d3de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 7 Feb 2024 17:11:07 +0100 Subject: [PATCH 2/4] make datagateway forward options requests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- .../http/services/datagateway/datagateway.go | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/internal/http/services/datagateway/datagateway.go b/internal/http/services/datagateway/datagateway.go index 2a571d2cf4..342e0f2b84 100644 --- a/internal/http/services/datagateway/datagateway.go +++ b/internal/http/services/datagateway/datagateway.go @@ -147,6 +147,9 @@ func (s *svc) setHandler() { case "PATCH": s.doPatch(w, r) return + case "OPTIONS": + s.doOptions(w, r) + return default: w.WriteHeader(http.StatusNotImplemented) return @@ -408,6 +411,52 @@ func (s *svc) doPatch(w http.ResponseWriter, r *http.Request) { } } +func (s *svc) doOptions(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + log := appctx.GetLogger(ctx) + + claims, err := s.verify(ctx, r) + if err != nil { + err = errors.Wrap(err, "datagateway: error validating transfer token") + log.Error().Err(err).Str("token", r.Header.Get(TokenTransportHeader)).Msg("invalid transfer token") + w.WriteHeader(http.StatusForbidden) + return + } + + log.Debug().Str("target", claims.Target).Msg("sending request to internal data server") + + httpClient := s.client + httpReq, err := rhttp.NewRequest(ctx, "OPTIONS", claims.Target, nil) + if err != nil { + log.Error().Err(err).Msg("wrong request") + w.WriteHeader(http.StatusInternalServerError) + return + } + httpReq.Header = r.Header + + httpRes, err := httpClient.Do(httpReq) + if err != nil { + log.Error().Err(err).Msg("error doing OPTIONS request to data service") + w.WriteHeader(http.StatusInternalServerError) + return + } + defer httpRes.Body.Close() + + copyHeader(w.Header(), httpRes.Header) + + // add upload expiry / transfer token expiry header for tus https://tus.io/protocols/resumable-upload.html#expiration + w.Header().Set(UploadExpiresHeader, time.Unix(claims.ExpiresAt, 0).Format(time.RFC1123)) + + if httpRes.StatusCode != http.StatusOK { + // swallow the body and set content-length to 0 to prevent reverse proxies from trying to read from it + w.Header().Set("Content-Length", "0") + w.WriteHeader(httpRes.StatusCode) + return + } + + w.WriteHeader(http.StatusOK) +} + func copyHeader(dst, src http.Header) { for key, values := range src { for i := range values { From a9f0c2a737ab485ab56b176143920cf71d3d67f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 7 Feb 2024 17:12:40 +0100 Subject: [PATCH 3/4] dataproviders have to set cors headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- internal/http/services/datagateway/datagateway.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/internal/http/services/datagateway/datagateway.go b/internal/http/services/datagateway/datagateway.go index 342e0f2b84..13411fc36d 100644 --- a/internal/http/services/datagateway/datagateway.go +++ b/internal/http/services/datagateway/datagateway.go @@ -135,7 +135,6 @@ func (s *svc) setHandler() { r = r.WithContext(ctx) switch r.Method { case "HEAD": - addCorsHeader(w) s.doHead(w, r) return case "GET": @@ -157,13 +156,6 @@ func (s *svc) setHandler() { }) } -func addCorsHeader(res http.ResponseWriter) { - headers := res.Header() - headers.Set("Access-Control-Allow-Origin", "*") - headers.Set("Access-Control-Allow-Headers", "Content-Type, Origin, Authorization") - headers.Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, HEAD") -} - func (s *svc) verify(ctx context.Context, r *http.Request) (*transferClaims, error) { // Extract transfer token from request header. If not existing, assume that it's the last path segment instead. token := r.Header.Get(TokenTransportHeader) From e5ed64ee4920811e696816a971f7c0912be92f61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rn=20Friedrich=20Dreyer?= Date: Wed, 7 Feb 2024 17:16:51 +0100 Subject: [PATCH 4/4] revert SendContentMd5 change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörn Friedrich Dreyer --- pkg/storage/fs/s3ng/blobstore/blobstore.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/storage/fs/s3ng/blobstore/blobstore.go b/pkg/storage/fs/s3ng/blobstore/blobstore.go index 4fa391ebbb..9c744e7540 100644 --- a/pkg/storage/fs/s3ng/blobstore/blobstore.go +++ b/pkg/storage/fs/s3ng/blobstore/blobstore.go @@ -71,7 +71,7 @@ func (bs *Blobstore) Upload(node *node.Node, source string) error { } defer reader.Close() - _, err = bs.client.PutObject(context.Background(), bs.bucket, bs.path(node), reader, node.Blobsize, minio.PutObjectOptions{ContentType: "application/octet-stream", SendContentMd5: false}) + _, err = bs.client.PutObject(context.Background(), bs.bucket, bs.path(node), reader, node.Blobsize, minio.PutObjectOptions{ContentType: "application/octet-stream", SendContentMd5: true}) if err != nil { return errors.Wrapf(err, "could not store object '%s' into bucket '%s'", bs.path(node), bs.bucket)