mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-25 00:50:31 +00:00
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:
parent
830e3d1f64
commit
7163fde724
7 changed files with 94 additions and 16 deletions
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue