[#449] Add support headers for vhs and servername
All checks were successful
/ DCO (pull_request) Successful in 1m9s
/ Vulncheck (pull_request) Successful in 1m11s
/ Builds (1.21) (pull_request) Successful in 1m20s
/ Builds (1.22) (pull_request) Successful in 1m23s
/ Lint (pull_request) Successful in 1m35s
/ Tests (1.21) (pull_request) Successful in 1m35s
/ Tests (1.22) (pull_request) Successful in 1m36s

Signed-off-by: Roman Loginov <r.loginov@yadro.com>
This commit is contained in:
Roman Loginov 2024-08-01 16:24:47 +03:00
parent 94070b107b
commit a7187ad956
9 changed files with 139 additions and 19 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -736,7 +736,9 @@ vhs:
"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 banquets 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. |