forked from TrueCloudLab/frostfs-s3-gw
[#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 <r.loginov@yadro.com>
This commit is contained in:
parent
3cd88d6204
commit
e1579f16c0
14 changed files with 74 additions and 22 deletions
|
@ -41,6 +41,7 @@ type (
|
||||||
RetryMaxAttempts() int
|
RetryMaxAttempts() int
|
||||||
RetryMaxBackoff() time.Duration
|
RetryMaxBackoff() time.Duration
|
||||||
RetryStrategy() RetryStrategy
|
RetryStrategy() RetryStrategy
|
||||||
|
TLSTerminationHeader() string
|
||||||
}
|
}
|
||||||
|
|
||||||
FrostFSID interface {
|
FrostFSID interface {
|
||||||
|
|
|
@ -98,7 +98,7 @@ func (h *handler) GetObjectAttributesHandler(w http.ResponseWriter, r *http.Requ
|
||||||
}
|
}
|
||||||
info := extendedInfo.ObjectInfo
|
info := extendedInfo.ObjectInfo
|
||||||
|
|
||||||
encryptionParams, err := formEncryptionParams(r)
|
encryptionParams, err := h.formEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -103,12 +103,12 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
srcObjInfo := extendedSrcObjInfo.ObjectInfo
|
srcObjInfo := extendedSrcObjInfo.ObjectInfo
|
||||||
|
|
||||||
srcEncryptionParams, err := formCopySourceEncryptionParams(r)
|
srcEncryptionParams, err := h.formCopySourceEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dstEncryptionParams, err := formEncryptionParams(r)
|
dstEncryptionParams, err := h.formEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -202,7 +202,7 @@ func (h *handler) GetObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptionParams, err := formEncryptionParams(r)
|
encryptionParams, err := h.formEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -140,6 +140,10 @@ func (c *configMock) RetryStrategy() RetryStrategy {
|
||||||
return RetryStrategyConstant
|
return RetryStrategyConstant
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *configMock) TLSTerminationHeader() string {
|
||||||
|
return "X-Frostfs-TLS-Termination"
|
||||||
|
}
|
||||||
|
|
||||||
func prepareHandlerContext(t *testing.T) *handlerContext {
|
func prepareHandlerContext(t *testing.T) *handlerContext {
|
||||||
hc, err := prepareHandlerContextBase(layer.DefaultCachesConfigs(zap.NewExample()))
|
hc, err := prepareHandlerContextBase(layer.DefaultCachesConfigs(zap.NewExample()))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -51,7 +51,7 @@ func (h *handler) HeadObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
info := extendedInfo.ObjectInfo
|
info := extendedInfo.ObjectInfo
|
||||||
|
|
||||||
encryptionParams, err := formEncryptionParams(r)
|
encryptionParams, err := h.formEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -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 {
|
if err != nil {
|
||||||
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
|
@ -223,7 +223,7 @@ func (h *handler) UploadPartHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ContentSHA256Hash: r.Header.Get(api.AmzContentSha256),
|
ContentSHA256Hash: r.Header.Get(api.AmzContentSha256),
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Info.Encryption, err = formEncryptionParams(r)
|
p.Info.Encryption, err = h.formEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
|
@ -323,7 +323,7 @@ func (h *handler) UploadPartCopy(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
srcEncryptionParams, err := formCopySourceEncryptionParams(r)
|
srcEncryptionParams, err := h.formCopySourceEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
||||||
return
|
return
|
||||||
|
@ -348,7 +348,7 @@ func (h *handler) UploadPartCopy(w http.ResponseWriter, r *http.Request) {
|
||||||
Range: srcRange,
|
Range: srcRange,
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Info.Encryption, err = formEncryptionParams(r)
|
p.Info.Encryption, err = h.formEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err, additional...)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
|
@ -593,7 +593,7 @@ func (h *handler) ListPartsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
PartNumberMarker: partNumberMarker,
|
PartNumberMarker: partNumberMarker,
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Info.Encryption, err = formEncryptionParams(r)
|
p.Info.Encryption, err = h.formEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
||||||
return
|
return
|
||||||
|
@ -629,7 +629,7 @@ func (h *handler) AbortMultipartUploadHandler(w http.ResponseWriter, r *http.Req
|
||||||
Key: reqInfo.ObjectName,
|
Key: reqInfo.ObjectName,
|
||||||
}
|
}
|
||||||
|
|
||||||
p.Encryption, err = formEncryptionParams(r)
|
p.Encryption, err = h.formEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -228,7 +228,7 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
metadata[api.ContentLanguage] = contentLanguage
|
metadata[api.ContentLanguage] = contentLanguage
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptionParams, err := formEncryptionParams(r)
|
encryptionParams, err := h.formEncryptionParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
h.logAndSendError(ctx, w, "invalid sse headers", reqInfo, err)
|
||||||
return
|
return
|
||||||
|
@ -353,15 +353,15 @@ func (h *handler) getBodyReader(r *http.Request) (io.ReadCloser, error) {
|
||||||
return chunkReader, nil
|
return chunkReader, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func formEncryptionParams(r *http.Request) (enc encryption.Params, err error) {
|
func (h *handler) formEncryptionParams(r *http.Request) (enc encryption.Params, err error) {
|
||||||
return formEncryptionParamsBase(r, false)
|
return h.formEncryptionParamsBase(r, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func formCopySourceEncryptionParams(r *http.Request) (enc encryption.Params, err error) {
|
func (h *handler) formCopySourceEncryptionParams(r *http.Request) (enc encryption.Params, err error) {
|
||||||
return formEncryptionParamsBase(r, true)
|
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
|
var sseCustomerAlgorithm, sseCustomerKey, sseCustomerKeyMD5 string
|
||||||
if isCopySource {
|
if isCopySource {
|
||||||
sseCustomerAlgorithm = r.Header.Get(api.AmzCopySourceServerSideEncryptionCustomerAlgorithm)
|
sseCustomerAlgorithm = r.Header.Get(api.AmzCopySourceServerSideEncryptionCustomerAlgorithm)
|
||||||
|
@ -377,7 +377,17 @@ func formEncryptionParamsBase(r *http.Request, isCopySource bool) (enc encryptio
|
||||||
return
|
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)
|
return enc, apierr.GetAPIError(apierr.ErrInsecureSSECustomerRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -119,6 +119,7 @@ type (
|
||||||
vhsNamespacesEnabled map[string]bool
|
vhsNamespacesEnabled map[string]bool
|
||||||
retryMaxBackoff time.Duration
|
retryMaxBackoff time.Duration
|
||||||
retryStrategy handler.RetryStrategy
|
retryStrategy handler.RetryStrategy
|
||||||
|
tlsTerminationHeader string
|
||||||
}
|
}
|
||||||
|
|
||||||
maxClientsConfig struct {
|
maxClientsConfig struct {
|
||||||
|
@ -275,6 +276,7 @@ func (s *appSettings) update(v *viper.Viper, log *zap.Logger) {
|
||||||
httpLoggingMaxLogSize := v.GetInt(cfgHTTPLoggingMaxLogSize)
|
httpLoggingMaxLogSize := v.GetInt(cfgHTTPLoggingMaxLogSize)
|
||||||
httpLoggingOutputPath := v.GetString(cfgHTTPLoggingDestination)
|
httpLoggingOutputPath := v.GetString(cfgHTTPLoggingDestination)
|
||||||
httpLoggingUseGzip := v.GetBool(cfgHTTPLoggingGzip)
|
httpLoggingUseGzip := v.GetBool(cfgHTTPLoggingGzip)
|
||||||
|
tlsTerminationHeader := v.GetString(cfgSSECTLSTerminationHeader)
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
@ -304,6 +306,7 @@ func (s *appSettings) update(v *viper.Viper, log *zap.Logger) {
|
||||||
s.vhsHeader = vhsHeader
|
s.vhsHeader = vhsHeader
|
||||||
s.servernameHeader = servernameHeader
|
s.servernameHeader = servernameHeader
|
||||||
s.vhsNamespacesEnabled = vhsNamespacesEnabled
|
s.vhsNamespacesEnabled = vhsNamespacesEnabled
|
||||||
|
s.tlsTerminationHeader = tlsTerminationHeader
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *appSettings) prepareVHSNamespaces(v *viper.Viper, log *zap.Logger, defaultNamespaces []string) map[string]bool {
|
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
|
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) {
|
func (s *appSettings) AccessBoxContainer() (cid.ID, bool) {
|
||||||
if s.accessbox != nil {
|
if s.accessbox != nil {
|
||||||
return *s.accessbox, true
|
return *s.accessbox, true
|
||||||
|
|
|
@ -59,9 +59,10 @@ const (
|
||||||
|
|
||||||
defaultAccessBoxCacheRemovingCheckInterval = 5 * time.Minute
|
defaultAccessBoxCacheRemovingCheckInterval = 5 * time.Minute
|
||||||
|
|
||||||
defaultNamespaceHeader = "X-Frostfs-Namespace"
|
defaultNamespaceHeader = "X-Frostfs-Namespace"
|
||||||
defaultVHSHeader = "X-Frostfs-S3-VHS"
|
defaultVHSHeader = "X-Frostfs-S3-VHS"
|
||||||
defaultServernameHeader = "X-Frostfs-Servername"
|
defaultServernameHeader = "X-Frostfs-Servername"
|
||||||
|
defaultTLSTerminationHeader = "X-Frostfs-TLS-Termination"
|
||||||
|
|
||||||
defaultMultinetFallbackDelay = 300 * time.Millisecond
|
defaultMultinetFallbackDelay = 300 * time.Millisecond
|
||||||
|
|
||||||
|
@ -273,6 +274,9 @@ const ( // Settings.
|
||||||
// Server.
|
// Server.
|
||||||
cfgReconnectInterval = "reconnect_interval"
|
cfgReconnectInterval = "reconnect_interval"
|
||||||
|
|
||||||
|
// SSE-C.
|
||||||
|
cfgSSECTLSTerminationHeader = "sse_c.tls_termination_header"
|
||||||
|
|
||||||
// envPrefix is an environment variables prefix used for configuration.
|
// envPrefix is an environment variables prefix used for configuration.
|
||||||
envPrefix = "S3_GW"
|
envPrefix = "S3_GW"
|
||||||
)
|
)
|
||||||
|
@ -909,6 +913,9 @@ func newSettings() *viper.Viper {
|
||||||
// multinet
|
// multinet
|
||||||
v.SetDefault(cfgMultinetFallbackDelay, defaultMultinetFallbackDelay)
|
v.SetDefault(cfgMultinetFallbackDelay, defaultMultinetFallbackDelay)
|
||||||
|
|
||||||
|
// sse-c
|
||||||
|
v.SetDefault(cfgSSECTLSTerminationHeader, defaultTLSTerminationHeader)
|
||||||
|
|
||||||
// Bind flags
|
// Bind flags
|
||||||
if err := bindFlags(v, flags); err != nil {
|
if err := bindFlags(v, flags); err != nil {
|
||||||
panic(fmt.Errorf("bind flags: %w", err))
|
panic(fmt.Errorf("bind flags: %w", err))
|
||||||
|
|
|
@ -263,3 +263,6 @@ S3_GW_MULTINET_FALLBACK_DELAY=300ms
|
||||||
# List of subnets and IP addresses to use as source for those subnets
|
# 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_MASK=1.2.3.4/24
|
||||||
S3_GW_MULTINET_SUBNETS_1_SOURCE_IPS=1.2.3.4 1.2.3.5
|
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
|
||||||
|
|
|
@ -311,3 +311,6 @@ multinet:
|
||||||
source_ips:
|
source_ips:
|
||||||
- 1.2.3.4
|
- 1.2.3.4
|
||||||
- 1.2.3.5
|
- 1.2.3.5
|
||||||
|
|
||||||
|
sse_c:
|
||||||
|
tls_termination_header: X-Frostfs-TLS-Termination
|
||||||
|
|
|
@ -196,6 +196,7 @@ There are some custom types used for brevity:
|
||||||
| `containers` | [Containers configuration](#containers-section) |
|
| `containers` | [Containers configuration](#containers-section) |
|
||||||
| `vhs` | [VHS configuration](#vhs-section) |
|
| `vhs` | [VHS configuration](#vhs-section) |
|
||||||
| `multinet` | [Multinet configuration](#multinet-section) |
|
| `multinet` | [Multinet configuration](#multinet-section) |
|
||||||
|
| `sse_c` | [SSE-C configuration](#sse_c-section) |
|
||||||
|
|
||||||
### General section
|
### General section
|
||||||
|
|
||||||
|
@ -674,7 +675,7 @@ web:
|
||||||
|
|
||||||
| Parameter | Type | SIGHUP reload | Default value | Description |
|
| 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. |
|
| `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. |
|
| `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. |
|
| `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. |
|
| `mask` | `string` | yes | | Destination subnet. |
|
||||||
| `source_ips` | `[]string` | yes | | Array of source IP addresses to use when dialing 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. |
|
||||||
|
|
|
@ -178,4 +178,5 @@ const (
|
||||||
MultinetDialSuccess = "multinet dial successful"
|
MultinetDialSuccess = "multinet dial successful"
|
||||||
MultinetDialFail = "multinet dial failed"
|
MultinetDialFail = "multinet dial failed"
|
||||||
FailedToParseHTTPTime = "failed to parse http time, header is ignored"
|
FailedToParseHTTPTime = "failed to parse http time, header is ignored"
|
||||||
|
WarnInvalidTypeTLSTerminationHeader = "invalid type of value of tls termination header"
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue