forked from TrueCloudLab/frostfs-s3-gw
[#371] Add custom Source IP header configuration
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
This commit is contained in:
parent
e22ff52165
commit
e3f03f4599
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)
|
- Add new `reconnect_interval` config param (#291)
|
||||||
- Support `GetBucketPolicyStatus` (#301)
|
- Support `GetBucketPolicyStatus` (#301)
|
||||||
- Add FrostfsID cache (#269)
|
- Add FrostfsID cache (#269)
|
||||||
|
- Add new `source_ip_header` config param (#371)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Generalise config param `use_default_xmlns_for_complete_multipart` to `use_default_xmlns` so that use default xmlns for all requests (#221)
|
- 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 := httptest.NewRequest(http.MethodPut, defaultURL, bytes.NewReader(body))
|
||||||
r.URL.RawQuery = query.Encode()
|
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))
|
r = r.WithContext(middleware.SetReqInfo(hc.Context(), reqInfo))
|
||||||
|
|
||||||
return w, r
|
return w, r
|
||||||
|
@ -425,7 +425,7 @@ func prepareTestPayloadRequest(hc *handlerContext, bktName, objName string, payl
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
r := httptest.NewRequest(http.MethodPut, defaultURL, payload)
|
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))
|
r = r.WithContext(middleware.SetReqInfo(hc.Context(), reqInfo))
|
||||||
|
|
||||||
return w, r
|
return w, r
|
||||||
|
|
|
@ -315,7 +315,7 @@ func TestPutBucketLockConfigurationHandler(t *testing.T) {
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
r := httptest.NewRequest(http.MethodPut, defaultURL, bytes.NewReader(body))
|
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)
|
hc.Handler().PutBucketObjectLockConfigHandler(w, r)
|
||||||
|
|
||||||
|
@ -388,7 +388,7 @@ func TestGetBucketLockConfigurationHandler(t *testing.T) {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
r := httptest.NewRequest(http.MethodPut, defaultURL, bytes.NewReader(nil))
|
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)
|
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)
|
req.Body = io.NopCloser(reqBody)
|
||||||
|
|
||||||
w := httptest.NewRecorder()
|
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.SetReqInfo(ctx, reqInfo))
|
||||||
req = req.WithContext(middleware.SetBox(req.Context(), &middleware.Box{
|
req = req.WithContext(middleware.SetBox(req.Context(), &middleware.Box{
|
||||||
ClientTime: signTime,
|
ClientTime: signTime,
|
||||||
|
|
|
@ -83,17 +83,24 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewReqInfo returns new ReqInfo based on parameters.
|
// NewReqInfo returns new ReqInfo based on parameters.
|
||||||
func NewReqInfo(w http.ResponseWriter, r *http.Request, req ObjectRequest) *ReqInfo {
|
func NewReqInfo(w http.ResponseWriter, r *http.Request, req ObjectRequest, sourceIPHeader string) *ReqInfo {
|
||||||
return &ReqInfo{
|
reqInfo := &ReqInfo{
|
||||||
API: req.Method,
|
API: req.Method,
|
||||||
BucketName: req.Bucket,
|
BucketName: req.Bucket,
|
||||||
ObjectName: req.Object,
|
ObjectName: req.Object,
|
||||||
UserAgent: r.UserAgent(),
|
UserAgent: r.UserAgent(),
|
||||||
RemoteHost: getSourceIP(r),
|
|
||||||
RequestID: GetRequestID(w),
|
RequestID: GetRequestID(w),
|
||||||
DeploymentID: deploymentID.String(),
|
DeploymentID: deploymentID.String(),
|
||||||
URL: r.URL,
|
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.
|
// AppendTags -- appends key/val to ReqInfo.tags.
|
||||||
|
@ -193,6 +200,7 @@ func GetReqLog(ctx context.Context) *zap.Logger {
|
||||||
type RequestSettings interface {
|
type RequestSettings interface {
|
||||||
NamespaceHeader() string
|
NamespaceHeader() string
|
||||||
ResolveNamespaceAlias(string) string
|
ResolveNamespaceAlias(string) string
|
||||||
|
SourceIPHeader() string
|
||||||
}
|
}
|
||||||
|
|
||||||
func Request(log *zap.Logger, settings RequestSettings) Func {
|
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
|
// set request info into context
|
||||||
// bucket name and object will be set in reqInfo later (limitation of go-chi)
|
// 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()))
|
reqInfo.Namespace = settings.ResolveNamespaceAlias(r.Header.Get(settings.NamespaceHeader()))
|
||||||
r = r.WithContext(SetReqInfo(r.Context(), reqInfo))
|
r = r.WithContext(SetReqInfo(r.Context(), reqInfo))
|
||||||
|
|
||||||
|
@ -317,11 +325,14 @@ func getSourceIP(r *http.Request) string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if addr != "" {
|
if addr == "" {
|
||||||
return addr
|
addr = r.RemoteAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to remote address if headers not set.
|
// Default to remote address if headers not set.
|
||||||
addr, _, _ = net.SplitHostPort(r.RemoteAddr)
|
raddr, _, _ := net.SplitHostPort(addr)
|
||||||
|
if raddr == "" {
|
||||||
return addr
|
return addr
|
||||||
}
|
}
|
||||||
|
return raddr
|
||||||
|
}
|
||||||
|
|
|
@ -64,6 +64,10 @@ type middlewareSettingsMock struct {
|
||||||
aclEnabled bool
|
aclEnabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *middlewareSettingsMock) SourceIPHeader() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (r *middlewareSettingsMock) NamespaceHeader() string {
|
func (r *middlewareSettingsMock) NamespaceHeader() string {
|
||||||
return FrostfsNamespaceHeader
|
return FrostfsNamespaceHeader
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,6 +109,7 @@ type (
|
||||||
defaultNamespaces []string
|
defaultNamespaces []string
|
||||||
authorizedControlAPIKeys [][]byte
|
authorizedControlAPIKeys [][]byte
|
||||||
policyDenyByDefault bool
|
policyDenyByDefault bool
|
||||||
|
sourceIPHeader string
|
||||||
}
|
}
|
||||||
|
|
||||||
maxClientsConfig struct {
|
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.setMD5Enabled(v.GetBool(cfgMD5Enabled))
|
||||||
s.setAuthorizedControlAPIKeys(append(fetchAuthorizedKeys(log, v), key.PublicKey()))
|
s.setAuthorizedControlAPIKeys(append(fetchAuthorizedKeys(log, v), key.PublicKey()))
|
||||||
s.setPolicyDenyByDefault(v.GetBool(cfgPolicyDenyByDefault))
|
s.setPolicyDenyByDefault(v.GetBool(cfgPolicyDenyByDefault))
|
||||||
|
s.setSourceIPHeader(v.GetString(cfgSourceIPHeader))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *appSettings) updateNamespacesSettings(v *viper.Viper, log *zap.Logger) {
|
func (s *appSettings) updateNamespacesSettings(v *viper.Viper, log *zap.Logger) {
|
||||||
|
@ -429,6 +431,18 @@ func (s *appSettings) setPolicyDenyByDefault(policyDenyByDefault bool) {
|
||||||
s.mu.Unlock()
|
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) {
|
func (a *App) initAPI(ctx context.Context) {
|
||||||
a.initLayer(ctx)
|
a.initLayer(ctx)
|
||||||
a.initHandler()
|
a.initHandler()
|
||||||
|
|
|
@ -181,6 +181,8 @@ const ( // Settings.
|
||||||
// Namespaces.
|
// Namespaces.
|
||||||
cfgNamespacesConfig = "namespaces.config"
|
cfgNamespacesConfig = "namespaces.config"
|
||||||
|
|
||||||
|
cfgSourceIPHeader = "source_ip_header"
|
||||||
|
|
||||||
// Command line args.
|
// Command line args.
|
||||||
cmdHelp = "help"
|
cmdHelp = "help"
|
||||||
cmdVersion = "version"
|
cmdVersion = "version"
|
||||||
|
|
|
@ -220,3 +220,6 @@ S3_GW_PROXY_CONTRACT=proxy.frostfs
|
||||||
|
|
||||||
# Namespaces configuration
|
# Namespaces configuration
|
||||||
S3_GW_NAMESPACES_CONFIG=namespaces.json
|
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:
|
namespaces:
|
||||||
config: namespaces.json
|
config: namespaces.json
|
||||||
|
|
||||||
|
# Custom header to retrieve Source IP
|
||||||
|
source_ip_header: "Source-Ip"
|
||||||
|
|
|
@ -220,6 +220,8 @@ allowed_access_key_id_prefixes:
|
||||||
- 3stjWenX15YwYzczMr88gy3CQr4NYFBQ8P7keGzH5QFn
|
- 3stjWenX15YwYzczMr88gy3CQr4NYFBQ8P7keGzH5QFn
|
||||||
|
|
||||||
reconnect_interval: 1m
|
reconnect_interval: 1m
|
||||||
|
|
||||||
|
source_ip_header: "Source-Ip"
|
||||||
```
|
```
|
||||||
|
|
||||||
| Parameter | Type | SIGHUP reload | Default value | Description |
|
| 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. |
|
| `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. |
|
| `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. |
|
| `reconnect_interval` | `duration` | no | `1m` | Listeners reconnection interval. |
|
||||||
|
| `source_ip_header` | `string` | yes | | Custom header to retrieve Source IP. |
|
||||||
|
|
||||||
### `wallet` section
|
### `wallet` section
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue