proxy protocol: added an option to make the proxy header required

now we can configure SFTPGo to accept or reject requests without the proxy
header when the proxy protocol is enabled
This commit is contained in:
Nicola Murino 2020-02-29 00:02:06 +01:00
parent 830e3d1f64
commit 7163fde724
7 changed files with 94 additions and 16 deletions

View file

@ -160,7 +160,10 @@ The `sftpgo` configuration file contains the following sections:
- `git-receive-pack`, `git-upload-pack`, `git-upload-archive`. These commands enable support for Git repositories over SSH, they need to be installed and in your system's `PATH`. Git commands are not allowed inside virtual folders. - `git-receive-pack`, `git-upload-pack`, `git-upload-archive`. These commands enable support for Git repositories over SSH, they need to be installed and in your system's `PATH`. Git commands are not allowed inside virtual folders.
- `rsync`. The `rsync` command need to be installed and in your system's `PATH`. We cannot avoid that rsync create symlinks so if the user has the permission to create symlinks we add the option `--safe-links` to the received rsync command if it is not already set. This should prevent to create symlinks that point outside the home dir. If the user cannot create symlinks we add the option `--munge-links`, if it is not already set. This should make symlinks unusable (but manually recoverable). The `rsync` command interacts with the filesystem directly and it is not aware about virtual folders, so it will be automatically disabled for users with virtual folders. - `rsync`. The `rsync` command need to be installed and in your system's `PATH`. We cannot avoid that rsync create symlinks so if the user has the permission to create symlinks we add the option `--safe-links` to the received rsync command if it is not already set. This should prevent to create symlinks that point outside the home dir. If the user cannot create symlinks we add the option `--munge-links`, if it is not already set. This should make symlinks unusable (but manually recoverable). The `rsync` command interacts with the filesystem directly and it is not aware about virtual folders, so it will be automatically disabled for users with virtual folders.
- `keyboard_interactive_auth_program`, string. Absolute path to an external program to use for keyboard interactive authentication. See the "Keyboard Interactive Authentication" paragraph for more details. - `keyboard_interactive_auth_program`, string. Absolute path to an external program to use for keyboard interactive authentication. See the "Keyboard Interactive Authentication" paragraph for more details.
- `proxy_protocol`, integer. Support for [HAProxy PROXY protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt). If you are running SFTPGo behind a proxy server such as HAProxy, AWS ELB or NGNIX, you can enable the proxy protocol. It provides a convenient way to safely transport connection information such as a client's address across multiple layers of NAT or TCP proxies to get the real client IP address instead of the proxy IP. Set to 1 to enable proxy protocol, default 0. 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 - `proxy_protocol`, integer. Support for [HAProxy PROXY protocol](https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt). If you are running SFTPGo behind a proxy server such as HAProxy, AWS ELB or NGNIX, you can enable the proxy protocol. It provides a convenient way to safely transport connection information such as a client's address across multiple layers of NAT or TCP proxies to get the real client IP address instead of the proxy IP. Both protocol v1 and v2 are supported. 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. The following modes are supported:
- 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
- **"data_provider"**, the configuration for the data provider - **"data_provider"**, the configuration for the data provider
- `driver`, string. Supported drivers are `sqlite`, `mysql`, `postgresql`, `bolt`, `memory` - `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. - `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.
@ -888,7 +891,7 @@ The **connection failed logs** can be used for integration in tools such as [Fai
SFTPGo can easily saturate a Gigabit connection, on low end hardware, with no special configurations and this is generally enough for most use cases. SFTPGo can easily saturate a Gigabit connection, on low end hardware, with no special configurations and this is generally enough for most use cases.
The main bootlenecks are the encryption and the messages authentication, so if you can use a fast cipher with implicit message authentication, for example `aes128-gcm@openssh.com`, you will get a big performace boost. The main bootlenecks are the encryption and the messages authentication, so if you can use a fast cipher with implicit message authentication, for example `aes128-gcm@openssh.com`, you will get a big performance boost.
There is an open [issue](https://github.com/drakkan/sftpgo/issues/69) with some other suggestions to improve performance and some comparisons against OpenSSH. There is an open [issue](https://github.com/drakkan/sftpgo/issues/69) with some other suggestions to improve performance and some comparisons against OpenSSH.

View file

@ -172,12 +172,19 @@ func LoadConfig(configDir, configName string) error {
globalConf.SFTPD.Banner = defaultBanner globalConf.SFTPD.Banner = defaultBanner
} }
if globalConf.SFTPD.UploadMode < 0 || globalConf.SFTPD.UploadMode > 2 { if globalConf.SFTPD.UploadMode < 0 || globalConf.SFTPD.UploadMode > 2 {
err = fmt.Errorf("invalid upload_mode 0 and 1 are supported, configured: %v reset upload_mode to 0", err = fmt.Errorf("invalid upload_mode 0, 1 and 2 are supported, configured: %v reset upload_mode to 0",
globalConf.SFTPD.UploadMode) globalConf.SFTPD.UploadMode)
globalConf.SFTPD.UploadMode = 0 globalConf.SFTPD.UploadMode = 0
logger.Warn(logSender, "", "Configuration error: %v", err) logger.Warn(logSender, "", "Configuration error: %v", err)
logger.WarnToConsole("Configuration error: %v", err) logger.WarnToConsole("Configuration error: %v", err)
} }
if globalConf.SFTPD.ProxyProtocol < 0 || globalConf.SFTPD.ProxyProtocol > 2 {
err = fmt.Errorf("invalid proxy_protocol 0, 1 and 2 are supported, configured: %v reset proxy_protocol to 0",
globalConf.SFTPD.ProxyProtocol)
globalConf.SFTPD.ProxyProtocol = 0
logger.Warn(logSender, "", "Configuration error: %v", err)
logger.WarnToConsole("Configuration error: %v", err)
}
if globalConf.ProviderConf.ExternalAuthScope < 0 || globalConf.ProviderConf.ExternalAuthScope > 7 { if globalConf.ProviderConf.ExternalAuthScope < 0 || globalConf.ProviderConf.ExternalAuthScope > 7 {
err = fmt.Errorf("invalid external_auth_scope: %v reset to 0", globalConf.ProviderConf.ExternalAuthScope) err = fmt.Errorf("invalid external_auth_scope: %v reset to 0", globalConf.ProviderConf.ExternalAuthScope)
globalConf.ProviderConf.ExternalAuthScope = 0 globalConf.ProviderConf.ExternalAuthScope = 0

View file

@ -140,6 +140,27 @@ func TestInvalidCredentialsPath(t *testing.T) {
os.Remove(configFilePath) os.Remove(configFilePath)
} }
func TestInvalidProxyProtocol(t *testing.T) {
configDir := ".."
confName := tempConfigName + ".json"
configFilePath := filepath.Join(configDir, confName)
config.LoadConfig(configDir, "")
sftpdConf := config.GetSFTPDConfig()
sftpdConf.ProxyProtocol = 10
c := make(map[string]sftpd.Configuration)
c["sftpd"] = sftpdConf
jsonConf, _ := json.Marshal(c)
err := ioutil.WriteFile(configFilePath, jsonConf, 0666)
if err != nil {
t.Errorf("error saving temporary configuration")
}
err = config.LoadConfig(configDir, tempConfigName)
if err == nil {
t.Errorf("Loading configuration with invalid proxy_protocol must fail")
}
os.Remove(configFilePath)
}
func TestSetGetConfig(t *testing.T) { func TestSetGetConfig(t *testing.T) {
sftpdConf := config.GetSFTPDConfig() sftpdConf := config.GetSFTPDConfig()
sftpdConf.IdleTimeout = 3 sftpdConf.IdleTimeout = 3

View file

@ -6,7 +6,7 @@ _daemon = sftpgo
[Definition] [Definition]
# By default, first authenticate method is public_key and must be excluded from the filter to avoid false positives failed attemps # By default, first authenticate method is public_key and must be excluded from the filter to avoid false positives failed attempts
failregex = ^.*"sender":"connection_failed","client_ip":"<HOST>","username":".*","login_type":"password".*"}$ failregex = ^.*"sender":"connection_failed","client_ip":"<HOST>","username":".*","login_type":"password".*"}$
ignoreregex = ignoreregex =

View file

@ -1709,3 +1709,18 @@ func TestSFTPExtensions(t *testing.T) {
} }
sftpExtensions = initialSFTPExtensions sftpExtensions = initialSFTPExtensions
} }
func TestProxyProtocolVersion(t *testing.T) {
c := Configuration{
ProxyProtocol: 1,
}
proxyListener := c.getProxyListener(nil)
if proxyListener.Policy != nil {
t.Error("proxy listener policy must be nil")
}
c.ProxyProtocol = 2
proxyListener = c.getProxyListener(nil)
if proxyListener.Policy == nil {
t.Error("proxy listener policy must be not nil")
}
}

View file

@ -3,6 +3,7 @@ package sftpd
import ( import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -26,7 +27,10 @@ const (
defaultPrivateECDSAKeyName = "id_ecdsa" defaultPrivateECDSAKeyName = "id_ecdsa"
) )
var sftpExtensions = []string{"posix-rename@openssh.com"} var (
sftpExtensions = []string{"posix-rename@openssh.com"}
errWrongProxyProtoVersion = errors.New("unacceptable proxy protocol version")
)
// Configuration for the SFTP server // Configuration for the SFTP server
type Configuration struct { type Configuration struct {
@ -104,10 +108,12 @@ type Configuration struct {
// If you are running SFTPGo behind a proxy server such as HAProxy, AWS ELB or NGNIX, you can enable // If you are running SFTPGo behind a proxy server such as HAProxy, AWS ELB or NGNIX, you can enable
// the proxy protocol. It provides a convenient way to safely transport connection information // the proxy protocol. It provides a convenient way to safely transport connection information
// such as a client's address across multiple layers of NAT or TCP proxies to get the real // such as a client's address across multiple layers of NAT or TCP proxies to get the real
// client IP address instead of the proxy IP. // client IP address instead of the proxy IP. Both protocol v1 and v2 are supported.
// Set to 1 to enable proxy protocol. // - 0 means disabled
// You have to enable the protocol in your proxy configuration too, for example for HAProxy // - 1 means proxy protocol enabled. Proxy header will be used and requests without proxy header will be accepted.
// add "send-proxy" or "send-proxy-v2" to each server configuration line // - 2 means proxy protocol required. Proxy header will be used and requests without proxy header will be rejected.
// 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"` ProxyProtocol int `json:"proxy_protocol" mapstructure:"proxy_protocol"`
} }
@ -193,13 +199,7 @@ func (c Configuration) Initialize(configDir string) error {
logger.Warn(logSender, "", "error starting listener on address %s:%d: %v", c.BindAddress, c.BindPort, err) logger.Warn(logSender, "", "error starting listener on address %s:%d: %v", c.BindAddress, c.BindPort, err)
return err return err
} }
var proxyListener *proxyproto.Listener proxyListener := c.getProxyListener(listener)
if c.ProxyProtocol == 1 {
proxyListener = &proxyproto.Listener{
Listener: listener,
}
}
actions = c.Actions actions = c.Actions
uploadMode = c.UploadMode uploadMode = c.UploadMode
setstatMode = c.SetstatMode setstatMode = c.SetstatMode
@ -219,6 +219,23 @@ func (c Configuration) Initialize(configDir string) error {
} }
} }
func (c *Configuration) getProxyListener(listener net.Listener) *proxyproto.Listener {
var proxyListener *proxyproto.Listener
if c.ProxyProtocol > 0 {
var policyFunc func(upstream net.Addr) (proxyproto.Policy, error)
if c.ProxyProtocol == 2 {
policyFunc = func(upstream net.Addr) (proxyproto.Policy, error) {
return proxyproto.REQUIRE, nil
}
}
proxyListener = &proxyproto.Listener{
Listener: listener,
Policy: policyFunc,
}
}
return proxyListener
}
func (c Configuration) checkIdleTimer() { func (c Configuration) checkIdleTimer() {
if c.IdleTimeout > 0 { if c.IdleTimeout > 0 {
startIdleTimer(time.Duration(c.IdleTimeout) * time.Minute) startIdleTimer(time.Duration(c.IdleTimeout) * time.Minute)

View file

@ -216,6 +216,17 @@ func TestMain(m *testing.M) {
waitTCPListening(fmt.Sprintf("%s:%d", sftpdConf.BindAddress, sftpdConf.BindPort)) waitTCPListening(fmt.Sprintf("%s:%d", sftpdConf.BindAddress, sftpdConf.BindPort))
sftpdConf.BindPort = 2224
sftpdConf.ProxyProtocol = 2
go func() {
logger.Debug(logSender, "", "initializing SFTP server with config %+v", sftpdConf)
if err := sftpdConf.Initialize(configDir); err != nil {
logger.Error(logSender, "", "could not start SFTP server: %v", err)
}
}()
waitTCPListening(fmt.Sprintf("%s:%d", sftpdConf.BindAddress, sftpdConf.BindPort))
exitCode := m.Run() exitCode := m.Run()
os.Remove(logFilePath) os.Remove(logFilePath)
os.Remove(loginBannerFile) os.Remove(loginBannerFile)
@ -341,6 +352,10 @@ func TestProxyProtocol(t *testing.T) {
t.Errorf("error mkdir: %v", err) t.Errorf("error mkdir: %v", err)
} }
} }
client, err = getSftpClientWithAddr(user, usePubKey, "127.0.0.1:2224")
if err == nil {
t.Error("request without a proxy header must be rejected")
}
httpd.RemoveUser(user, http.StatusOK) httpd.RemoveUser(user, http.StatusOK)
os.RemoveAll(user.GetHomeDir()) os.RemoveAll(user.GetHomeDir())
} }