diff --git a/api/handler/put.go b/api/handler/put.go index cb2456733..68dbffa3f 100644 --- a/api/handler/put.go +++ b/api/handler/put.go @@ -391,7 +391,7 @@ func (h *handler) formEncryptionParamsBase(r *http.Request, isCopySource bool) ( if tlsTerminationStr := r.Header.Get(h.cfg.TLSTerminationHeader()); len(tlsTerminationStr) > 0 { tlsTermination, err := strconv.ParseBool(tlsTerminationStr) if err != nil { - h.reqLogger(r.Context()).Warn(logs.WarnInvalidTypeTLSTerminationHeader, zap.Error(err)) + h.reqLogger(r.Context()).Warn(logs.WarnInvalidTypeTLSTerminationHeader, zap.String("header", tlsTerminationStr), zap.Error(err)) } else { needCheckTLS = !tlsTermination } diff --git a/api/handler/put_test.go b/api/handler/put_test.go index fa877c863..d06fee758 100644 --- a/api/handler/put_test.go +++ b/api/handler/put_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "crypto/md5" + "crypto/tls" "encoding/base64" "encoding/hex" "encoding/json" @@ -634,6 +635,186 @@ func TestPutObjectWithContentLanguage(t *testing.T) { require.Equal(t, expectedContentLanguage, w.Header().Get(api.ContentLanguage)) } +func TestFormEncryptionParamsBase(t *testing.T) { + hc := prepareHandlerContext(t) + + userSecret := "test1customer2secret3with32char4" + expectedEncKey := []byte(userSecret) + emptyEncKey := []byte(nil) + + validAlgo := "AES256" + validKey := "dGVzdDFjdXN0b21lcjJzZWNyZXQzd2l0aDMyY2hhcjQ=" + validMD5 := "zcQmPqFhtJaxkOIg5tXm9g==" + + invalidAlgo := "TTT111" + invalidKeyBase64 := "dGVzdDFjdXN0b21lcjJzZWNyZXQzd2l0aDMyY2hhcjQ" + invalidKeySize := "dGVzdDFjdXN0b21lcjJzZWNyZXQzd2l0aA==" + invalidMD5Base64 := "zcQmPqFhtJaxkOIg5tXm9g" + invalidMD5 := "zcQmPqPhtJaxkOIg5tXm9g==" + + for _, tc := range []struct { + name string + algo string + key string + md5 string + tlsTermination string + reqWithoutTLS bool + reqWithoutSSE bool + isCopySource bool + err error + }{ + { + name: "valid requst copy source", + algo: validAlgo, + key: validKey, + md5: validMD5, + isCopySource: true, + }, + { + name: "valid request with TLS", + algo: validAlgo, + key: validKey, + md5: validMD5, + }, + { + name: "valid request without TLS and valid termination header", + algo: validAlgo, + key: validKey, + md5: validMD5, + tlsTermination: "true", + reqWithoutTLS: true, + }, + { + name: "request without tls and termination header", + algo: validAlgo, + key: validKey, + md5: validMD5, + reqWithoutTLS: true, + err: apierr.GetAPIError(apierr.ErrInsecureSSECustomerRequest), + }, + { + name: "request without tls and invalid header", + algo: validAlgo, + key: validKey, + md5: validMD5, + tlsTermination: "invalid", + reqWithoutTLS: true, + err: apierr.GetAPIError(apierr.ErrInsecureSSECustomerRequest), + }, + { + name: "missing SSE customer algorithm", + key: validKey, + md5: validMD5, + err: apierr.GetAPIError(apierr.ErrMissingSSECustomerAlgorithm), + }, + { + name: "missing SSE customer key", + algo: validAlgo, + md5: validMD5, + err: apierr.GetAPIError(apierr.ErrMissingSSECustomerKey), + }, + { + name: "invalid encryption algorithm", + algo: invalidAlgo, + key: validKey, + md5: validMD5, + err: apierr.GetAPIError(apierr.ErrInvalidEncryptionAlgorithm), + }, + { + name: "invalid base64 SSE customer key", + algo: validAlgo, + key: invalidKeyBase64, + md5: validMD5, + err: apierr.GetAPIError(apierr.ErrInvalidSSECustomerKey), + }, + { + name: "invalid base64 SSE customer parameters", + algo: validAlgo, + key: invalidKeyBase64, + md5: validMD5, + isCopySource: true, + err: apierr.GetAPIError(apierr.ErrInvalidSSECustomerParameters), + }, + { + name: "invalid size of custom key", + algo: validAlgo, + key: invalidKeySize, + md5: validMD5, + err: apierr.GetAPIError(apierr.ErrInvalidSSECustomerKey), + }, + { + name: "invalid size of custom key - copy source", + algo: validAlgo, + key: invalidKeySize, + md5: validMD5, + isCopySource: true, + err: apierr.GetAPIError(apierr.ErrInvalidSSECustomerParameters), + }, + { + name: "invalid base64 key md5 of customer", + algo: validAlgo, + key: validKey, + md5: invalidMD5Base64, + err: apierr.GetAPIError(apierr.ErrSSECustomerKeyMD5Mismatch), + }, + { + name: "invalid md5 sum key of customer", + algo: validAlgo, + key: validKey, + md5: invalidMD5, + err: apierr.GetAPIError(apierr.ErrSSECustomerKeyMD5Mismatch), + }, + { + name: "request without sse", + reqWithoutSSE: true, + }, + } { + t.Run(tc.name, func(t *testing.T) { + r := prepareRequestForEncryption(tc.algo, tc.key, tc.md5, tc.tlsTermination, tc.reqWithoutTLS, tc.reqWithoutSSE, tc.isCopySource) + + enc, err := hc.h.formEncryptionParamsBase(r, tc.isCopySource) + if tc.err != nil { + require.ErrorIs(t, tc.err, err) + return + } + + require.NoError(t, err) + + if tc.reqWithoutSSE { + require.Equal(t, emptyEncKey, enc.Key()) + } else { + require.Equal(t, expectedEncKey, enc.Key()) + } + }) + } +} + +func prepareRequestForEncryption(algo, key, md5, tlsTermination string, reqWithoutTLS, reqWithoutSSE, isCopySource bool) *http.Request { + r := httptest.NewRequest(http.MethodPost, "/", nil) + + if !reqWithoutTLS { + r.TLS = &tls.ConnectionState{} + } + + if !reqWithoutSSE { + if isCopySource { + r.Header.Set(api.AmzCopySourceServerSideEncryptionCustomerAlgorithm, algo) + r.Header.Set(api.AmzCopySourceServerSideEncryptionCustomerKey, key) + r.Header.Set(api.AmzCopySourceServerSideEncryptionCustomerKeyMD5, md5) + } else { + r.Header.Set(api.AmzServerSideEncryptionCustomerAlgorithm, algo) + r.Header.Set(api.AmzServerSideEncryptionCustomerKey, key) + r.Header.Set(api.AmzServerSideEncryptionCustomerKeyMD5, md5) + } + } + + if tlsTermination != "" { + r.Header.Set("X-Frostfs-TLS-Termination", tlsTermination) + } + + return r +} + func postObjectBase(hc *handlerContext, ns, bktName, key, filename, content string) *httptest.ResponseRecorder { policy := "eyJleHBpcmF0aW9uIjogIjIwMjUtMTItMDFUMTI6MDA6MDAuMDAwWiIsImNvbmRpdGlvbnMiOiBbCiBbInN0YXJ0cy13aXRoIiwgIiR4LWFtei1jcmVkZW50aWFsIiwgIiJdLAogWyJzdGFydHMtd2l0aCIsICIkeC1hbXotZGF0ZSIsICIiXSwKIFsic3RhcnRzLXdpdGgiLCAiJGtleSIsICIiXQpdfQ==" diff --git a/cmd/s3-gw/app.go b/cmd/s3-gw/app.go index 7af302bb2..9163eff66 100644 --- a/cmd/s3-gw/app.go +++ b/cmd/s3-gw/app.go @@ -317,7 +317,7 @@ func (s *appSettings) update(v *viper.Viper, log *zap.Logger) { httpLoggingUseGzip := v.GetBool(cfgHTTPLoggingGzip) tombstoneMembersSize := fetchTombstoneMembersSize(v) tombstoneLifetime := fetchTombstoneLifetime(v) - tlsTerminationHeader := v.GetString(cfgSSECTLSTerminationHeader) + tlsTerminationHeader := v.GetString(cfgEncryptionTLSTerminationHeader) s.mu.Lock() defer s.mu.Unlock() diff --git a/cmd/s3-gw/app_settings.go b/cmd/s3-gw/app_settings.go index 1650b6b33..87414147d 100644 --- a/cmd/s3-gw/app_settings.go +++ b/cmd/s3-gw/app_settings.go @@ -281,8 +281,8 @@ const ( // Settings. // Server. cfgReconnectInterval = "reconnect_interval" - // SSE-C. - cfgSSECTLSTerminationHeader = "sse_c.tls_termination_header" + // Encryption. + cfgEncryptionTLSTerminationHeader = "encryption.tls_termination_header" // envPrefix is an environment variables prefix used for configuration. envPrefix = "S3_GW" @@ -950,8 +950,8 @@ func newSettings() *viper.Viper { // multinet v.SetDefault(cfgMultinetFallbackDelay, defaultMultinetFallbackDelay) - // sse-c - v.SetDefault(cfgSSECTLSTerminationHeader, defaultTLSTerminationHeader) + // encryption + v.SetDefault(cfgEncryptionTLSTerminationHeader, defaultTLSTerminationHeader) // Bind flags if err := bindFlags(v, flags); err != nil { diff --git a/config/config.env b/config/config.env index 986cfdf65..d9845eee7 100644 --- a/config/config.env +++ b/config/config.env @@ -271,4 +271,4 @@ 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 +S3_GW_ENCRYPTION_TLS_TERMINATION_TLS_HEADER=X-Frostfs-TLS-Termination diff --git a/config/config.yaml b/config/config.yaml index a4fc23820..469f0af1b 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -319,5 +319,5 @@ multinet: - 1.2.3.4 - 1.2.3.5 -sse_c: +encryption: tls_termination_header: X-Frostfs-TLS-Termination diff --git a/docs/configuration.md b/docs/configuration.md index 99ebaf2e6..24f90e6df 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -196,7 +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) | +| `encryption` | [Encryption configuration](#encryption-section) | ### General section @@ -860,15 +860,15 @@ multinet: | `mask` | `string` | yes | | Destination subnet. | | `source_ips` | `[]string` | yes | | Array of source IP addresses to use when dialing destination subnet. | -# `sse_c` section +# `encryption` section -Configuration of SSE-C. +Configuration of encryption. ```yaml -sse_c: +encryption: 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. | +| 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 server-side encryption. |