[#562] Add tests for form encryption params
All checks were successful
/ DCO (pull_request) Successful in 2m52s
/ Vulncheck (pull_request) Successful in 2m59s
/ Builds (pull_request) Successful in 2m7s
/ Lint (pull_request) Successful in 3m16s
/ Tests (pull_request) Successful in 2m9s

Signed-off-by: Roman Loginov <r.loginov@yadro.com>
This commit is contained in:
Roman Loginov 2024-12-04 13:45:52 +03:00
parent a5614278a7
commit d6c451c782
7 changed files with 196 additions and 15 deletions

View file

@ -381,7 +381,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
}

View file

@ -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=="

View file

@ -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()

View file

@ -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 {

View file

@ -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

View file

@ -319,5 +319,5 @@ multinet:
- 1.2.3.4
- 1.2.3.5
sse_c:
encryption:
tls_termination_header: X-Frostfs-TLS-Termination

View file

@ -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. |