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
This commit is contained in:
Nicola Murino 2020-03-01 23:12:28 +01:00
parent b885d453a2
commit 833b702b90
6 changed files with 57 additions and 9 deletions

View file

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

View file

@ -63,6 +63,7 @@ func init() {
EnabledSSHCommands: sftpd.GetDefaultSSHCommands(),
KeyboardInteractiveProgram: "",
ProxyProtocol: 0,
ProxyAllowed: []string{},
},
ProviderConf: dataprovider.Config{
Driver: "sqlite",

View file

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

View file

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

View file

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

View file

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