[#562] Add tests for form encryption params
Signed-off-by: Roman Loginov <r.loginov@yadro.com>
This commit is contained in:
parent
a5614278a7
commit
d6c451c782
7 changed files with 196 additions and 15 deletions
|
@ -381,7 +381,7 @@ func (h *handler) formEncryptionParamsBase(r *http.Request, isCopySource bool) (
|
||||||
if tlsTerminationStr := r.Header.Get(h.cfg.TLSTerminationHeader()); len(tlsTerminationStr) > 0 {
|
if tlsTerminationStr := r.Header.Get(h.cfg.TLSTerminationHeader()); len(tlsTerminationStr) > 0 {
|
||||||
tlsTermination, err := strconv.ParseBool(tlsTerminationStr)
|
tlsTermination, err := strconv.ParseBool(tlsTerminationStr)
|
||||||
if err != nil {
|
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 {
|
} else {
|
||||||
needCheckTLS = !tlsTermination
|
needCheckTLS = !tlsTermination
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/md5"
|
"crypto/md5"
|
||||||
|
"crypto/tls"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
@ -634,6 +635,186 @@ func TestPutObjectWithContentLanguage(t *testing.T) {
|
||||||
require.Equal(t, expectedContentLanguage, w.Header().Get(api.ContentLanguage))
|
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 {
|
func postObjectBase(hc *handlerContext, ns, bktName, key, filename, content string) *httptest.ResponseRecorder {
|
||||||
policy := "eyJleHBpcmF0aW9uIjogIjIwMjUtMTItMDFUMTI6MDA6MDAuMDAwWiIsImNvbmRpdGlvbnMiOiBbCiBbInN0YXJ0cy13aXRoIiwgIiR4LWFtei1jcmVkZW50aWFsIiwgIiJdLAogWyJzdGFydHMtd2l0aCIsICIkeC1hbXotZGF0ZSIsICIiXSwKIFsic3RhcnRzLXdpdGgiLCAiJGtleSIsICIiXQpdfQ=="
|
policy := "eyJleHBpcmF0aW9uIjogIjIwMjUtMTItMDFUMTI6MDA6MDAuMDAwWiIsImNvbmRpdGlvbnMiOiBbCiBbInN0YXJ0cy13aXRoIiwgIiR4LWFtei1jcmVkZW50aWFsIiwgIiJdLAogWyJzdGFydHMtd2l0aCIsICIkeC1hbXotZGF0ZSIsICIiXSwKIFsic3RhcnRzLXdpdGgiLCAiJGtleSIsICIiXQpdfQ=="
|
||||||
|
|
||||||
|
|
|
@ -317,7 +317,7 @@ func (s *appSettings) update(v *viper.Viper, log *zap.Logger) {
|
||||||
httpLoggingUseGzip := v.GetBool(cfgHTTPLoggingGzip)
|
httpLoggingUseGzip := v.GetBool(cfgHTTPLoggingGzip)
|
||||||
tombstoneMembersSize := fetchTombstoneMembersSize(v)
|
tombstoneMembersSize := fetchTombstoneMembersSize(v)
|
||||||
tombstoneLifetime := fetchTombstoneLifetime(v)
|
tombstoneLifetime := fetchTombstoneLifetime(v)
|
||||||
tlsTerminationHeader := v.GetString(cfgSSECTLSTerminationHeader)
|
tlsTerminationHeader := v.GetString(cfgEncryptionTLSTerminationHeader)
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
|
@ -281,8 +281,8 @@ const ( // Settings.
|
||||||
// Server.
|
// Server.
|
||||||
cfgReconnectInterval = "reconnect_interval"
|
cfgReconnectInterval = "reconnect_interval"
|
||||||
|
|
||||||
// SSE-C.
|
// Encryption.
|
||||||
cfgSSECTLSTerminationHeader = "sse_c.tls_termination_header"
|
cfgEncryptionTLSTerminationHeader = "encryption.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"
|
||||||
|
@ -950,8 +950,8 @@ func newSettings() *viper.Viper {
|
||||||
// multinet
|
// multinet
|
||||||
v.SetDefault(cfgMultinetFallbackDelay, defaultMultinetFallbackDelay)
|
v.SetDefault(cfgMultinetFallbackDelay, defaultMultinetFallbackDelay)
|
||||||
|
|
||||||
// sse-c
|
// encryption
|
||||||
v.SetDefault(cfgSSECTLSTerminationHeader, defaultTLSTerminationHeader)
|
v.SetDefault(cfgEncryptionTLSTerminationHeader, defaultTLSTerminationHeader)
|
||||||
|
|
||||||
// Bind flags
|
// Bind flags
|
||||||
if err := bindFlags(v, flags); err != nil {
|
if err := bindFlags(v, flags); err != nil {
|
||||||
|
|
|
@ -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
|
S3_GW_MULTINET_SUBNETS_1_SOURCE_IPS=1.2.3.4 1.2.3.5
|
||||||
|
|
||||||
# Header for determining the termination of TLS.
|
# 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
|
||||||
|
|
|
@ -319,5 +319,5 @@ multinet:
|
||||||
- 1.2.3.4
|
- 1.2.3.4
|
||||||
- 1.2.3.5
|
- 1.2.3.5
|
||||||
|
|
||||||
sse_c:
|
encryption:
|
||||||
tls_termination_header: X-Frostfs-TLS-Termination
|
tls_termination_header: X-Frostfs-TLS-Termination
|
||||||
|
|
|
@ -196,7 +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) |
|
| `encryption` | [Encryption configuration](#encryption-section) |
|
||||||
|
|
||||||
### General section
|
### General section
|
||||||
|
|
||||||
|
@ -860,15 +860,15 @@ 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
|
# `encryption` section
|
||||||
|
|
||||||
Configuration of SSE-C.
|
Configuration of encryption.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
sse_c:
|
encryption:
|
||||||
tls_termination_header: X-Frostfs-TLS-Termination
|
tls_termination_header: X-Frostfs-TLS-Termination
|
||||||
```
|
```
|
||||||
|
|
||||||
| Parameter | Type | SIGHUP reload | Default value | Description |
|
| 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. |
|
| `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. |
|
||||||
|
|
Loading…
Reference in a new issue