浏览代码

allows host keys auto generation inside a user configured directory

Fixes #124
Nicola Murino 5 年之前
父节点
当前提交
cd380973df
共有 3 个文件被更改,包括 93 次插入20 次删除
  1. 18 13
      docs/full-configuration.md
  2. 38 3
      sftpd/internal_test.go
  3. 37 4
      sftpd/server.go

+ 18 - 13
docs/full-configuration.md

@@ -53,7 +53,7 @@ The configuration file contains the following sections:
     - `hook`, string. Absolute path to the command to execute or HTTP URL to notify.
     - `hook`, string. Absolute path to the command to execute or HTTP URL to notify.
   - `keys`, struct array. Deprecated, please use `host_keys`.
   - `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.
+  - `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, the daemon will search or try to generate `id_rsa` and `id_ecdsa` keys inside the configuration directory. If you configure absolute paths to files named `id_rsa` and/or `id_ecdsa` then SFTPGo will try to generate these keys using the default settings.
   - `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")
@@ -119,23 +119,28 @@ The configuration file contains the following sections:
 
 
 A full example showing the default config (in JSON format) can be found [here](../sftpgo.json).
 A full example showing the default config (in JSON format) can be found [here](../sftpgo.json).
 
 
-If you want to use a private key that use an algorithm different from RSA or ECDSA, or more private keys, then generate your own keys and replace the empty `keys` array with something like this:
+If you want to use a private host key that use an algorithm/setting different from the auto generated RSA/ECDSA keys, or more than two private keys, you can generate your own keys and replace the empty `keys` array with something like this:
 
 
 ```json
 ```json
-"keys": [
-  {
-    "private_key": "id_rsa"
-  },
-  {
-    "private_key": "id_ecdsa"
-  },
-  {
-    "private_key": "id_ed25519"
-  }
+"host_keys": [
+  "id_rsa",
+  "id_ecdsa",
+  "id_ed25519"
 ]
 ]
 ```
 ```
 
 
-where `id_rsa`, `id_ecdsa` and `id_ed25519` in this example are files containing your generated keys. You can use absolute paths or paths relative to the configuration directory.
+where `id_rsa`, `id_ecdsa` and `id_ed25519`, in this example, are files containing your generated keys. You can use absolute paths or paths relative to the configuration directory.
+
+If you want the default host keys generation in a directory different from the config dir, please specify absolute paths to files named `id_rsa` or `id_ecdsa` like this:
+
+```json
+"host_keys": [
+  "/etc/sftpgo/keys/id_rsa",
+  "/etc/sftpgo/keys/id_ecdsa"
+]
+```
+
+then SFTPGo will try to create `id_rsa` and `id_ecdsa`, if they are missing, inside the existing directory `/etc/sftpgo/keys`.
 
 
 The configuration can be read from JSON, TOML, YAML, HCL, envfile and Java properties config files. If your `config-file` flag is set to `sftpgo` (default value), you need to create a configuration file called `sftpgo.json` or `sftpgo.yaml` and so on inside `config-dir`.
 The configuration can be read from JSON, TOML, YAML, HCL, envfile and Java properties config files. If your `config-file` flag is set to `sftpgo` (default value), you need to create a configuration file called `sftpgo.json` or `sftpgo.yaml` and so on inside `config-dir`.
 
 

+ 38 - 3
sftpd/internal_test.go

@@ -1803,18 +1803,53 @@ func TestProxyProtocolVersion(t *testing.T) {
 }
 }
 
 
 func TestLoadHostKeys(t *testing.T) {
 func TestLoadHostKeys(t *testing.T) {
+	configDir := ".."
+	serverConfig := &ssh.ServerConfig{}
 	c := Configuration{}
 	c := Configuration{}
 	c.HostKeys = []string{".", "missing file"}
 	c.HostKeys = []string{".", "missing file"}
-	err := c.checkAndLoadHostKeys("..", &ssh.ServerConfig{})
+	err := c.checkAndLoadHostKeys(configDir, 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.HostKeys = []string{testfile}
 	c.HostKeys = []string{testfile}
-	err = c.checkAndLoadHostKeys("..", &ssh.ServerConfig{})
+	err = c.checkAndLoadHostKeys(configDir, serverConfig)
 	assert.Error(t, err)
 	assert.Error(t, err)
 	err = os.Remove(testfile)
 	err = os.Remove(testfile)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
+	keysDir := filepath.Join(os.TempDir(), "keys")
+	err = os.MkdirAll(keysDir, 0777)
+	assert.NoError(t, err)
+	rsaKeyName := filepath.Join(keysDir, defaultPrivateRSAKeyName)
+	ecdsaKeyName := filepath.Join(keysDir, defaultPrivateECDSAKeyName)
+	nonDefaultKeyName := filepath.Join(keysDir, "akey")
+	c.HostKeys = []string{nonDefaultKeyName, rsaKeyName, ecdsaKeyName}
+	err = c.checkAndLoadHostKeys(configDir, serverConfig)
+	assert.Error(t, err)
+	assert.FileExists(t, rsaKeyName)
+	assert.FileExists(t, ecdsaKeyName)
+	assert.NoFileExists(t, nonDefaultKeyName)
+	err = os.Remove(rsaKeyName)
+	assert.NoError(t, err)
+	err = os.Remove(ecdsaKeyName)
+	assert.NoError(t, err)
+	if runtime.GOOS != osWindows {
+		err = os.Chmod(keysDir, 0551)
+		assert.NoError(t, err)
+		c.HostKeys = nil
+		err = c.checkAndLoadHostKeys(keysDir, serverConfig)
+		assert.Error(t, err)
+		c.HostKeys = []string{rsaKeyName, ecdsaKeyName}
+		err = c.checkAndLoadHostKeys(configDir, serverConfig)
+		assert.Error(t, err)
+		c.HostKeys = []string{ecdsaKeyName, rsaKeyName}
+		err = c.checkAndLoadHostKeys(configDir, serverConfig)
+		assert.Error(t, err)
+		err = os.Chmod(keysDir, 0755)
+		assert.NoError(t, err)
+	}
+	err = os.RemoveAll(keysDir)
+	assert.NoError(t, err)
 }
 }
 
 
 func TestCertCheckerInitErrors(t *testing.T) {
 func TestCertCheckerInitErrors(t *testing.T) {
@@ -1853,7 +1888,7 @@ func TestUpdateQuotaAfterRenameMissingFile(t *testing.T) {
 	request := sftp.NewRequest("Rename", "/testfile")
 	request := sftp.NewRequest("Rename", "/testfile")
 	request.Filepath = "/dir"
 	request.Filepath = "/dir"
 	request.Target = path.Join("vdir", "dir")
 	request.Target = path.Join("vdir", "dir")
-	if runtime.GOOS != "windows" {
+	if runtime.GOOS != osWindows {
 		testDirPath := filepath.Join(mappedPath, "dir")
 		testDirPath := filepath.Join(mappedPath, "dir")
 		err := os.MkdirAll(testDirPath, 0777)
 		err := os.MkdirAll(testDirPath, 0777)
 		assert.NoError(t, err)
 		assert.NoError(t, err)

+ 37 - 4
sftpd/server.go

@@ -513,15 +513,40 @@ func (c *Configuration) checkSSHCommands() {
 	c.EnabledSSHCommands = sshCommands
 	c.EnabledSSHCommands = sshCommands
 }
 }
 
 
-// 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) checkHostKeyAutoGeneration(configDir string) error {
+	for _, k := range c.HostKeys {
+		if filepath.IsAbs(k) {
+			if _, err := os.Stat(k); os.IsNotExist(err) {
+				keyName := filepath.Base(k)
+				switch keyName {
+				case defaultPrivateRSAKeyName:
+					logger.Info(logSender, "", "try to create non-existent host key %#v", k)
+					logger.InfoToConsole("try to create non-existent host key %#v", k)
+					err = utils.GenerateRSAKeys(k)
+					if err != nil {
+						return err
+					}
+				case defaultPrivateECDSAKeyName:
+					logger.Info(logSender, "", "try to create non-existent host key %#v", k)
+					logger.InfoToConsole("try to create non-existent host key %#v", k)
+					err = utils.GenerateECDSAKeys(k)
+					if err != nil {
+						return err
+					}
+				default:
+					logger.Warn(logSender, "", "non-existent host key %#v will not be created", k)
+					logger.WarnToConsole("non-existent host key %#v will not be created", k)
+				}
+			}
+		}
+	}
 	if len(c.HostKeys) == 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)
 			if _, err := os.Stat(autoFile); os.IsNotExist(err) {
 			if _, err := os.Stat(autoFile); os.IsNotExist(err) {
-				logger.Info(logSender, "", "No host keys configured and %#v does not exist; creating new key for server", autoFile)
-				logger.InfoToConsole("No host keys configured and %#v does not exist; creating new key for server", autoFile)
+				logger.Info(logSender, "", "No host keys configured and %#v does not exist; try to create a new host key", autoFile)
+				logger.InfoToConsole("No host keys configured and %#v does not exist; try to create a new host key", autoFile)
 				if k == defaultPrivateRSAKeyName {
 				if k == defaultPrivateRSAKeyName {
 					err = utils.GenerateRSAKeys(autoFile)
 					err = utils.GenerateRSAKeys(autoFile)
 				} else {
 				} else {
@@ -534,6 +559,14 @@ func (c *Configuration) checkAndLoadHostKeys(configDir string, serverConfig *ssh
 			c.HostKeys = append(c.HostKeys, k)
 			c.HostKeys = append(c.HostKeys, k)
 		}
 		}
 	}
 	}
+	return nil
+}
+
+// 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 err := c.checkHostKeyAutoGeneration(configDir); err != nil {
+		return err
+	}
 	for _, k := range c.HostKeys {
 	for _, k := range c.HostKeys {
 		hostKey := k
 		hostKey := k
 		if !utils.IsFileInputValid(hostKey) {
 		if !utils.IsFileInputValid(hostKey) {