[#449] Add support headers for vhs and servername
All checks were successful
/ DCO (pull_request) Successful in 1m5s
/ Vulncheck (pull_request) Successful in 1m14s
/ Builds (1.21) (pull_request) Successful in 1m27s
/ Builds (1.22) (pull_request) Successful in 1m21s
/ Lint (pull_request) Successful in 2m0s
/ Tests (1.21) (pull_request) Successful in 1m46s
/ Tests (1.22) (pull_request) Successful in 1m40s
All checks were successful
/ DCO (pull_request) Successful in 1m5s
/ Vulncheck (pull_request) Successful in 1m14s
/ Builds (1.21) (pull_request) Successful in 1m27s
/ Builds (1.22) (pull_request) Successful in 1m21s
/ Lint (pull_request) Successful in 2m0s
/ Tests (1.21) (pull_request) Successful in 1m46s
/ Tests (1.22) (pull_request) Successful in 1m40s
Signed-off-by: Roman Loginov <r.loginov@yadro.com>
This commit is contained in:
parent
94070b107b
commit
e906ec99de
9 changed files with 141 additions and 19 deletions
|
@ -5,7 +5,7 @@ This document outlines major changes between releases.
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Add support for virtual hosted style addressing (#446)
|
- Add support for virtual hosted style addressing (#446, #449)
|
||||||
|
|
||||||
## [0.30.0] - Kangshung -2024-07-19
|
## [0.30.0] - Kangshung -2024-07-19
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ package middleware
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||||
|
@ -14,6 +15,8 @@ const wildcardPlaceholder = "<wildcard>"
|
||||||
type VHSSettings interface {
|
type VHSSettings interface {
|
||||||
Domains() []string
|
Domains() []string
|
||||||
GlobalVHS() bool
|
GlobalVHS() bool
|
||||||
|
VHSHeader() string
|
||||||
|
ServernameHeader() string
|
||||||
VHSNamespacesEnabled() map[string]bool
|
VHSNamespacesEnabled() map[string]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,8 +26,9 @@ func PrepareAddressStyle(settings VHSSettings, log *zap.Logger) Func {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
reqInfo := GetReqInfo(ctx)
|
reqInfo := GetReqInfo(ctx)
|
||||||
reqLogger := reqLogOrDefault(ctx, log)
|
reqLogger := reqLogOrDefault(ctx, log)
|
||||||
|
headerVHSEnabled := r.Header.Get(settings.VHSHeader())
|
||||||
|
|
||||||
if isVHSAddress(settings.GlobalVHS(), settings.VHSNamespacesEnabled(), reqInfo.Namespace) {
|
if isVHSAddress(headerVHSEnabled, settings.GlobalVHS(), settings.VHSNamespacesEnabled(), reqInfo.Namespace) {
|
||||||
prepareVHSAddress(reqInfo, r, settings)
|
prepareVHSAddress(reqInfo, r, settings)
|
||||||
} else {
|
} else {
|
||||||
preparePathStyleAddress(reqInfo, r, reqLogger)
|
preparePathStyleAddress(reqInfo, r, reqLogger)
|
||||||
|
@ -35,9 +39,12 @@ func PrepareAddressStyle(settings VHSSettings, log *zap.Logger) Func {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func isVHSAddress(enabledFlag bool, vhsNamespaces map[string]bool, namespace string) bool {
|
func isVHSAddress(headerVHSEnabled string, enabledFlag bool, vhsNamespaces map[string]bool, namespace string) bool {
|
||||||
result := enabledFlag
|
if result, err := strconv.ParseBool(headerVHSEnabled); err == nil {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
result := enabledFlag
|
||||||
if v, ok := vhsNamespaces[namespace]; ok {
|
if v, ok := vhsNamespaces[namespace]; ok {
|
||||||
result = v
|
result = v
|
||||||
}
|
}
|
||||||
|
@ -47,7 +54,7 @@ func isVHSAddress(enabledFlag bool, vhsNamespaces map[string]bool, namespace str
|
||||||
|
|
||||||
func prepareVHSAddress(reqInfo *ReqInfo, r *http.Request, settings VHSSettings) {
|
func prepareVHSAddress(reqInfo *ReqInfo, r *http.Request, settings VHSSettings) {
|
||||||
reqInfo.RequestVHSEnabled = true
|
reqInfo.RequestVHSEnabled = true
|
||||||
bktName, match := checkDomain(r.Host, settings.Domains())
|
bktName, match := checkDomain(r.Host, getDomains(r, settings))
|
||||||
if match {
|
if match {
|
||||||
if bktName == "" {
|
if bktName == "" {
|
||||||
reqInfo.RequestType = noneType
|
reqInfo.RequestType = noneType
|
||||||
|
@ -74,6 +81,14 @@ func prepareVHSAddress(reqInfo *ReqInfo, r *http.Request, settings VHSSettings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getDomains(r *http.Request, settings VHSSettings) []string {
|
||||||
|
if headerServername := r.Header.Get(settings.ServernameHeader()); headerServername != "" {
|
||||||
|
return []string{headerServername}
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings.Domains()
|
||||||
|
}
|
||||||
|
|
||||||
func preparePathStyleAddress(reqInfo *ReqInfo, r *http.Request, reqLogger *zap.Logger) {
|
func preparePathStyleAddress(reqInfo *ReqInfo, r *http.Request, reqLogger *zap.Logger) {
|
||||||
bktObj := strings.TrimPrefix(r.URL.Path, "/")
|
bktObj := strings.TrimPrefix(r.URL.Path, "/")
|
||||||
if bktObj == "" {
|
if bktObj == "" {
|
||||||
|
|
|
@ -10,6 +10,11 @@ import (
|
||||||
"go.uber.org/zap/zaptest"
|
"go.uber.org/zap/zaptest"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
FrostfsVHSHeader = "X-Frostfs-S3-VHS"
|
||||||
|
FrostfsServernameHeader = "X-Frostfs-Servername"
|
||||||
|
)
|
||||||
|
|
||||||
type VHSSettingsMock struct {
|
type VHSSettingsMock struct {
|
||||||
domains []string
|
domains []string
|
||||||
}
|
}
|
||||||
|
@ -22,17 +27,26 @@ func (v *VHSSettingsMock) GlobalVHS() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *VHSSettingsMock) VHSHeader() string {
|
||||||
|
return FrostfsVHSHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *VHSSettingsMock) ServernameHeader() string {
|
||||||
|
return FrostfsServernameHeader
|
||||||
|
}
|
||||||
|
|
||||||
func (v *VHSSettingsMock) VHSNamespacesEnabled() map[string]bool {
|
func (v *VHSSettingsMock) VHSNamespacesEnabled() map[string]bool {
|
||||||
return make(map[string]bool)
|
return make(map[string]bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsVHSAddress(t *testing.T) {
|
func TestIsVHSAddress(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
vhsEnabledFlag bool
|
headerVHSEnabled string
|
||||||
vhsNamespaced map[string]bool
|
vhsEnabledFlag bool
|
||||||
namespace string
|
vhsNamespaced map[string]bool
|
||||||
expected bool
|
namespace string
|
||||||
|
expected bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "vhs disabled",
|
name: "vhs disabled",
|
||||||
|
@ -60,9 +74,29 @@ func TestIsVHSAddress(t *testing.T) {
|
||||||
namespace: "kapusta",
|
namespace: "kapusta",
|
||||||
expected: true,
|
expected: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "vhs enabled (header)",
|
||||||
|
headerVHSEnabled: "true",
|
||||||
|
vhsEnabledFlag: false,
|
||||||
|
vhsNamespaced: map[string]bool{
|
||||||
|
"kapusta": false,
|
||||||
|
},
|
||||||
|
namespace: "kapusta",
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "vhs disabled (header)",
|
||||||
|
headerVHSEnabled: "false",
|
||||||
|
vhsEnabledFlag: true,
|
||||||
|
vhsNamespaced: map[string]bool{
|
||||||
|
"kapusta": true,
|
||||||
|
},
|
||||||
|
namespace: "kapusta",
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
actual := isVHSAddress(tc.vhsEnabledFlag, tc.vhsNamespaced, tc.namespace)
|
actual := isVHSAddress(tc.headerVHSEnabled, tc.vhsEnabledFlag, tc.vhsNamespaced, tc.namespace)
|
||||||
require.Equal(t, tc.expected, actual)
|
require.Equal(t, tc.expected, actual)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -383,3 +417,27 @@ func TestCheckDomains(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetDomains(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||||
|
settings := &VHSSettingsMock{
|
||||||
|
domains: []string{
|
||||||
|
"s3.domain.com",
|
||||||
|
"s3.<wildcard>.domain.com",
|
||||||
|
"domain.com",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("the request does not contain the X-Frostfs-Servername header", func(t *testing.T) {
|
||||||
|
actualDomains := getDomains(req, settings)
|
||||||
|
require.Equal(t, settings.domains, actualDomains)
|
||||||
|
})
|
||||||
|
|
||||||
|
serverName := "domain.com"
|
||||||
|
req.Header.Set(settings.ServernameHeader(), serverName)
|
||||||
|
|
||||||
|
t.Run("the request contains the X-Frostfs-Servername header", func(t *testing.T) {
|
||||||
|
actualDomains := getDomains(req, settings)
|
||||||
|
require.Equal(t, []string{serverName}, actualDomains)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -23,7 +23,11 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
const FrostfsNamespaceHeader = "X-Frostfs-Namespace"
|
const (
|
||||||
|
FrostfsNamespaceHeader = "X-Frostfs-Namespace"
|
||||||
|
FrostfsVHSHeader = "X-Frostfs-S3-VHS"
|
||||||
|
FrostfsServernameHeader = "X-Frostfs-Servername"
|
||||||
|
)
|
||||||
|
|
||||||
type poolStatisticMock struct {
|
type poolStatisticMock struct {
|
||||||
}
|
}
|
||||||
|
@ -102,6 +106,14 @@ func (r *middlewareSettingsMock) GlobalVHS() bool {
|
||||||
return r.vhsEnabled
|
return r.vhsEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *middlewareSettingsMock) VHSHeader() string {
|
||||||
|
return FrostfsVHSHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *middlewareSettingsMock) ServernameHeader() string {
|
||||||
|
return FrostfsServernameHeader
|
||||||
|
}
|
||||||
|
|
||||||
func (r *middlewareSettingsMock) VHSNamespacesEnabled() map[string]bool {
|
func (r *middlewareSettingsMock) VHSNamespacesEnabled() map[string]bool {
|
||||||
return r.vhsNamespacesEnabled
|
return r.vhsNamespacesEnabled
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,6 +107,8 @@ type (
|
||||||
retryMaxAttempts int
|
retryMaxAttempts int
|
||||||
domains []string
|
domains []string
|
||||||
vhsEnabled bool
|
vhsEnabled bool
|
||||||
|
vhsHeader string
|
||||||
|
servernameHeader string
|
||||||
vhsNamespacesEnabled map[string]bool
|
vhsNamespacesEnabled map[string]bool
|
||||||
retryMaxBackoff time.Duration
|
retryMaxBackoff time.Duration
|
||||||
retryStrategy handler.RetryStrategy
|
retryStrategy handler.RetryStrategy
|
||||||
|
@ -252,6 +254,8 @@ func (s *appSettings) updateNamespacesSettings(v *viper.Viper, log *zap.Logger)
|
||||||
func (s *appSettings) setVHSSettings(v *viper.Viper, log *zap.Logger) {
|
func (s *appSettings) setVHSSettings(v *viper.Viper, log *zap.Logger) {
|
||||||
domains := fetchDomains(v, log)
|
domains := fetchDomains(v, log)
|
||||||
vhsEnabled := v.GetBool(cfgVHSEnabled)
|
vhsEnabled := v.GetBool(cfgVHSEnabled)
|
||||||
|
vhsHeader := v.GetString(cfgVHSHeader)
|
||||||
|
servernameHeader := v.GetString(cfgServernameHeader)
|
||||||
nsMap := fetchVHSNamespaces(v, log)
|
nsMap := fetchVHSNamespaces(v, log)
|
||||||
vhsNamespaces := make(map[string]bool, len(nsMap))
|
vhsNamespaces := make(map[string]bool, len(nsMap))
|
||||||
for ns, flag := range nsMap {
|
for ns, flag := range nsMap {
|
||||||
|
@ -263,6 +267,8 @@ func (s *appSettings) setVHSSettings(v *viper.Viper, log *zap.Logger) {
|
||||||
|
|
||||||
s.domains = domains
|
s.domains = domains
|
||||||
s.vhsEnabled = vhsEnabled
|
s.vhsEnabled = vhsEnabled
|
||||||
|
s.vhsHeader = vhsHeader
|
||||||
|
s.servernameHeader = servernameHeader
|
||||||
s.vhsNamespacesEnabled = vhsNamespaces
|
s.vhsNamespacesEnabled = vhsNamespaces
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,6 +284,18 @@ func (s *appSettings) GlobalVHS() bool {
|
||||||
return s.vhsEnabled
|
return s.vhsEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *appSettings) VHSHeader() string {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
return s.vhsHeader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *appSettings) ServernameHeader() string {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
return s.servernameHeader
|
||||||
|
}
|
||||||
|
|
||||||
func (s *appSettings) VHSNamespacesEnabled() map[string]bool {
|
func (s *appSettings) VHSNamespacesEnabled() map[string]bool {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
defer s.mu.RUnlock()
|
defer s.mu.RUnlock()
|
||||||
|
|
|
@ -54,7 +54,9 @@ const (
|
||||||
|
|
||||||
defaultAccessBoxCacheRemovingCheckInterval = 5 * time.Minute
|
defaultAccessBoxCacheRemovingCheckInterval = 5 * time.Minute
|
||||||
|
|
||||||
defaultNamespaceHeader = "X-Frostfs-Namespace"
|
defaultNamespaceHeader = "X-Frostfs-Namespace"
|
||||||
|
defaultVHSHeader = "X-Frostfs-S3-VHS"
|
||||||
|
defaultServernameHeader = "X-Frostfs-Servername"
|
||||||
|
|
||||||
defaultConstraintName = "default"
|
defaultConstraintName = "default"
|
||||||
|
|
||||||
|
@ -146,8 +148,11 @@ const ( // Settings.
|
||||||
|
|
||||||
cfgListenDomains = "listen_domains"
|
cfgListenDomains = "listen_domains"
|
||||||
|
|
||||||
cfgVHSEnabled = "vhs.enabled"
|
// VHS.
|
||||||
cfgVHSNamespaces = "vhs.namespaces"
|
cfgVHSEnabled = "vhs.enabled"
|
||||||
|
cfgVHSHeader = "vhs.vhs_header"
|
||||||
|
cfgServernameHeader = "vhs.servername_header"
|
||||||
|
cfgVHSNamespaces = "vhs.namespaces"
|
||||||
|
|
||||||
// Peers.
|
// Peers.
|
||||||
cfgPeers = "peers"
|
cfgPeers = "peers"
|
||||||
|
@ -793,6 +798,10 @@ func newSettings() *viper.Viper {
|
||||||
v.SetDefault(cfgRetryMaxAttempts, defaultRetryMaxAttempts)
|
v.SetDefault(cfgRetryMaxAttempts, defaultRetryMaxAttempts)
|
||||||
v.SetDefault(cfgRetryMaxBackoff, defaultRetryMaxBackoff)
|
v.SetDefault(cfgRetryMaxBackoff, defaultRetryMaxBackoff)
|
||||||
|
|
||||||
|
// vhs
|
||||||
|
v.SetDefault(cfgVHSHeader, defaultVHSHeader)
|
||||||
|
v.SetDefault(cfgServernameHeader, defaultServernameHeader)
|
||||||
|
|
||||||
// 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))
|
||||||
|
|
|
@ -41,6 +41,10 @@ S3_GW_LISTEN_DOMAINS="domain.com <wildcard>.domain.com"
|
||||||
|
|
||||||
# VHS enabled flag
|
# VHS enabled flag
|
||||||
S3_GW_VHS_ENABLED=false
|
S3_GW_VHS_ENABLED=false
|
||||||
|
# Header for determining whether VHS is enabled for the request
|
||||||
|
S3_GW_VHS_VHS_HEADER=X-Frostfs-S3-VHS
|
||||||
|
# Header for determining servername
|
||||||
|
S3_GW_VHS_SERVERNAME_HEADER=X-Frostfs-Servername
|
||||||
|
|
||||||
# Config file
|
# Config file
|
||||||
S3_GW_CONFIG=/path/to/config/yaml
|
S3_GW_CONFIG=/path/to/config/yaml
|
||||||
|
|
|
@ -46,6 +46,8 @@ listen_domains:
|
||||||
|
|
||||||
vhs:
|
vhs:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
vhs_header: X-Frostfs-S3-VHS
|
||||||
|
servername_header: X-Frostfs-Servername
|
||||||
namespaces:
|
namespaces:
|
||||||
"ns1": false
|
"ns1": false
|
||||||
"ns2": true
|
"ns2": true
|
||||||
|
|
|
@ -731,12 +731,16 @@ Configuration of virtual hosted addressing style.
|
||||||
```yaml
|
```yaml
|
||||||
vhs:
|
vhs:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
vhs_header: X-Frostfs-S3-VHS
|
||||||
|
servername_header: X-Frostfs-Servername
|
||||||
namespaces:
|
namespaces:
|
||||||
"ns1": false
|
"ns1": false
|
||||||
"ns2": true
|
"ns2": true
|
||||||
```
|
```
|
||||||
|
|
||||||
| Parameter | Type | SIGHUP reload | Default value | Description |
|
| Parameter | Type | SIGHUP reload | Default value | Description |
|
||||||
|--------------|-------------------|---------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------|
|
|---------------------|-------------------|---------------|------------------------|----------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `enabled` | `bool` | yes | `false` | Enables the use of virtual host addressing for banquets at the application level. |
|
| `enabled` | `bool` | yes | `false` | Enables the use of virtual host addressing for buckets at the application level. |
|
||||||
| `namespaces` | `map[string]bool` | yes | | A map in which the keys are the name of the namespace, and the values are the flag responsible for enabling VHS for the specified namespace. |
|
| `vhs_header` | `string` | yes | `X-Frostfs-S3-VHS` | Header for determining whether VHS is enabled for the request. |
|
||||||
|
| `servername_header` | `string` | yes | `X-Frostfs-Servername` | Header for determining servername. |
|
||||||
|
| `namespaces` | `map[string]bool` | yes | | A map in which the keys are the name of the namespace, and the values are the flag responsible for enabling VHS for the specified namespace. |
|
||||||
|
|
Loading…
Reference in a new issue