mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-21 23:20:24 +00:00
Support for HAProxy PROXY protocol
you can proxy and/or load balance the SFTP/SCP service without losing the information about the client's address.
This commit is contained in:
parent
637463a068
commit
830e3d1f64
8 changed files with 95 additions and 23 deletions
|
@ -5,7 +5,8 @@ os:
|
|||
- osx
|
||||
|
||||
go:
|
||||
- "1.13.x"
|
||||
- 1.13.x
|
||||
- 1.14.x
|
||||
|
||||
env:
|
||||
- GO111MODULE=on
|
||||
|
|
|
@ -27,6 +27,7 @@ Full featured and highly configurable SFTP server
|
|||
- SCP and rsync are supported.
|
||||
- Support for serving local filesystem, S3 Compatible Object Storage and Google Cloud Storage over SFTP/SCP.
|
||||
- Prometheus metrics are exposed.
|
||||
- Support for HAProxy PROXY protocol: you can proxy and/or load balance the SFTP/SCP service without losing the information about the client's address.
|
||||
- REST API for users management, backup, restore and real time reports of the active connections with possibility of forcibly closing a connection.
|
||||
- Web based interface to easily manage users and connections.
|
||||
- Easy migration from Linux system user accounts.
|
||||
|
@ -159,6 +160,7 @@ 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.
|
||||
- `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.
|
||||
- `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
|
||||
- **"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.
|
||||
|
@ -219,7 +221,8 @@ Here is a full example showing the default config in JSON format:
|
|||
"login_banner_file": "",
|
||||
"setstat_mode": 0,
|
||||
"enabled_ssh_commands": ["md5sum", "sha1sum", "cd", "pwd"],
|
||||
"keyboard_interactive_auth_program": ""
|
||||
"keyboard_interactive_auth_program": "",
|
||||
"proxy_protocol": 0
|
||||
},
|
||||
"data_provider": {
|
||||
"driver": "sqlite",
|
||||
|
@ -909,6 +912,7 @@ There is an open [issue](https://github.com/drakkan/sftpgo/issues/69) with some
|
|||
- [ZeroConf](https://github.com/grandcat/zeroconf)
|
||||
- [SB Admin 2](https://github.com/BlackrockDigital/startbootstrap-sb-admin-2)
|
||||
- [shlex](https://github.com/google/shlex)
|
||||
- [go-proxyproto](https://github.com/pires/go-proxyproto)
|
||||
|
||||
Some code was initially taken from [Pterodactyl sftp server](https://github.com/pterodactyl/sftp-server)
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@ func init() {
|
|||
LoginBannerFile: "",
|
||||
EnabledSSHCommands: sftpd.GetDefaultSSHCommands(),
|
||||
KeyboardInteractiveProgram: "",
|
||||
ProxyProtocol: 0,
|
||||
},
|
||||
ProviderConf: dataprovider.Config{
|
||||
Driver: "sqlite",
|
||||
|
|
1
go.mod
1
go.mod
|
@ -17,6 +17,7 @@ require (
|
|||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||
github.com/nathanaelle/password v1.0.0
|
||||
github.com/pelletier/go-toml v1.6.0 // indirect
|
||||
github.com/pires/go-proxyproto v0.0.0-20200213100827-833e5d06d8f0
|
||||
github.com/pkg/sftp v1.11.0
|
||||
github.com/prometheus/client_golang v1.4.1
|
||||
github.com/prometheus/procfs v0.0.10 // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -164,6 +164,8 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn
|
|||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
|
||||
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
|
||||
github.com/pires/go-proxyproto v0.0.0-20200213100827-833e5d06d8f0 h1:iIf32tEZ9PiKn9rPH6kloHsKshbnv8rZ8NWPfJjXj7o=
|
||||
github.com/pires/go-proxyproto v0.0.0-20200213100827-833e5d06d8f0/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
|
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/drakkan/sftpgo/logger"
|
||||
"github.com/drakkan/sftpgo/metrics"
|
||||
"github.com/drakkan/sftpgo/utils"
|
||||
"github.com/pires/go-proxyproto"
|
||||
"github.com/pkg/sftp"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
@ -99,6 +100,15 @@ type Configuration struct {
|
|||
// Absolute path to an external program to use for keyboard interactive authentication.
|
||||
// Leave empty to disable this authentication mode.
|
||||
KeyboardInteractiveProgram string `json:"keyboard_interactive_auth_program" mapstructure:"keyboard_interactive_auth_program"`
|
||||
// Support for HAProxy PROXY protocol.
|
||||
// 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.
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// Key contains information about host keys
|
||||
|
@ -183,23 +193,38 @@ 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
|
||||
}
|
||||
var proxyListener *proxyproto.Listener
|
||||
if c.ProxyProtocol == 1 {
|
||||
proxyListener = &proxyproto.Listener{
|
||||
Listener: listener,
|
||||
}
|
||||
}
|
||||
|
||||
actions = c.Actions
|
||||
uploadMode = c.UploadMode
|
||||
setstatMode = c.SetstatMode
|
||||
logger.Info(logSender, "", "server listener registered address: %v", listener.Addr().String())
|
||||
if c.IdleTimeout > 0 {
|
||||
startIdleTimer(time.Duration(c.IdleTimeout) * time.Minute)
|
||||
}
|
||||
c.checkIdleTimer()
|
||||
|
||||
for {
|
||||
conn, _ := listener.Accept()
|
||||
if conn != nil {
|
||||
var conn net.Conn
|
||||
if proxyListener != nil {
|
||||
conn, err = proxyListener.Accept()
|
||||
} else {
|
||||
conn, err = listener.Accept()
|
||||
}
|
||||
if conn != nil && err == nil {
|
||||
go c.AcceptInboundConnection(conn, serverConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c Configuration) checkIdleTimer() {
|
||||
if c.IdleTimeout > 0 {
|
||||
startIdleTimer(time.Duration(c.IdleTimeout) * time.Minute)
|
||||
}
|
||||
}
|
||||
|
||||
func (c Configuration) configureSecurityOptions(serverConfig *ssh.ServerConfig) {
|
||||
if len(c.KexAlgorithms) > 0 {
|
||||
serverConfig.KeyExchanges = c.KexAlgorithms
|
||||
|
|
|
@ -205,6 +205,17 @@ func TestMain(m *testing.M) {
|
|||
waitTCPListening(fmt.Sprintf("%s:%d", sftpdConf.BindAddress, sftpdConf.BindPort))
|
||||
waitTCPListening(fmt.Sprintf("%s:%d", httpdConf.BindAddress, httpdConf.BindPort))
|
||||
|
||||
sftpdConf.BindPort = 2222
|
||||
sftpdConf.ProxyProtocol = 1
|
||||
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()
|
||||
os.Remove(logFilePath)
|
||||
os.Remove(loginBannerFile)
|
||||
|
@ -312,6 +323,28 @@ func TestBasicSFTPHandling(t *testing.T) {
|
|||
os.RemoveAll(user.GetHomeDir())
|
||||
}
|
||||
|
||||
func TestProxyProtocol(t *testing.T) {
|
||||
usePubKey := false
|
||||
user, _, err := httpd.AddUser(getTestUser(usePubKey), http.StatusOK)
|
||||
if err != nil {
|
||||
t.Errorf("unable to add user: %v", err)
|
||||
}
|
||||
// remove the home dir to test auto creation
|
||||
os.RemoveAll(user.HomeDir)
|
||||
client, err := getSftpClientWithAddr(user, usePubKey, "127.0.0.1:2222")
|
||||
if err != nil {
|
||||
t.Errorf("unable to create sftp client: %v", err)
|
||||
} else {
|
||||
defer client.Close()
|
||||
_, err = client.Getwd()
|
||||
if err != nil {
|
||||
t.Errorf("error mkdir: %v", err)
|
||||
}
|
||||
}
|
||||
httpd.RemoveUser(user, http.StatusOK)
|
||||
os.RemoveAll(user.GetHomeDir())
|
||||
}
|
||||
|
||||
func TestUploadResume(t *testing.T) {
|
||||
usePubKey := false
|
||||
u := getTestUser(usePubKey)
|
||||
|
@ -4626,7 +4659,7 @@ func runSSHCommand(command string, user dataprovider.User, usePubKey bool) ([]by
|
|||
return stdout.Bytes(), err
|
||||
}
|
||||
|
||||
func getSftpClient(user dataprovider.User, usePubKey bool) (*sftp.Client, error) {
|
||||
func getSftpClientWithAddr(user dataprovider.User, usePubKey bool, addr string) (*sftp.Client, error) {
|
||||
var sftpClient *sftp.Client
|
||||
config := &ssh.ClientConfig{
|
||||
User: user.Username,
|
||||
|
@ -4647,7 +4680,7 @@ func getSftpClient(user dataprovider.User, usePubKey bool) (*sftp.Client, error)
|
|||
config.Auth = []ssh.AuthMethod{ssh.Password(defaultPassword)}
|
||||
}
|
||||
}
|
||||
conn, err := ssh.Dial("tcp", sftpServerAddr, config)
|
||||
conn, err := ssh.Dial("tcp", addr, config)
|
||||
if err != nil {
|
||||
return sftpClient, err
|
||||
}
|
||||
|
@ -4655,6 +4688,10 @@ func getSftpClient(user dataprovider.User, usePubKey bool) (*sftp.Client, error)
|
|||
return sftpClient, err
|
||||
}
|
||||
|
||||
func getSftpClient(user dataprovider.User, usePubKey bool) (*sftp.Client, error) {
|
||||
return getSftpClientWithAddr(user, usePubKey, sftpServerAddr)
|
||||
}
|
||||
|
||||
func getKeyboardInteractiveSftpClient(user dataprovider.User, answers []string) (*sftp.Client, error) {
|
||||
var sftpClient *sftp.Client
|
||||
config := &ssh.ClientConfig{
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
"login_banner_file": "",
|
||||
"setstat_mode": 0,
|
||||
"enabled_ssh_commands": ["md5sum", "sha1sum", "cd", "pwd"],
|
||||
"keyboard_interactive_auth_program": ""
|
||||
"keyboard_interactive_auth_program": "",
|
||||
"proxy_protocol": 0
|
||||
},
|
||||
"data_provider": {
|
||||
"driver": "sqlite",
|
||||
|
|
Loading…
Reference in a new issue