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:
Nicola Murino 2020-05-16 23:26:44 +02:00
parent 469d36d979
commit a08dd85efd
7 changed files with 70 additions and 37 deletions

View file

@ -57,7 +57,7 @@ func init() {
Command: "",
HTTPNotificationURL: "",
},
Keys: []sftpd.Key{},
HostKeys: []string{},
KexAlgorithms: []string{},
Ciphers: []string{},
MACs: []string{},
@ -218,6 +218,7 @@ func LoadConfig(configDir, configName string) error {
logger.WarnToConsole("Configuration error: %v", err)
}
checkHooksCompatibility()
checkHostKeyCompatibility()
logger.Debug(logSender, "", "config file used: '%#v', config loaded: %+v", viper.ConfigFileUsed(), getRedactedGlobalConf())
return err
}
@ -240,3 +241,14 @@ func checkHooksCompatibility() {
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)
}
}
}

View file

@ -15,6 +15,7 @@ import (
"github.com/drakkan/sftpgo/httpclient"
"github.com/drakkan/sftpgo/httpd"
"github.com/drakkan/sftpgo/sftpd"
"github.com/drakkan/sftpgo/utils"
)
const (
@ -205,6 +206,37 @@ func TestHookCompatibity(t *testing.T) {
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) {
sftpdConf := config.GetSFTPDConfig()
sftpdConf.IdleTimeout = 3

View file

@ -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.
- `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.
- `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.
- `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")
- `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")

View file

@ -1738,24 +1738,13 @@ func TestProxyProtocolVersion(t *testing.T) {
func TestLoadHostKeys(t *testing.T) {
c := Configuration{}
c.Keys = []Key{
{
PrivateKey: ".",
},
{
PrivateKey: "missing file",
},
}
c.HostKeys = []string{".", "missing file"}
err := c.checkAndLoadHostKeys("..", &ssh.ServerConfig{})
assert.Error(t, err)
testfile := filepath.Join(os.TempDir(), "invalidkey")
err = ioutil.WriteFile(testfile, []byte("some bytes"), 0666)
assert.NoError(t, err)
c.Keys = []Key{
{
PrivateKey: testfile,
},
}
c.HostKeys = []string{testfile}
err = c.checkAndLoadHostKeys("..", &ssh.ServerConfig{})
assert.Error(t, err)
err = os.Remove(testfile)

View file

@ -63,8 +63,13 @@ type Configuration struct {
UploadMode int `json:"upload_mode" mapstructure:"upload_mode"`
// Actions to execute on SFTP create, download, delete and rename
Actions Actions `json:"actions" mapstructure:"actions"`
// Keys are a list of host keys
// Deprecated: please use HostKeys
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
// preference order.
KexAlgorithms []string `json:"kex_algorithms" mapstructure:"kex_algorithms"`
@ -131,6 +136,7 @@ type Configuration struct {
}
// Key contains information about host keys
// Deprecated: please use HostKeys
type Key struct {
// The private key path as absolute path or relative to the configuration directory
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.
func (c *Configuration) checkAndLoadHostKeys(configDir string, serverConfig *ssh.ServerConfig) error {
if len(c.Keys) == 0 {
if len(c.HostKeys) == 0 {
defaultKeys := []string{defaultPrivateRSAKeyName, defaultPrivateECDSAKeyName}
for _, k := range defaultKeys {
autoFile := filepath.Join(configDir, k)
@ -525,22 +531,22 @@ func (c *Configuration) checkAndLoadHostKeys(configDir string, serverConfig *ssh
return err
}
}
c.Keys = append(c.Keys, Key{PrivateKey: k})
c.HostKeys = append(c.HostKeys, k)
}
}
for _, k := range c.Keys {
privateFile := k.PrivateKey
if !utils.IsFileInputValid(privateFile) {
logger.Warn(logSender, "", "unable to load invalid host key: %#v", privateFile)
logger.WarnToConsole("unable to load invalid host key: %#v", privateFile)
for _, k := range c.HostKeys {
hostKey := k
if !utils.IsFileInputValid(hostKey) {
logger.Warn(logSender, "", "unable to load invalid host key: %#v", hostKey)
logger.WarnToConsole("unable to load invalid host key: %#v", hostKey)
continue
}
if !filepath.IsAbs(privateFile) {
privateFile = filepath.Join(configDir, privateFile)
if !filepath.IsAbs(hostKey) {
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 {
return err
}

View file

@ -293,11 +293,7 @@ func TestInitialization(t *testing.T) {
sftpdConf.ProxyAllowed = []string{"1270.0.0.1"}
err = sftpdConf.Initialize(configDir)
assert.Error(t, err)
sftpdConf.Keys = []sftpd.Key{
{
PrivateKey: "missing file",
},
}
sftpdConf.HostKeys = []string{"missing file"}
err = sftpdConf.Initialize(configDir)
assert.Error(t, err)
sftpdConf.Keys = nil

View file

@ -12,7 +12,7 @@
"command": "",
"http_notification_url": ""
},
"keys": [],
"host_keys": [],
"kex_algorithms": [],
"ciphers": [],
"macs": [],
@ -26,7 +26,6 @@
"pwd",
"scp"
],
"keyboard_interactive_auth_program": "",
"keyboard_interactive_auth_hook": "",
"proxy_protocol": 0,
"proxy_allowed": []
@ -50,12 +49,10 @@
"command": "",
"http_notification_url": ""
},
"external_auth_program": "",
"external_auth_hook": "",
"external_auth_scope": 0,
"credentials_path": "credentials",
"pre_login_hook": "",
"pre_login_program": ""
"pre_login_hook": ""
},
"httpd": {
"bind_port": 8080,