mirror of
https://github.com/drakkan/sftpgo.git
synced 2025-12-06 22:30:56 +03:00
FTP: enable ftpserverlib logging and make debug mode configurable
This commit is contained in:
2
.github/workflows/development.yml
vendored
2
.github/workflows/development.yml
vendored
@@ -228,7 +228,7 @@ jobs:
|
|||||||
gzip output/man/man1/*
|
gzip output/man/man1/*
|
||||||
cp sftpgo output/
|
cp sftpgo output/
|
||||||
|
|
||||||
- uses: uraimo/run-on-arch-action@v2.0.10
|
- uses: uraimo/run-on-arch-action@v2.1.0
|
||||||
if: ${{ matrix.arch != 'amd64' }}
|
if: ${{ matrix.arch != 'amd64' }}
|
||||||
name: Build for ${{ matrix.arch }}
|
name: Build for ${{ matrix.arch }}
|
||||||
id: build
|
id: build
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -250,7 +250,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
SFTPGO_VERSION: ${{ steps.get_version.outputs.SFTPGO_VERSION }}
|
SFTPGO_VERSION: ${{ steps.get_version.outputs.SFTPGO_VERSION }}
|
||||||
|
|
||||||
- uses: uraimo/run-on-arch-action@v2.0.10
|
- uses: uraimo/run-on-arch-action@v2.1.0
|
||||||
if: ${{ matrix.arch != 'amd64' }}
|
if: ${{ matrix.arch != 'amd64' }}
|
||||||
name: Build for ${{ matrix.arch }}
|
name: Build for ${{ matrix.arch }}
|
||||||
id: build
|
id: build
|
||||||
|
|||||||
@@ -742,6 +742,12 @@ func getFTPDBindingFromEnv(idx int) {
|
|||||||
isSet = true
|
isSet = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug, ok := lookupBoolFromEnv(fmt.Sprintf("SFTPGO_FTPD__BINDINGS__%v__DEBUG", idx))
|
||||||
|
if ok {
|
||||||
|
binding.Debug = debug
|
||||||
|
isSet = true
|
||||||
|
}
|
||||||
|
|
||||||
if isSet {
|
if isSet {
|
||||||
if len(globalConf.FTPD.Bindings) > idx {
|
if len(globalConf.FTPD.Bindings) > idx {
|
||||||
globalConf.FTPD.Bindings[idx] = binding
|
globalConf.FTPD.Bindings[idx] = binding
|
||||||
|
|||||||
@@ -479,6 +479,7 @@ func TestFTPDBindingsFromEnv(t *testing.T) {
|
|||||||
os.Setenv("SFTPGO_FTPD__BINDINGS__9__TLS_MODE", "1")
|
os.Setenv("SFTPGO_FTPD__BINDINGS__9__TLS_MODE", "1")
|
||||||
os.Setenv("SFTPGO_FTPD__BINDINGS__9__FORCE_PASSIVE_IP", "127.0.1.1")
|
os.Setenv("SFTPGO_FTPD__BINDINGS__9__FORCE_PASSIVE_IP", "127.0.1.1")
|
||||||
os.Setenv("SFTPGO_FTPD__BINDINGS__9__CLIENT_AUTH_TYPE", "2")
|
os.Setenv("SFTPGO_FTPD__BINDINGS__9__CLIENT_AUTH_TYPE", "2")
|
||||||
|
os.Setenv("SFTPGO_FTPD__BINDINGS__9__DEBUG", "1")
|
||||||
|
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__ADDRESS")
|
os.Unsetenv("SFTPGO_FTPD__BINDINGS__0__ADDRESS")
|
||||||
@@ -492,6 +493,7 @@ func TestFTPDBindingsFromEnv(t *testing.T) {
|
|||||||
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__TLS_MODE")
|
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__TLS_MODE")
|
||||||
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__FORCE_PASSIVE_IP")
|
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__FORCE_PASSIVE_IP")
|
||||||
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__CLIENT_AUTH_TYPE")
|
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__CLIENT_AUTH_TYPE")
|
||||||
|
os.Unsetenv("SFTPGO_FTPD__BINDINGS__9__DEBUG")
|
||||||
})
|
})
|
||||||
|
|
||||||
configDir := ".."
|
configDir := ".."
|
||||||
@@ -508,6 +510,7 @@ func TestFTPDBindingsFromEnv(t *testing.T) {
|
|||||||
require.Len(t, bindings[0].TLSCipherSuites, 2)
|
require.Len(t, bindings[0].TLSCipherSuites, 2)
|
||||||
require.Equal(t, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", bindings[0].TLSCipherSuites[0])
|
require.Equal(t, "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", bindings[0].TLSCipherSuites[0])
|
||||||
require.Equal(t, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", bindings[0].TLSCipherSuites[1])
|
require.Equal(t, "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", bindings[0].TLSCipherSuites[1])
|
||||||
|
require.False(t, bindings[0].Debug)
|
||||||
require.Equal(t, 2203, bindings[1].Port)
|
require.Equal(t, 2203, bindings[1].Port)
|
||||||
require.Equal(t, "127.0.1.1", bindings[1].Address)
|
require.Equal(t, "127.0.1.1", bindings[1].Address)
|
||||||
require.True(t, bindings[1].ApplyProxyConfig) // default value
|
require.True(t, bindings[1].ApplyProxyConfig) // default value
|
||||||
@@ -515,6 +518,7 @@ func TestFTPDBindingsFromEnv(t *testing.T) {
|
|||||||
require.Equal(t, "127.0.1.1", bindings[1].ForcePassiveIP)
|
require.Equal(t, "127.0.1.1", bindings[1].ForcePassiveIP)
|
||||||
require.Equal(t, 2, bindings[1].ClientAuthType)
|
require.Equal(t, 2, bindings[1].ClientAuthType)
|
||||||
require.Nil(t, bindings[1].TLSCipherSuites)
|
require.Nil(t, bindings[1].TLSCipherSuites)
|
||||||
|
require.True(t, bindings[1].Debug)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWebDAVBindingsFromEnv(t *testing.T) {
|
func TestWebDAVBindingsFromEnv(t *testing.T) {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ If the `hook` defines an HTTP URL then this URL will be invoked as HTTP POST. Th
|
|||||||
- `action`
|
- `action`
|
||||||
- `username`
|
- `username`
|
||||||
- `path`
|
- `path`
|
||||||
- `target_path`, included for `rename` action
|
- `target_path`, included for `rename` action and `sftpgo-copy` SSH command
|
||||||
- `ssh_cmd`, included for `ssh_cmd` action
|
- `ssh_cmd`, included for `ssh_cmd` action
|
||||||
- `file_size`, included for `pre-upload`, `upload`, `download`, `delete` actions if the file size is greater than `0`
|
- `file_size`, included for `pre-upload`, `upload`, `download`, `delete` actions if the file size is greater than `0`
|
||||||
- `fs_provider`, `0` for local filesystem, `1` for S3 backend, `2` for Google Cloud Storage (GCS) backend, `3` for Azure Blob Storage backend, `4` for local encrypted backend, `5` for SFTP backend
|
- `fs_provider`, `0` for local filesystem, `1` for S3 backend, `2` for Google Cloud Storage (GCS) backend, `3` for Azure Blob Storage backend, `4` for local encrypted backend, `5` for SFTP backend
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ The configuration file contains the following sections:
|
|||||||
- `force_passive_ip`, ip address. External IP address to expose for passive connections. Leavy empty to autodetect. If not empty, it must be a valid IPv4 address. Defaut: "".
|
- `force_passive_ip`, ip address. External IP address to expose for passive connections. Leavy empty to autodetect. If not empty, it must be a valid IPv4 address. Defaut: "".
|
||||||
- `client_auth_type`, integer. Set to `1` to require a client certificate and verify it. Set to `2` to request a client certificate during the TLS handshake and verify it if given, in this mode the client is allowed not to send a certificate. At least one certification authority must be defined in order to verify client certificates. If no certification authority is defined, this setting is ignored. Default: 0.
|
- `client_auth_type`, integer. Set to `1` to require a client certificate and verify it. Set to `2` to request a client certificate during the TLS handshake and verify it if given, in this mode the client is allowed not to send a certificate. At least one certification authority must be defined in order to verify client certificates. If no certification authority is defined, this setting is ignored. Default: 0.
|
||||||
- `tls_cipher_suites`, list of strings. List of supported cipher suites for TLS version 1.2. If empty, a default list of secure cipher suites is used, with a preference order based on hardware performance. Note that TLS 1.3 ciphersuites are not configurable. The supported ciphersuites names are defined [here](https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L52). Any invalid name will be silently ignored. The order matters, the ciphers listed first will be the preferred ones. Default: empty.
|
- `tls_cipher_suites`, list of strings. List of supported cipher suites for TLS version 1.2. If empty, a default list of secure cipher suites is used, with a preference order based on hardware performance. Note that TLS 1.3 ciphersuites are not configurable. The supported ciphersuites names are defined [here](https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L52). Any invalid name will be silently ignored. The order matters, the ciphers listed first will be the preferred ones. Default: empty.
|
||||||
|
- `debug`, boolean. If enabled any FTP command will be logged. This will generate a lot of logs. Enable only if you are investigating a client compatibility issue or something similar. You shouldn't leave this setting enabled for production servers. Default `false`.
|
||||||
- `banner`, string. Greeting banner displayed when a connection first comes in. Leave empty to use the default banner. Default `SFTPGo <version> ready`, for example `SFTPGo 1.0.0-dev ready`.
|
- `banner`, string. Greeting banner displayed when a connection first comes in. Leave empty to use the default banner. Default `SFTPGo <version> ready`, for example `SFTPGo 1.0.0-dev ready`.
|
||||||
- `banner_file`, path to the banner file. The contents of the specified file, if any, are displayed when someone connects to the server. It can be a path relative to the config dir or an absolute one. If set, it overrides the banner string provided by the `banner` option. Leave empty to disable.
|
- `banner_file`, path to the banner file. The contents of the specified file, if any, are displayed when someone connects to the server. It can be a path relative to the config dir or an absolute one. If set, it overrides the banner string provided by the `banner` option. Leave empty to disable.
|
||||||
- `active_transfers_port_non_20`, boolean. Do not impose the port 20 for active data transfers. Enabling this option allows to run SFTPGo with less privilege. Default: false.
|
- `active_transfers_port_non_20`, boolean. Do not impose the port 20 for active data transfers. Enabling this option allows to run SFTPGo with less privilege. Default: false.
|
||||||
|
|||||||
@@ -51,7 +51,9 @@ type Binding struct {
|
|||||||
// any invalid name will be silently ignored.
|
// any invalid name will be silently ignored.
|
||||||
// The order matters, the ciphers listed first will be the preferred ones.
|
// The order matters, the ciphers listed first will be the preferred ones.
|
||||||
TLSCipherSuites []string `json:"tls_cipher_suites" mapstructure:"tls_cipher_suites"`
|
TLSCipherSuites []string `json:"tls_cipher_suites" mapstructure:"tls_cipher_suites"`
|
||||||
ciphers []uint16
|
// Debug enables the FTP debug mode. In debug mode, every FTP command will be logged
|
||||||
|
Debug bool `json:"debug" mapstructure:"debug"`
|
||||||
|
ciphers []uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Binding) setCiphers() {
|
func (b *Binding) setCiphers() {
|
||||||
@@ -217,7 +219,9 @@ func (c *Configuration) Initialize(configDir string) error {
|
|||||||
server := NewServer(c, configDir, binding, idx)
|
server := NewServer(c, configDir, binding, idx)
|
||||||
|
|
||||||
go func(s *Server) {
|
go func(s *Server) {
|
||||||
|
ftpLogger := logger.LeveledLogger{Sender: "ftpserverlib"}
|
||||||
ftpServer := ftpserver.NewFtpServer(s)
|
ftpServer := ftpserver.NewFtpServer(s)
|
||||||
|
ftpServer.Logger = ftpLogger.With("server_id", fmt.Sprintf("FTP_%v", s.ID))
|
||||||
logger.Info(logSender, "", "starting FTP serving, binding: %v", s.binding.GetAddress())
|
logger.Info(logSender, "", "starting FTP serving, binding: %v", s.binding.GetAddress())
|
||||||
util.CheckTCP4Port(s.binding.Port)
|
util.CheckTCP4Port(s.binding.Port)
|
||||||
exitChannel <- ftpServer.ListenAndServe()
|
exitChannel <- ftpServer.ListenAndServe()
|
||||||
|
|||||||
@@ -135,6 +135,7 @@ func (s *Server) GetSettings() (*ftpserver.Settings, error) {
|
|||||||
|
|
||||||
// ClientConnected is called to send the very first welcome message
|
// ClientConnected is called to send the very first welcome message
|
||||||
func (s *Server) ClientConnected(cc ftpserver.ClientContext) (string, error) {
|
func (s *Server) ClientConnected(cc ftpserver.ClientContext) (string, error) {
|
||||||
|
cc.SetDebug(s.binding.Debug)
|
||||||
ipAddr := util.GetIPFromRemoteAddress(cc.RemoteAddr().String())
|
ipAddr := util.GetIPFromRemoteAddress(cc.RemoteAddr().String())
|
||||||
common.Connections.AddClientConnection(ipAddr)
|
common.Connections.AddClientConnection(ipAddr)
|
||||||
if common.IsBanned(ipAddr) {
|
if common.IsBanned(ipAddr) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
|
ftpserverlog "github.com/fclairamb/ftpserverlib/log"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
||||||
)
|
)
|
||||||
@@ -60,7 +61,8 @@ func (l *StdLoggerWrapper) Write(p []byte) (n int, err error) {
|
|||||||
|
|
||||||
// LeveledLogger is a logger that accepts a message string and a variadic number of key-value pairs
|
// LeveledLogger is a logger that accepts a message string and a variadic number of key-value pairs
|
||||||
type LeveledLogger struct {
|
type LeveledLogger struct {
|
||||||
Sender string
|
Sender string
|
||||||
|
additionalKeyVals []interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addKeysAndValues(ev *zerolog.Event, keysAndValues ...interface{}) {
|
func addKeysAndValues(ev *zerolog.Event, keysAndValues ...interface{}) {
|
||||||
@@ -81,6 +83,9 @@ func addKeysAndValues(ev *zerolog.Event, keysAndValues ...interface{}) {
|
|||||||
func (l *LeveledLogger) Error(msg string, keysAndValues ...interface{}) {
|
func (l *LeveledLogger) Error(msg string, keysAndValues ...interface{}) {
|
||||||
ev := logger.Error()
|
ev := logger.Error()
|
||||||
ev.Timestamp().Str("sender", l.Sender)
|
ev.Timestamp().Str("sender", l.Sender)
|
||||||
|
if len(l.additionalKeyVals) > 0 {
|
||||||
|
addKeysAndValues(ev, l.additionalKeyVals...)
|
||||||
|
}
|
||||||
addKeysAndValues(ev, keysAndValues...)
|
addKeysAndValues(ev, keysAndValues...)
|
||||||
ev.Msg(msg)
|
ev.Msg(msg)
|
||||||
}
|
}
|
||||||
@@ -89,6 +94,9 @@ func (l *LeveledLogger) Error(msg string, keysAndValues ...interface{}) {
|
|||||||
func (l *LeveledLogger) Info(msg string, keysAndValues ...interface{}) {
|
func (l *LeveledLogger) Info(msg string, keysAndValues ...interface{}) {
|
||||||
ev := logger.Info()
|
ev := logger.Info()
|
||||||
ev.Timestamp().Str("sender", l.Sender)
|
ev.Timestamp().Str("sender", l.Sender)
|
||||||
|
if len(l.additionalKeyVals) > 0 {
|
||||||
|
addKeysAndValues(ev, l.additionalKeyVals...)
|
||||||
|
}
|
||||||
addKeysAndValues(ev, keysAndValues...)
|
addKeysAndValues(ev, keysAndValues...)
|
||||||
ev.Msg(msg)
|
ev.Msg(msg)
|
||||||
}
|
}
|
||||||
@@ -97,6 +105,9 @@ func (l *LeveledLogger) Info(msg string, keysAndValues ...interface{}) {
|
|||||||
func (l *LeveledLogger) Debug(msg string, keysAndValues ...interface{}) {
|
func (l *LeveledLogger) Debug(msg string, keysAndValues ...interface{}) {
|
||||||
ev := logger.Debug()
|
ev := logger.Debug()
|
||||||
ev.Timestamp().Str("sender", l.Sender)
|
ev.Timestamp().Str("sender", l.Sender)
|
||||||
|
if len(l.additionalKeyVals) > 0 {
|
||||||
|
addKeysAndValues(ev, l.additionalKeyVals...)
|
||||||
|
}
|
||||||
addKeysAndValues(ev, keysAndValues...)
|
addKeysAndValues(ev, keysAndValues...)
|
||||||
ev.Msg(msg)
|
ev.Msg(msg)
|
||||||
}
|
}
|
||||||
@@ -105,10 +116,21 @@ func (l *LeveledLogger) Debug(msg string, keysAndValues ...interface{}) {
|
|||||||
func (l *LeveledLogger) Warn(msg string, keysAndValues ...interface{}) {
|
func (l *LeveledLogger) Warn(msg string, keysAndValues ...interface{}) {
|
||||||
ev := logger.Warn()
|
ev := logger.Warn()
|
||||||
ev.Timestamp().Str("sender", l.Sender)
|
ev.Timestamp().Str("sender", l.Sender)
|
||||||
|
if len(l.additionalKeyVals) > 0 {
|
||||||
|
addKeysAndValues(ev, l.additionalKeyVals...)
|
||||||
|
}
|
||||||
addKeysAndValues(ev, keysAndValues...)
|
addKeysAndValues(ev, keysAndValues...)
|
||||||
ev.Msg(msg)
|
ev.Msg(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// With returns a LeveledLogger with additional context specific keyvals
|
||||||
|
func (l *LeveledLogger) With(keysAndValues ...interface{}) ftpserverlog.Logger {
|
||||||
|
return &LeveledLogger{
|
||||||
|
Sender: l.Sender,
|
||||||
|
additionalKeyVals: append(l.additionalKeyVals, keysAndValues...),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetLogger get the configured logger instance
|
// GetLogger get the configured logger instance
|
||||||
func GetLogger() *zerolog.Logger {
|
func GetLogger() *zerolog.Logger {
|
||||||
return &logger
|
return &logger
|
||||||
|
|||||||
@@ -82,7 +82,8 @@
|
|||||||
"tls_mode": 0,
|
"tls_mode": 0,
|
||||||
"force_passive_ip": "",
|
"force_passive_ip": "",
|
||||||
"client_auth_type": 0,
|
"client_auth_type": 0,
|
||||||
"tls_cipher_suites": []
|
"tls_cipher_suites": [],
|
||||||
|
"debug": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"banner": "",
|
"banner": "",
|
||||||
|
|||||||
Reference in New Issue
Block a user