mirror of
https://github.com/drakkan/sftpgo.git
synced 2025-12-06 22:30:56 +03:00
sftpd: deprecate keys and add a new host_keys config param
host_key defines the private host keys as plain list of strings. Remove the other deprecated config params from the default config too. Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
@@ -57,7 +57,7 @@ func init() {
|
|||||||
Command: "",
|
Command: "",
|
||||||
HTTPNotificationURL: "",
|
HTTPNotificationURL: "",
|
||||||
},
|
},
|
||||||
Keys: []sftpd.Key{},
|
HostKeys: []string{},
|
||||||
KexAlgorithms: []string{},
|
KexAlgorithms: []string{},
|
||||||
Ciphers: []string{},
|
Ciphers: []string{},
|
||||||
MACs: []string{},
|
MACs: []string{},
|
||||||
@@ -218,6 +218,7 @@ func LoadConfig(configDir, configName string) error {
|
|||||||
logger.WarnToConsole("Configuration error: %v", err)
|
logger.WarnToConsole("Configuration error: %v", err)
|
||||||
}
|
}
|
||||||
checkHooksCompatibility()
|
checkHooksCompatibility()
|
||||||
|
checkHostKeyCompatibility()
|
||||||
logger.Debug(logSender, "", "config file used: '%#v', config loaded: %+v", viper.ConfigFileUsed(), getRedactedGlobalConf())
|
logger.Debug(logSender, "", "config file used: '%#v', config loaded: %+v", viper.ConfigFileUsed(), getRedactedGlobalConf())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -240,3 +241,14 @@ func checkHooksCompatibility() {
|
|||||||
globalConf.SFTPD.KeyboardInteractiveHook = globalConf.SFTPD.KeyboardInteractiveProgram //nolint:staticcheck
|
globalConf.SFTPD.KeyboardInteractiveHook = globalConf.SFTPD.KeyboardInteractiveProgram //nolint:staticcheck
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkHostKeyCompatibility() {
|
||||||
|
// we copy deprecated fields to new ones to keep backward compatibility so lint is disabled
|
||||||
|
if len(globalConf.SFTPD.Keys) > 0 && len(globalConf.SFTPD.HostKeys) == 0 { //nolint:staticcheck
|
||||||
|
logger.Warn(logSender, "", "keys is deprecated, please use host_keys")
|
||||||
|
logger.WarnToConsole("keys is deprecated, please use host_keys")
|
||||||
|
for _, k := range globalConf.SFTPD.Keys { //nolint:staticcheck
|
||||||
|
globalConf.SFTPD.HostKeys = append(globalConf.SFTPD.HostKeys, k.PrivateKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/drakkan/sftpgo/httpclient"
|
"github.com/drakkan/sftpgo/httpclient"
|
||||||
"github.com/drakkan/sftpgo/httpd"
|
"github.com/drakkan/sftpgo/httpd"
|
||||||
"github.com/drakkan/sftpgo/sftpd"
|
"github.com/drakkan/sftpgo/sftpd"
|
||||||
|
"github.com/drakkan/sftpgo/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -205,6 +206,37 @@ func TestHookCompatibity(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHostKeyCompatibility(t *testing.T) {
|
||||||
|
configDir := ".."
|
||||||
|
confName := tempConfigName + ".json"
|
||||||
|
configFilePath := filepath.Join(configDir, confName)
|
||||||
|
err := config.LoadConfig(configDir, configName)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
sftpdConf := config.GetSFTPDConfig()
|
||||||
|
sftpdConf.Keys = []sftpd.Key{ //nolint:staticcheck
|
||||||
|
{
|
||||||
|
PrivateKey: "rsa",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PrivateKey: "ecdsa",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c := make(map[string]sftpd.Configuration)
|
||||||
|
c["sftpd"] = sftpdConf
|
||||||
|
jsonConf, err := json.Marshal(c)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = ioutil.WriteFile(configFilePath, jsonConf, 0666)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
err = config.LoadConfig(configDir, tempConfigName)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
sftpdConf = config.GetSFTPDConfig()
|
||||||
|
assert.Equal(t, 2, len(sftpdConf.HostKeys))
|
||||||
|
assert.True(t, utils.IsStringInSlice("rsa", sftpdConf.HostKeys))
|
||||||
|
assert.True(t, utils.IsStringInSlice("ecdsa", sftpdConf.HostKeys))
|
||||||
|
err = os.Remove(configFilePath)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestSetGetConfig(t *testing.T) {
|
func TestSetGetConfig(t *testing.T) {
|
||||||
sftpdConf := config.GetSFTPDConfig()
|
sftpdConf := config.GetSFTPDConfig()
|
||||||
sftpdConf.IdleTimeout = 3
|
sftpdConf.IdleTimeout = 3
|
||||||
|
|||||||
@@ -50,8 +50,9 @@ The configuration file contains the following sections:
|
|||||||
- `execute_on`, list of strings. Valid values are `download`, `upload`, `delete`, `rename`, `ssh_cmd`. Leave empty to disable actions.
|
- `execute_on`, list of strings. Valid values are `download`, `upload`, `delete`, `rename`, `ssh_cmd`. Leave empty to disable actions.
|
||||||
- `command`, string. Absolute path to the command to execute. Leave empty to disable.
|
- `command`, string. Absolute path to the command to execute. Leave empty to disable.
|
||||||
- `http_notification_url`, a valid URL. An HTTP GET request will be executed to this URL. Leave empty to disable.
|
- `http_notification_url`, a valid URL. An HTTP GET request will be executed to this URL. Leave empty to disable.
|
||||||
- `keys`, struct array. It contains the daemon's private keys. If empty or missing, the daemon will search or try to generate `id_rsa` and `id_ecdsa` keys in the configuration directory.
|
- `keys`, struct array. Deprecated, please use `host_keys`.
|
||||||
- `private_key`, path to the private key file. It can be a path relative to the config dir or an absolute one.
|
- `private_key`, path to the private key file. It can be a path relative to the config dir or an absolute one.
|
||||||
|
- `host_keys`, list of strings. It contains the daemon's private host keys. Each host key can be defined as a path relative to the configuration directory or an absolute one. If empty or missing, the daemon will search or try to generate `id_rsa` and `id_ecdsa` keys inside the configuration directory.
|
||||||
- `kex_algorithms`, list of strings. Available KEX (Key Exchange) algorithms in preference order. Leave empty to use default values. The supported values can be found here: [`crypto/ssh`](https://github.com/golang/crypto/blob/master/ssh/common.go#L46 "Supported kex algos")
|
- `kex_algorithms`, list of strings. Available KEX (Key Exchange) algorithms in preference order. Leave empty to use default values. The supported values can be found here: [`crypto/ssh`](https://github.com/golang/crypto/blob/master/ssh/common.go#L46 "Supported kex algos")
|
||||||
- `ciphers`, list of strings. Allowed ciphers. Leave empty to use default values. The supported values can be found here: [`crypto/ssh`](https://github.com/golang/crypto/blob/master/ssh/common.go#L28 "Supported ciphers")
|
- `ciphers`, list of strings. Allowed ciphers. Leave empty to use default values. The supported values can be found here: [`crypto/ssh`](https://github.com/golang/crypto/blob/master/ssh/common.go#L28 "Supported ciphers")
|
||||||
- `macs`, list of strings. Available MAC (message authentication code) algorithms in preference order. Leave empty to use default values. The supported values can be found here: [`crypto/ssh`](https://github.com/golang/crypto/blob/master/ssh/common.go#L84 "Supported MACs")
|
- `macs`, list of strings. Available MAC (message authentication code) algorithms in preference order. Leave empty to use default values. The supported values can be found here: [`crypto/ssh`](https://github.com/golang/crypto/blob/master/ssh/common.go#L84 "Supported MACs")
|
||||||
|
|||||||
@@ -1738,24 +1738,13 @@ func TestProxyProtocolVersion(t *testing.T) {
|
|||||||
|
|
||||||
func TestLoadHostKeys(t *testing.T) {
|
func TestLoadHostKeys(t *testing.T) {
|
||||||
c := Configuration{}
|
c := Configuration{}
|
||||||
c.Keys = []Key{
|
c.HostKeys = []string{".", "missing file"}
|
||||||
{
|
|
||||||
PrivateKey: ".",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
PrivateKey: "missing file",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
err := c.checkAndLoadHostKeys("..", &ssh.ServerConfig{})
|
err := c.checkAndLoadHostKeys("..", &ssh.ServerConfig{})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
testfile := filepath.Join(os.TempDir(), "invalidkey")
|
testfile := filepath.Join(os.TempDir(), "invalidkey")
|
||||||
err = ioutil.WriteFile(testfile, []byte("some bytes"), 0666)
|
err = ioutil.WriteFile(testfile, []byte("some bytes"), 0666)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
c.Keys = []Key{
|
c.HostKeys = []string{testfile}
|
||||||
{
|
|
||||||
PrivateKey: testfile,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
err = c.checkAndLoadHostKeys("..", &ssh.ServerConfig{})
|
err = c.checkAndLoadHostKeys("..", &ssh.ServerConfig{})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
err = os.Remove(testfile)
|
err = os.Remove(testfile)
|
||||||
|
|||||||
@@ -63,8 +63,13 @@ type Configuration struct {
|
|||||||
UploadMode int `json:"upload_mode" mapstructure:"upload_mode"`
|
UploadMode int `json:"upload_mode" mapstructure:"upload_mode"`
|
||||||
// Actions to execute on SFTP create, download, delete and rename
|
// Actions to execute on SFTP create, download, delete and rename
|
||||||
Actions Actions `json:"actions" mapstructure:"actions"`
|
Actions Actions `json:"actions" mapstructure:"actions"`
|
||||||
// Keys are a list of host keys
|
// Deprecated: please use HostKeys
|
||||||
Keys []Key `json:"keys" mapstructure:"keys"`
|
Keys []Key `json:"keys" mapstructure:"keys"`
|
||||||
|
// HostKeys define the daemon's private host keys.
|
||||||
|
// Each host key can be defined as a path relative to the configuration directory or an absolute one.
|
||||||
|
// If empty or missing, the daemon will search or try to generate "id_rsa" and "id_ecdsa" host keys
|
||||||
|
// inside the configuration directory.
|
||||||
|
HostKeys []string `json:"host_keys" mapstructure:"host_keys"`
|
||||||
// KexAlgorithms specifies the available KEX (Key Exchange) algorithms in
|
// KexAlgorithms specifies the available KEX (Key Exchange) algorithms in
|
||||||
// preference order.
|
// preference order.
|
||||||
KexAlgorithms []string `json:"kex_algorithms" mapstructure:"kex_algorithms"`
|
KexAlgorithms []string `json:"kex_algorithms" mapstructure:"kex_algorithms"`
|
||||||
@@ -131,6 +136,7 @@ type Configuration struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Key contains information about host keys
|
// Key contains information about host keys
|
||||||
|
// Deprecated: please use HostKeys
|
||||||
type Key struct {
|
type Key struct {
|
||||||
// The private key path as absolute path or relative to the configuration directory
|
// The private key path as absolute path or relative to the configuration directory
|
||||||
PrivateKey string `json:"private_key" mapstructure:"private_key"`
|
PrivateKey string `json:"private_key" mapstructure:"private_key"`
|
||||||
@@ -509,7 +515,7 @@ func (c *Configuration) checkSSHCommands() {
|
|||||||
|
|
||||||
// If no host keys are defined we try to use or generate the default ones.
|
// If no host keys are defined we try to use or generate the default ones.
|
||||||
func (c *Configuration) checkAndLoadHostKeys(configDir string, serverConfig *ssh.ServerConfig) error {
|
func (c *Configuration) checkAndLoadHostKeys(configDir string, serverConfig *ssh.ServerConfig) error {
|
||||||
if len(c.Keys) == 0 {
|
if len(c.HostKeys) == 0 {
|
||||||
defaultKeys := []string{defaultPrivateRSAKeyName, defaultPrivateECDSAKeyName}
|
defaultKeys := []string{defaultPrivateRSAKeyName, defaultPrivateECDSAKeyName}
|
||||||
for _, k := range defaultKeys {
|
for _, k := range defaultKeys {
|
||||||
autoFile := filepath.Join(configDir, k)
|
autoFile := filepath.Join(configDir, k)
|
||||||
@@ -525,22 +531,22 @@ func (c *Configuration) checkAndLoadHostKeys(configDir string, serverConfig *ssh
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.Keys = append(c.Keys, Key{PrivateKey: k})
|
c.HostKeys = append(c.HostKeys, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, k := range c.Keys {
|
for _, k := range c.HostKeys {
|
||||||
privateFile := k.PrivateKey
|
hostKey := k
|
||||||
if !utils.IsFileInputValid(privateFile) {
|
if !utils.IsFileInputValid(hostKey) {
|
||||||
logger.Warn(logSender, "", "unable to load invalid host key: %#v", privateFile)
|
logger.Warn(logSender, "", "unable to load invalid host key: %#v", hostKey)
|
||||||
logger.WarnToConsole("unable to load invalid host key: %#v", privateFile)
|
logger.WarnToConsole("unable to load invalid host key: %#v", hostKey)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !filepath.IsAbs(privateFile) {
|
if !filepath.IsAbs(hostKey) {
|
||||||
privateFile = filepath.Join(configDir, privateFile)
|
hostKey = filepath.Join(configDir, hostKey)
|
||||||
}
|
}
|
||||||
logger.Info(logSender, "", "Loading private key: %s", privateFile)
|
logger.Info(logSender, "", "Loading private host key: %s", hostKey)
|
||||||
|
|
||||||
privateBytes, err := ioutil.ReadFile(privateFile)
|
privateBytes, err := ioutil.ReadFile(hostKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -293,11 +293,7 @@ func TestInitialization(t *testing.T) {
|
|||||||
sftpdConf.ProxyAllowed = []string{"1270.0.0.1"}
|
sftpdConf.ProxyAllowed = []string{"1270.0.0.1"}
|
||||||
err = sftpdConf.Initialize(configDir)
|
err = sftpdConf.Initialize(configDir)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
sftpdConf.Keys = []sftpd.Key{
|
sftpdConf.HostKeys = []string{"missing file"}
|
||||||
{
|
|
||||||
PrivateKey: "missing file",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
err = sftpdConf.Initialize(configDir)
|
err = sftpdConf.Initialize(configDir)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
sftpdConf.Keys = nil
|
sftpdConf.Keys = nil
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"command": "",
|
"command": "",
|
||||||
"http_notification_url": ""
|
"http_notification_url": ""
|
||||||
},
|
},
|
||||||
"keys": [],
|
"host_keys": [],
|
||||||
"kex_algorithms": [],
|
"kex_algorithms": [],
|
||||||
"ciphers": [],
|
"ciphers": [],
|
||||||
"macs": [],
|
"macs": [],
|
||||||
@@ -26,7 +26,6 @@
|
|||||||
"pwd",
|
"pwd",
|
||||||
"scp"
|
"scp"
|
||||||
],
|
],
|
||||||
"keyboard_interactive_auth_program": "",
|
|
||||||
"keyboard_interactive_auth_hook": "",
|
"keyboard_interactive_auth_hook": "",
|
||||||
"proxy_protocol": 0,
|
"proxy_protocol": 0,
|
||||||
"proxy_allowed": []
|
"proxy_allowed": []
|
||||||
@@ -50,12 +49,10 @@
|
|||||||
"command": "",
|
"command": "",
|
||||||
"http_notification_url": ""
|
"http_notification_url": ""
|
||||||
},
|
},
|
||||||
"external_auth_program": "",
|
|
||||||
"external_auth_hook": "",
|
"external_auth_hook": "",
|
||||||
"external_auth_scope": 0,
|
"external_auth_scope": 0,
|
||||||
"credentials_path": "credentials",
|
"credentials_path": "credentials",
|
||||||
"pre_login_hook": "",
|
"pre_login_hook": ""
|
||||||
"pre_login_program": ""
|
|
||||||
},
|
},
|
||||||
"httpd": {
|
"httpd": {
|
||||||
"bind_port": 8080,
|
"bind_port": 8080,
|
||||||
|
|||||||
Reference in New Issue
Block a user