Browse Source

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
Nicola Murino 5 years ago
parent
commit
833b702b90
6 changed files with 57 additions and 9 deletions
  1. 5 1
      README.md
  2. 1 0
      config/config.go
  3. 13 2
      sftpd/internal_test.go
  4. 29 5
      sftpd/server.go
  5. 7 0
      sftpd/sftpd_test.go
  6. 2 1
      sftpgo.json

+ 5 - 1
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",

+ 1 - 0
config/config.go

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

+ 13 - 2
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")
+	}
 }

+ 29 - 5
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() {

+ 7 - 0
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) {

+ 2 - 1
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",