[#371] Add custom Source IP header configuration #371
11 changed files with 54 additions and 13 deletions
|
@ -30,6 +30,7 @@ This document outlines major changes between releases.
|
|||
- Add new `reconnect_interval` config param (#291)
|
||||
- Support `GetBucketPolicyStatus` (#301)
|
||||
- Add FrostfsID cache (#269)
|
||||
- Add new `source_ip_header` config param (#371)
|
||||
|
||||
### Changed
|
||||
- Generalise config param `use_default_xmlns_for_complete_multipart` to `use_default_xmlns` so that use default xmlns for all requests (#221)
|
||||
|
|
|
@ -415,7 +415,7 @@ func prepareTestRequestWithQuery(hc *handlerContext, bktName, objName string, qu
|
|||
r := httptest.NewRequest(http.MethodPut, defaultURL, bytes.NewReader(body))
|
||||
r.URL.RawQuery = query.Encode()
|
||||
|
||||
reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: bktName, Object: objName})
|
||||
reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: bktName, Object: objName}, "")
|
||||
r = r.WithContext(middleware.SetReqInfo(hc.Context(), reqInfo))
|
||||
|
||||
return w, r
|
||||
|
@ -425,7 +425,7 @@ func prepareTestPayloadRequest(hc *handlerContext, bktName, objName string, payl
|
|||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodPut, defaultURL, payload)
|
||||
|
||||
reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: bktName, Object: objName})
|
||||
reqInfo := middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: bktName, Object: objName}, "")
|
||||
r = r.WithContext(middleware.SetReqInfo(hc.Context(), reqInfo))
|
||||
|
||||
return w, r
|
||||
|
|
|
@ -315,7 +315,7 @@ func TestPutBucketLockConfigurationHandler(t *testing.T) {
|
|||
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodPut, defaultURL, bytes.NewReader(body))
|
||||
r = r.WithContext(middleware.SetReqInfo(r.Context(), middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: tc.bucket})))
|
||||
r = r.WithContext(middleware.SetReqInfo(r.Context(), middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: tc.bucket}, "")))
|
||||
|
||||
hc.Handler().PutBucketObjectLockConfigHandler(w, r)
|
||||
|
||||
|
@ -388,7 +388,7 @@ func TestGetBucketLockConfigurationHandler(t *testing.T) {
|
|||
t.Run(tc.name, func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
r := httptest.NewRequest(http.MethodPut, defaultURL, bytes.NewReader(nil))
|
||||
r = r.WithContext(middleware.SetReqInfo(r.Context(), middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: tc.bucket})))
|
||||
r = r.WithContext(middleware.SetReqInfo(r.Context(), middleware.NewReqInfo(w, r, middleware.ObjectRequest{Bucket: tc.bucket}, "")))
|
||||
|
||||
hc.Handler().GetBucketObjectLockConfigHandler(w, r)
|
||||
|
||||
|
|
|
@ -351,7 +351,7 @@ func getChunkedRequest(ctx context.Context, t *testing.T, bktName, objName strin
|
|||
req.Body = io.NopCloser(reqBody)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
reqInfo := middleware.NewReqInfo(w, req, middleware.ObjectRequest{Bucket: bktName, Object: objName})
|
||||
reqInfo := middleware.NewReqInfo(w, req, middleware.ObjectRequest{Bucket: bktName, Object: objName}, "")
|
||||
req = req.WithContext(middleware.SetReqInfo(ctx, reqInfo))
|
||||
req = req.WithContext(middleware.SetBox(req.Context(), &middleware.Box{
|
||||
ClientTime: signTime,
|
||||
|
|
|
@ -83,17 +83,24 @@ var (
|
|||
)
|
||||
|
||||
// NewReqInfo returns new ReqInfo based on parameters.
|
||||
func NewReqInfo(w http.ResponseWriter, r *http.Request, req ObjectRequest) *ReqInfo {
|
||||
return &ReqInfo{
|
||||
func NewReqInfo(w http.ResponseWriter, r *http.Request, req ObjectRequest, sourceIPHeader string) *ReqInfo {
|
||||
reqInfo := &ReqInfo{
|
||||
API: req.Method,
|
||||
BucketName: req.Bucket,
|
||||
ObjectName: req.Object,
|
||||
UserAgent: r.UserAgent(),
|
||||
RemoteHost: getSourceIP(r),
|
||||
RequestID: GetRequestID(w),
|
||||
DeploymentID: deploymentID.String(),
|
||||
URL: r.URL,
|
||||
}
|
||||
|
||||
if sourceIPHeader != "" {
|
||||
reqInfo.RemoteHost = r.Header.Get(sourceIPHeader)
|
||||
} else {
|
||||
reqInfo.RemoteHost = getSourceIP(r)
|
||||
}
|
||||
|
||||
return reqInfo
|
||||
}
|
||||
|
||||
// AppendTags -- appends key/val to ReqInfo.tags.
|
||||
|
@ -193,6 +200,7 @@ func GetReqLog(ctx context.Context) *zap.Logger {
|
|||
type RequestSettings interface {
|
||||
NamespaceHeader() string
|
||||
ResolveNamespaceAlias(string) string
|
||||
SourceIPHeader() string
|
||||
}
|
||||
|
||||
func Request(log *zap.Logger, settings RequestSettings) Func {
|
||||
|
@ -211,7 +219,7 @@ func Request(log *zap.Logger, settings RequestSettings) Func {
|
|||
|
||||
// set request info into context
|
||||
// bucket name and object will be set in reqInfo later (limitation of go-chi)
|
||||
reqInfo := NewReqInfo(w, r, ObjectRequest{})
|
||||
reqInfo := NewReqInfo(w, r, ObjectRequest{}, settings.SourceIPHeader())
|
||||
reqInfo.Namespace = settings.ResolveNamespaceAlias(r.Header.Get(settings.NamespaceHeader()))
|
||||
r = r.WithContext(SetReqInfo(r.Context(), reqInfo))
|
||||
|
||||
|
@ -317,11 +325,14 @@ func getSourceIP(r *http.Request) string {
|
|||
}
|
||||
}
|
||||
|
||||
if addr != "" {
|
||||
return addr
|
||||
if addr == "" {
|
||||
addr = r.RemoteAddr
|
||||
}
|
||||
|
||||
// Default to remote address if headers not set.
|
||||
addr, _, _ = net.SplitHostPort(r.RemoteAddr)
|
||||
return addr
|
||||
raddr, _, _ := net.SplitHostPort(addr)
|
||||
if raddr == "" {
|
||||
return addr
|
||||
}
|
||||
return raddr
|
||||
}
|
||||
|
|
|
@ -64,6 +64,10 @@ type middlewareSettingsMock struct {
|
|||
aclEnabled bool
|
||||
}
|
||||
|
||||
func (r *middlewareSettingsMock) SourceIPHeader() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (r *middlewareSettingsMock) NamespaceHeader() string {
|
||||
return FrostfsNamespaceHeader
|
||||
}
|
||||
|
|
|
@ -109,6 +109,7 @@ type (
|
|||
defaultNamespaces []string
|
||||
authorizedControlAPIKeys [][]byte
|
||||
policyDenyByDefault bool
|
||||
sourceIPHeader string
|
||||
}
|
||||
|
||||
maxClientsConfig struct {
|
||||
|
@ -235,6 +236,7 @@ func (s *appSettings) update(v *viper.Viper, log *zap.Logger, key *keys.PrivateK
|
|||
s.setMD5Enabled(v.GetBool(cfgMD5Enabled))
|
||||
s.setAuthorizedControlAPIKeys(append(fetchAuthorizedKeys(log, v), key.PublicKey()))
|
||||
s.setPolicyDenyByDefault(v.GetBool(cfgPolicyDenyByDefault))
|
||||
s.setSourceIPHeader(v.GetString(cfgSourceIPHeader))
|
||||
}
|
||||
|
||||
func (s *appSettings) updateNamespacesSettings(v *viper.Viper, log *zap.Logger) {
|
||||
|
@ -429,6 +431,18 @@ func (s *appSettings) setPolicyDenyByDefault(policyDenyByDefault bool) {
|
|||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *appSettings) setSourceIPHeader(header string) {
|
||||
s.mu.Lock()
|
||||
s.sourceIPHeader = header
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *appSettings) SourceIPHeader() string {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.sourceIPHeader
|
||||
}
|
||||
|
||||
func (a *App) initAPI(ctx context.Context) {
|
||||
a.initLayer(ctx)
|
||||
a.initHandler()
|
||||
|
|
|
@ -181,6 +181,8 @@ const ( // Settings.
|
|||
// Namespaces.
|
||||
cfgNamespacesConfig = "namespaces.config"
|
||||
|
||||
cfgSourceIPHeader = "source_ip_header"
|
||||
|
||||
// Command line args.
|
||||
cmdHelp = "help"
|
||||
cmdVersion = "version"
|
||||
|
|
|
@ -220,3 +220,6 @@ S3_GW_PROXY_CONTRACT=proxy.frostfs
|
|||
|
||||
# Namespaces configuration
|
||||
S3_GW_NAMESPACES_CONFIG=namespaces.json
|
||||
|
||||
# Custom header to retrieve Source IP
|
||||
S3_GW_SOURCE_IP_HEADER=Source-Ip
|
||||
|
|
|
@ -259,3 +259,6 @@ proxy:
|
|||
|
||||
namespaces:
|
||||
config: namespaces.json
|
||||
|
||||
# Custom header to retrieve Source IP
|
||||
source_ip_header: "Source-Ip"
|
||||
|
|
|
@ -220,6 +220,8 @@ allowed_access_key_id_prefixes:
|
|||
- 3stjWenX15YwYzczMr88gy3CQr4NYFBQ8P7keGzH5QFn
|
||||
|
||||
reconnect_interval: 1m
|
||||
|
||||
source_ip_header: "Source-Ip"
|
||||
```
|
||||
|
||||
| Parameter | Type | SIGHUP reload | Default value | Description |
|
||||
|
@ -236,6 +238,7 @@ reconnect_interval: 1m
|
|||
| `max_clients_deadline` | `duration` | no | `30s` | Deadline after which the gate sends error `RequestTimeout` to a client. |
|
||||
| `allowed_access_key_id_prefixes` | `[]string` | no | | List of allowed `AccessKeyID` prefixes which S3 GW serve. If the parameter is omitted, all `AccessKeyID` will be accepted. |
|
||||
| `reconnect_interval` | `duration` | no | `1m` | Listeners reconnection interval. |
|
||||
| `source_ip_header` | `string` | yes | | Custom header to retrieve Source IP. |
|
||||
dkirillov marked this conversation as resolved
Outdated
|
||||
|
||||
### `wallet` section
|
||||
|
||||
|
|
Loading…
Reference in a new issue
Please, mention this param in changelog