From e1579f16c0a4b62415447ad3508b1139fbff7aeb Mon Sep 17 00:00:00 2001 From: Roman Loginov Date: Tue, 3 Dec 2024 15:36:40 +0300 Subject: [PATCH] [#562] Support TLS termination header for SSE-C The TLS termination header added for determining whether TLS needs to be checked. If the system requests come through a proxy server and TLS can terminate at the proxy level, you should use this header to disable TLS verification at SSE-C. Signed-off-by: Roman Loginov --- api/handler/api.go | 1 + api/handler/attributes.go | 2 +- api/handler/copy.go | 4 ++-- api/handler/get.go | 2 +- api/handler/handlers_test.go | 4 ++++ api/handler/head.go | 2 +- api/handler/multipart_upload.go | 12 ++++++------ api/handler/put.go | 24 +++++++++++++++++------- cmd/s3-gw/app.go | 9 +++++++++ cmd/s3-gw/app_settings.go | 13 ++++++++++--- config/config.env | 3 +++ config/config.yaml | 3 +++ docs/configuration.md | 16 +++++++++++++++- internal/logs/logs.go | 1 + 14 files changed, 74 insertions(+), 22 deletions(-) diff --git a/api/handler/api.go b/api/handler/api.go index 559977aa..6ef460ba 100644 --- a/api/handler/api.go +++ b/api/handler/api.go @@ -41,6 +41,7 @@ type ( RetryMaxAttempts() int RetryMaxBackoff() time.Duration RetryStrategy() RetryStrategy + TLSTerminationHeader() string } FrostFSID interface { diff --git a/api/handler/attributes.go b/api/handler/attributes.go index 22ceffb4..4e4a9132 100644 --- a/api/handler/attributes.go +++ b/api/handler/attributes.go @@ -98,7 +98,7 @@ func (h *handler) GetObjectAttributesHandler(w http.ResponseWriter, r *http.Requ } info := extendedInfo.ObjectInfo - encryptionParams, err := formEncryptionParams(r) + encryptionParams, err := h.formEncryptionParams(r) if err != nil { h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err) return diff --git a/api/handler/copy.go b/api/handler/copy.go index 00473fa9..f4c8c783 100644 --- a/api/handler/copy.go +++ b/api/handler/copy.go @@ -103,12 +103,12 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) { } srcObjInfo := extendedSrcObjInfo.ObjectInfo - srcEncryptionParams, err := formCopySourceEncryptionParams(r) + srcEncryptionParams, err := h.formCopySourceEncryptionParams(r) if err != nil { h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err) return } - dstEncryptionParams, err := formEncryptionParams(r) + dstEncryptionParams, err := h.formEncryptionParams(r) if err != nil { h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err) return diff --git a/api/handler/get.go b/api/handler/get.go index 11b87703..3403ac7d 100644 --- a/api/handler/get.go +++ b/api/handler/get.go @@ -202,7 +202,7 @@ func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) { return } - encryptionParams, err := formEncryptionParams(r) + encryptionParams, err := h.formEncryptionParams(r) if err != nil { h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err) return diff --git a/api/handler/handlers_test.go b/api/handler/handlers_test.go index 628bba77..5651f4b9 100644 --- a/api/handler/handlers_test.go +++ b/api/handler/handlers_test.go @@ -140,6 +140,10 @@ func (c *configMock) RetryStrategy() RetryStrategy { return RetryStrategyConstant } +func (c *configMock) TLSTerminationHeader() string { + return "X-Frostfs-TLS-Termination" +} + func prepareHandlerContext(t *testing.T) *handlerContext { hc, err := prepareHandlerContextBase(layer.DefaultCachesConfigs(zap.NewExample())) require.NoError(t, err) diff --git a/api/handler/head.go b/api/handler/head.go index 314adfe2..8556e4b7 100644 --- a/api/handler/head.go +++ b/api/handler/head.go @@ -51,7 +51,7 @@ func (h *handler) HeadObjectHandler(w http.ResponseWriter, r *http.Request) { } info := extendedInfo.ObjectInfo - encryptionParams, err := formEncryptionParams(r) + encryptionParams, err := h.formEncryptionParams(r) if err != nil { h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err) return diff --git a/api/handler/multipart_upload.go b/api/handler/multipart_upload.go index bb109275..73014853 100644 --- a/api/handler/multipart_upload.go +++ b/api/handler/multipart_upload.go @@ -138,7 +138,7 @@ func (h *handler) CreateMultipartUploadHandler(w http.ResponseWriter, r *http.Re } } - p.Info.Encryption, err = formEncryptionParams(r) + p.Info.Encryption, err = h.formEncryptionParams(r) if err != nil { h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err, additional...) return @@ -223,7 +223,7 @@ func (h *handler) UploadPartHandler(w http.ResponseWriter, r *http.Request) { ContentSHA256Hash: r.Header.Get(api.AmzContentSha256), } - p.Info.Encryption, err = formEncryptionParams(r) + p.Info.Encryption, err = h.formEncryptionParams(r) if err != nil { h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err, additional...) return @@ -323,7 +323,7 @@ func (h *handler) UploadPartCopy(w http.ResponseWriter, r *http.Request) { return } - srcEncryptionParams, err := formCopySourceEncryptionParams(r) + srcEncryptionParams, err := h.formCopySourceEncryptionParams(r) if err != nil { h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err) return @@ -348,7 +348,7 @@ func (h *handler) UploadPartCopy(w http.ResponseWriter, r *http.Request) { Range: srcRange, } - p.Info.Encryption, err = formEncryptionParams(r) + p.Info.Encryption, err = h.formEncryptionParams(r) if err != nil { h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err, additional...) return @@ -593,7 +593,7 @@ func (h *handler) ListPartsHandler(w http.ResponseWriter, r *http.Request) { PartNumberMarker: partNumberMarker, } - p.Info.Encryption, err = formEncryptionParams(r) + p.Info.Encryption, err = h.formEncryptionParams(r) if err != nil { h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err) return @@ -629,7 +629,7 @@ func (h *handler) AbortMultipartUploadHandler(w http.ResponseWriter, r *http.Req Key: reqInfo.ObjectName, } - p.Encryption, err = formEncryptionParams(r) + p.Encryption, err = h.formEncryptionParams(r) if err != nil { h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err) return diff --git a/api/handler/put.go b/api/handler/put.go index d08389f9..f480fe4c 100644 --- a/api/handler/put.go +++ b/api/handler/put.go @@ -228,7 +228,7 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) { metadata[api.ContentLanguage] = contentLanguage } - encryptionParams, err := formEncryptionParams(r) + encryptionParams, err := h.formEncryptionParams(r) if err != nil { h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err) return @@ -353,15 +353,15 @@ func (h *handler) getBodyReader(r *http.Request) (io.ReadCloser, error) { return chunkReader, nil } -func formEncryptionParams(r *http.Request) (enc encryption.Params, err error) { - return formEncryptionParamsBase(r, false) +func (h *handler) formEncryptionParams(r *http.Request) (enc encryption.Params, err error) { + return h.formEncryptionParamsBase(r, false) } -func formCopySourceEncryptionParams(r *http.Request) (enc encryption.Params, err error) { - return formEncryptionParamsBase(r, true) +func (h *handler) formCopySourceEncryptionParams(r *http.Request) (enc encryption.Params, err error) { + return h.formEncryptionParamsBase(r, true) } -func formEncryptionParamsBase(r *http.Request, isCopySource bool) (enc encryption.Params, err error) { +func (h *handler) formEncryptionParamsBase(r *http.Request, isCopySource bool) (enc encryption.Params, err error) { var sseCustomerAlgorithm, sseCustomerKey, sseCustomerKeyMD5 string if isCopySource { sseCustomerAlgorithm = r.Header.Get(api.AmzCopySourceServerSideEncryptionCustomerAlgorithm) @@ -377,7 +377,17 @@ func formEncryptionParamsBase(r *http.Request, isCopySource bool) (enc encryptio return } - if r.TLS == nil { + needCheckTLS := true + if tlsTerminationParam := r.Header.Get(h.cfg.TLSTerminationHeader()); len(tlsTerminationParam) > 0 { + tlsTermination, err := strconv.ParseBool(tlsTerminationParam) + if err != nil { + h.reqLogger(r.Context()).Warn(logs.WarnInvalidTypeTLSTerminationHeader, zap.Error(err)) + } else { + needCheckTLS = !tlsTermination + } + } + + if needCheckTLS && r.TLS == nil { return enc, apierr.GetAPIError(apierr.ErrInsecureSSECustomerRequest) } diff --git a/cmd/s3-gw/app.go b/cmd/s3-gw/app.go index 1ac2b305..8b301e0d 100644 --- a/cmd/s3-gw/app.go +++ b/cmd/s3-gw/app.go @@ -119,6 +119,7 @@ type ( vhsNamespacesEnabled map[string]bool retryMaxBackoff time.Duration retryStrategy handler.RetryStrategy + tlsTerminationHeader string } maxClientsConfig struct { @@ -275,6 +276,7 @@ func (s *appSettings) update(v *viper.Viper, log *zap.Logger) { httpLoggingMaxLogSize := v.GetInt(cfgHTTPLoggingMaxLogSize) httpLoggingOutputPath := v.GetString(cfgHTTPLoggingDestination) httpLoggingUseGzip := v.GetBool(cfgHTTPLoggingGzip) + tlsTerminationHeader := v.GetString(cfgSSECTLSTerminationHeader) s.mu.Lock() defer s.mu.Unlock() @@ -304,6 +306,7 @@ func (s *appSettings) update(v *viper.Viper, log *zap.Logger) { s.vhsHeader = vhsHeader s.servernameHeader = servernameHeader s.vhsNamespacesEnabled = vhsNamespacesEnabled + s.tlsTerminationHeader = tlsTerminationHeader } func (s *appSettings) prepareVHSNamespaces(v *viper.Viper, log *zap.Logger, defaultNamespaces []string) map[string]bool { @@ -498,6 +501,12 @@ func (s *appSettings) RetryStrategy() handler.RetryStrategy { return s.retryStrategy } +func (s *appSettings) TLSTerminationHeader() string { + s.mu.RLock() + defer s.mu.RUnlock() + return s.tlsTerminationHeader +} + func (s *appSettings) AccessBoxContainer() (cid.ID, bool) { if s.accessbox != nil { return *s.accessbox, true diff --git a/cmd/s3-gw/app_settings.go b/cmd/s3-gw/app_settings.go index 691e2319..66ee372a 100644 --- a/cmd/s3-gw/app_settings.go +++ b/cmd/s3-gw/app_settings.go @@ -59,9 +59,10 @@ const ( defaultAccessBoxCacheRemovingCheckInterval = 5 * time.Minute - defaultNamespaceHeader = "X-Frostfs-Namespace" - defaultVHSHeader = "X-Frostfs-S3-VHS" - defaultServernameHeader = "X-Frostfs-Servername" + defaultNamespaceHeader = "X-Frostfs-Namespace" + defaultVHSHeader = "X-Frostfs-S3-VHS" + defaultServernameHeader = "X-Frostfs-Servername" + defaultTLSTerminationHeader = "X-Frostfs-TLS-Termination" defaultMultinetFallbackDelay = 300 * time.Millisecond @@ -273,6 +274,9 @@ const ( // Settings. // Server. cfgReconnectInterval = "reconnect_interval" + // SSE-C. + cfgSSECTLSTerminationHeader = "sse_c.tls_termination_header" + // envPrefix is an environment variables prefix used for configuration. envPrefix = "S3_GW" ) @@ -909,6 +913,9 @@ func newSettings() *viper.Viper { // multinet v.SetDefault(cfgMultinetFallbackDelay, defaultMultinetFallbackDelay) + // sse-c + v.SetDefault(cfgSSECTLSTerminationHeader, defaultTLSTerminationHeader) + // Bind flags if err := bindFlags(v, flags); err != nil { panic(fmt.Errorf("bind flags: %w", err)) diff --git a/config/config.env b/config/config.env index ef6a27b1..b0fbb777 100644 --- a/config/config.env +++ b/config/config.env @@ -263,3 +263,6 @@ S3_GW_MULTINET_FALLBACK_DELAY=300ms # List of subnets and IP addresses to use as source for those subnets S3_GW_MULTINET_SUBNETS_1_MASK=1.2.3.4/24 S3_GW_MULTINET_SUBNETS_1_SOURCE_IPS=1.2.3.4 1.2.3.5 + +# Header for determining the termination of TLS. +S3_GW_SSE_C_TLS_TERMINATION_TLS_HEADER=X-Frostfs-TLS-Termination diff --git a/config/config.yaml b/config/config.yaml index 051f5f77..04792f27 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -311,3 +311,6 @@ multinet: source_ips: - 1.2.3.4 - 1.2.3.5 + +sse_c: + tls_termination_header: X-Frostfs-TLS-Termination diff --git a/docs/configuration.md b/docs/configuration.md index 70b2f332..c0861b53 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -196,6 +196,7 @@ There are some custom types used for brevity: | `containers` | [Containers configuration](#containers-section) | | `vhs` | [VHS configuration](#vhs-section) | | `multinet` | [Multinet configuration](#multinet-section) | +| `sse_c` | [SSE-C configuration](#sse_c-section) | ### General section @@ -674,7 +675,7 @@ web: | Parameter | Type | SIGHUP reload | Default value | Description | |-----------------------|------------|---------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `read_timeout` | `duration` | no | `0` | The maximum duration for reading the entire request, including the body. A zero or negative value means there will be no timeout. | +| `read_timeout` | `duration` | no | `0` | The maximum duration for reading the entire request, including the body. A zero or negative value means there will be no timeout. | | `read_header_timeout` | `duration` | no | `30s` | The amount of time allowed to read request headers. If `read_header_timeout` is zero, the value of `read_timeout` is used. If both are zero, there is no timeout. | | `write_timeout` | `duration` | no | `0` | The maximum duration before timing out writes of the response. A zero or negative value means there will be no timeout. | | `idle_timeout` | `duration` | no | `30s` | The maximum amount of time to wait for the next request when keep-alives are enabled. If `idle_timeout` is zero, the value of `read_timeout` is used. If both are zero, there is no timeout. | @@ -851,3 +852,16 @@ multinet: |--------------|------------|---------------|---------------|----------------------------------------------------------------------| | `mask` | `string` | yes | | Destination subnet. | | `source_ips` | `[]string` | yes | | Array of source IP addresses to use when dialing destination subnet. | + +# `sse_c` section + +Configuration of SSE-C. + +```yaml +sse_c: + tls_termination_header: X-Frostfs-TLS-Termination +``` + +| Parameter | Type | SIGHUP reload | Default value | Description | +| ------------------------ | -------- | ------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `tls_termination_header` | `string` | yes | `X-Frostfs-TLS-Termination` | The header for determining whether TLS needs to be checked. If the system requests come through a proxy server and TLS can terminate at the proxy level, you should use this header to disable TLS verification at SSE-C. | diff --git a/internal/logs/logs.go b/internal/logs/logs.go index f8047a2d..5036403d 100644 --- a/internal/logs/logs.go +++ b/internal/logs/logs.go @@ -178,4 +178,5 @@ const ( MultinetDialSuccess = "multinet dial successful" MultinetDialFail = "multinet dial failed" FailedToParseHTTPTime = "failed to parse http time, header is ignored" + WarnInvalidTypeTLSTerminationHeader = "invalid type of value of tls termination header" )