From 833b702b902143a4bb823c9dbbbf42aaa73cf524 Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Sun, 1 Mar 2020 23:12:28 +0100 Subject: [PATCH] proxy protocol: add list of allowed IP addresses and IP ranges "proxy_allowed" setting allows to specify the allowed IP address and IP ranges that can send the proxy header. This setting combined with "proxy_protocol" allows to ignore the header or to reject connections that send the proxy header from a non listed IP --- README.md | 6 +++++- config/config.go | 1 + sftpd/internal_test.go | 15 +++++++++++++-- sftpd/server.go | 34 +++++++++++++++++++++++++++++----- sftpd/sftpd_test.go | 7 +++++++ sftpgo.json | 3 ++- 6 files changed, 57 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 9333db9b..c83897b3 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,9 @@ The `sftpgo` configuration file contains the following sections: - 0, disabled - 1, enabled. Proxy header will be used and requests without proxy header will be accepted - 2, required. Proxy header will be used and requests without proxy header will be rejected + - `proxy_allowed`, List of IP addresses and IP ranges allowed to send the proxy header: + - If `proxy_protocol` is set to 1 and we receive a proxy header from an IP that is not in the list then the connection will be accepted and the header will be ignored + - If `proxy_protocol` is set to 2 and we receive a proxy header from an IP that is not in the list then the connection will be rejected - **"data_provider"**, the configuration for the data provider - `driver`, string. Supported drivers are `sqlite`, `mysql`, `postgresql`, `bolt`, `memory` - `name`, string. Database name. For driver `sqlite` this can be the database name relative to the config dir or the absolute path to the SQLite database. For driver `memory` this is the (optional) path relative to the config dir or the absolute path to the users dump to load. @@ -226,7 +229,8 @@ Here is a full example showing the default config in JSON format: "setstat_mode": 0, "enabled_ssh_commands": ["md5sum", "sha1sum", "cd", "pwd"], "keyboard_interactive_auth_program": "", - "proxy_protocol": 0 + "proxy_protocol": 0, + "proxy_allowed": [] }, "data_provider": { "driver": "sqlite", diff --git a/config/config.go b/config/config.go index 0a5196c1..d0ab411c 100644 --- a/config/config.go +++ b/config/config.go @@ -63,6 +63,7 @@ func init() { EnabledSSHCommands: sftpd.GetDefaultSSHCommands(), KeyboardInteractiveProgram: "", ProxyProtocol: 0, + ProxyAllowed: []string{}, }, ProviderConf: dataprovider.Config{ Driver: "sqlite", diff --git a/sftpd/internal_test.go b/sftpd/internal_test.go index abbd3f53..e516e25e 100644 --- a/sftpd/internal_test.go +++ b/sftpd/internal_test.go @@ -1791,13 +1791,24 @@ func TestProxyProtocolVersion(t *testing.T) { c := Configuration{ ProxyProtocol: 1, } - proxyListener := c.getProxyListener(nil) + proxyListener, _ := c.getProxyListener(nil) if proxyListener.Policy != nil { t.Error("proxy listener policy must be nil") } c.ProxyProtocol = 2 - proxyListener = c.getProxyListener(nil) + proxyListener, _ = c.getProxyListener(nil) if proxyListener.Policy == nil { t.Error("proxy listener policy must be not nil") } + c.ProxyProtocol = 1 + c.ProxyAllowed = []string{"invalid"} + _, err := c.getProxyListener(nil) + if err == nil { + t.Error("get proxy listener with invalid IP must fail") + } + c.ProxyProtocol = 2 + _, err = c.getProxyListener(nil) + if err == nil { + t.Error("get proxy listener with invalid IP must fail") + } } diff --git a/sftpd/server.go b/sftpd/server.go index ad9a22d3..ba622c3f 100644 --- a/sftpd/server.go +++ b/sftpd/server.go @@ -115,6 +115,12 @@ type Configuration struct { // If the proxy protocol is enabled in SFTPGo then you have to enable the protocol in your proxy configuration too, // for example for HAProxy add "send-proxy" or "send-proxy-v2" to each server configuration line. ProxyProtocol int `json:"proxy_protocol" mapstructure:"proxy_protocol"` + // List of IP addresses and IP ranges allowed to send the proxy header. + // If proxy protocol is set to 1 and we receive a proxy header from an IP that is not in the list then the + // connection will be accepted and the header will be ignored. + // If proxy protocol is set to 2 and we receive a proxy header from an IP that is not in the list then the + // connection will be rejected. + ProxyAllowed []string `json:"proxy_allowed" mapstructure:"proxy_allowed"` } // Key contains information about host keys @@ -199,7 +205,11 @@ func (c Configuration) Initialize(configDir string) error { logger.Warn(logSender, "", "error starting listener on address %s:%d: %v", c.BindAddress, c.BindPort, err) return err } - proxyListener := c.getProxyListener(listener) + proxyListener, err := c.getProxyListener(listener) + if err != nil { + logger.Warn(logSender, "", "error enabling proxy listener: %v", err) + return err + } actions = c.Actions uploadMode = c.UploadMode setstatMode = c.SetstatMode @@ -219,13 +229,27 @@ func (c Configuration) Initialize(configDir string) error { } } -func (c *Configuration) getProxyListener(listener net.Listener) *proxyproto.Listener { +func (c *Configuration) getProxyListener(listener net.Listener) (*proxyproto.Listener, error) { var proxyListener *proxyproto.Listener + var err error if c.ProxyProtocol > 0 { var policyFunc func(upstream net.Addr) (proxyproto.Policy, error) + if c.ProxyProtocol == 1 && len(c.ProxyAllowed) > 0 { + policyFunc, err = proxyproto.LaxWhiteListPolicy(c.ProxyAllowed) + if err != nil { + return nil, err + } + } if c.ProxyProtocol == 2 { - policyFunc = func(upstream net.Addr) (proxyproto.Policy, error) { - return proxyproto.REQUIRE, nil + if len(c.ProxyAllowed) == 0 { + policyFunc = func(upstream net.Addr) (proxyproto.Policy, error) { + return proxyproto.REQUIRE, nil + } + } else { + policyFunc, err = proxyproto.StrictWhiteListPolicy(c.ProxyAllowed) + if err != nil { + return nil, err + } } } proxyListener = &proxyproto.Listener{ @@ -233,7 +257,7 @@ func (c *Configuration) getProxyListener(listener net.Listener) *proxyproto.List Policy: policyFunc, } } - return proxyListener + return proxyListener, nil } func (c Configuration) checkIdleTimer() { diff --git a/sftpd/sftpd_test.go b/sftpd/sftpd_test.go index 7ba91f24..25dbf5e0 100644 --- a/sftpd/sftpd_test.go +++ b/sftpd/sftpd_test.go @@ -261,6 +261,13 @@ func TestInitialization(t *testing.T) { if err == nil { t.Error("Inizialize must fail, a SFTP server should be already running") } + sftpdConf.BindPort = 4444 + sftpdConf.ProxyProtocol = 1 + sftpdConf.ProxyAllowed = []string{"1270.0.0.1"} + err = sftpdConf.Initialize(configDir) + if err == nil { + t.Error("Inizialize must fail, proxy IP allowed is invalid") + } } func TestBasicSFTPHandling(t *testing.T) { diff --git a/sftpgo.json b/sftpgo.json index b99b1ff3..84027265 100644 --- a/sftpgo.json +++ b/sftpgo.json @@ -21,7 +21,8 @@ "setstat_mode": 0, "enabled_ssh_commands": ["md5sum", "sha1sum", "cd", "pwd"], "keyboard_interactive_auth_program": "", - "proxy_protocol": 0 + "proxy_protocol": 0, + "proxy_allowed": [] }, "data_provider": { "driver": "sqlite",