mirror of
https://github.com/drakkan/sftpgo.git
synced 2025-12-06 22:30:56 +03:00
Support multiple private (host) keys
With this patch, one can configure one or more private (host) keys in the configuration file. I made it a nested struct, so we can add more options later. Eg. host certificates might be a useful addition if I can figure out how this is done in golang's crypto/ssh... Signed-off-by: Jo Vandeginste <Jo.Vandeginste@kuleuven.be>
This commit is contained in:
14
README.md
14
README.md
@@ -49,7 +49,7 @@ Alternately you can use distro packages:
|
|||||||
|
|
||||||
The `sftpgo` executable supports the following command line flags:
|
The `sftpgo` executable supports the following command line flags:
|
||||||
|
|
||||||
- `-config-dir` string. Location of the config dir. This directory should contain the `sftpgo.conf` configuration file, the private key for the SFTP server (`id_rsa` file) and the SQLite database if you use SQLite as data provider. The server private key will be autogenerated if the user that executes SFTPGo has write access to the config-dir. The default value is "."
|
- `-config-dir` string. Location of the config dir. This directory should contain the `sftpgo.conf` configuration file and is used as the base for files with a relative path (eg. the private keys for the SFTP server, the SQLite database if you use SQLite as data provider). The default value is "."
|
||||||
- `-log-file-path` string. Location for the log file, default "sftpgo.log"
|
- `-log-file-path` string. Location for the log file, default "sftpgo.log"
|
||||||
- `-log-max-size` int. Maximum size in megabytes of the log file before it gets rotated. Default 10
|
- `-log-max-size` int. Maximum size in megabytes of the log file before it gets rotated. Default 10
|
||||||
- `-log-max-backups` int. Maximum number of old log files to retain. Default 5
|
- `-log-max-backups` int. Maximum number of old log files to retain. Default 5
|
||||||
@@ -57,6 +57,8 @@ The `sftpgo` executable supports the following command line flags:
|
|||||||
- `-log-compress` boolean. Determine if the rotated log files should be compressed using gzip
|
- `-log-compress` boolean. Determine if the rotated log files should be compressed using gzip
|
||||||
- `-log-verbose` boolean. Enable verbose logs. Default `true`
|
- `-log-verbose` boolean. Enable verbose logs. Default `true`
|
||||||
|
|
||||||
|
If you don't configure any private host keys, the daemon will use `id_rsa` in the configuration directory. If that file doesn't exist, the daemon will attempt to autogenerate it (if the user that executes SFTPGo has write access to the config-dir). The server supports any private key format supported by [`crypto/ssh`](https://github.com/golang/crypto/blob/master/ssh/keys.go#L32).
|
||||||
|
|
||||||
Before starting `sftpgo` a dataprovider must be configured.
|
Before starting `sftpgo` a dataprovider must be configured.
|
||||||
|
|
||||||
Sample SQL scripts to create the required database structure can be found insite the source tree [sql](https://github.com/drakkan/sftpgo/tree/master/sql "sql") directory. The SQL scripts filename's is, by convention, the date as `YYYYMMDD` and the suffix `.sql`. You need to apply all the SQL scripts for your database ordered by name, for example `20190706.sql` must be applied before `20190728.sql` and so on.
|
Sample SQL scripts to create the required database structure can be found insite the source tree [sql](https://github.com/drakkan/sftpgo/tree/master/sql "sql") directory. The SQL scripts filename's is, by convention, the date as `YYYYMMDD` and the suffix `.sql`. You need to apply all the SQL scripts for your database ordered by name, for example `20190706.sql` must be applied before `20190728.sql` and so on.
|
||||||
@@ -82,6 +84,8 @@ The `sftpgo.conf` configuration file contains the following sections:
|
|||||||
- `username`
|
- `username`
|
||||||
- `path`
|
- `path`
|
||||||
- `target_path`, added for `rename` action only
|
- `target_path`, added for `rename` action only
|
||||||
|
- `keys`, struct array. It contains the daemon's private keys
|
||||||
|
- `private_key`, path to the private key file
|
||||||
- **"data_provider"**, the configuration for the data provider
|
- **"data_provider"**, the configuration for the data provider
|
||||||
- `driver`, string. Supported drivers are `sqlite`, `mysql`, `postgresql`
|
- `driver`, string. Supported drivers are `sqlite`, `mysql`, `postgresql`
|
||||||
- `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.
|
- `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.
|
||||||
@@ -116,7 +120,15 @@ Here is a full example showing the default config:
|
|||||||
"execute_on": [],
|
"execute_on": [],
|
||||||
"command": "",
|
"command": "",
|
||||||
"http_notification_url": ""
|
"http_notification_url": ""
|
||||||
|
},
|
||||||
|
"keys": [
|
||||||
|
{
|
||||||
|
"private_key": "id_rsa"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"private_key": "id_ecdsa"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"data_provider": {
|
"data_provider": {
|
||||||
"driver": "sqlite",
|
"driver": "sqlite",
|
||||||
|
|||||||
5
main.go
5
main.go
@@ -36,9 +36,8 @@ func main() {
|
|||||||
logCompress bool
|
logCompress bool
|
||||||
logVerbose bool
|
logVerbose bool
|
||||||
)
|
)
|
||||||
flag.StringVar(&configDir, "config-dir", ".", "Location for SFTPGo config dir. It must contain sftpgo.conf, "+
|
flag.StringVar(&configDir, "config-dir", ".", "Location for SFTPGo config dir. It must contain sftpgo.conf "+
|
||||||
"the private key for the SFTP server (id_rsa file) and the SQLite database if you use SQLite as data provider. "+
|
"and is used as the base for files with a relative path (eg. the private keys for the SFTP server, the SQLite database if you use SQLite as data provider).")
|
||||||
"The server private key will be autogenerated if the user that executes SFTPGo has write access to the config-dir")
|
|
||||||
flag.StringVar(&logFilePath, "log-file-path", "sftpgo.log", "Location for the log file")
|
flag.StringVar(&logFilePath, "log-file-path", "sftpgo.log", "Location for the log file")
|
||||||
flag.IntVar(&logMaxSize, "log-max-size", 10, "Maximum size in megabytes of the log file before it gets rotated.")
|
flag.IntVar(&logMaxSize, "log-max-size", 10, "Maximum size in megabytes of the log file before it gets rotated.")
|
||||||
flag.IntVar(&logMaxBackups, "log-max-backups", 5, "Maximum number of old log files to retain")
|
flag.IntVar(&logMaxBackups, "log-max-backups", 5, "Maximum number of old log files to retain")
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ import (
|
|||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const defaultPrivateKeyName = "id_rsa"
|
||||||
|
|
||||||
// Configuration for the SFTP server
|
// Configuration for the SFTP server
|
||||||
type Configuration struct {
|
type Configuration struct {
|
||||||
// Identification string used by the server
|
// Identification string used by the server
|
||||||
@@ -43,6 +45,14 @@ type Configuration struct {
|
|||||||
Umask string `json:"umask"`
|
Umask string `json:"umask"`
|
||||||
// Actions to execute on SFTP create, download, delete and rename
|
// Actions to execute on SFTP create, download, delete and rename
|
||||||
Actions Actions `json:"actions"`
|
Actions Actions `json:"actions"`
|
||||||
|
// Keys are a list of host keys
|
||||||
|
Keys []Key `json:"keys"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Struct containing information about host keys
|
||||||
|
type Key struct {
|
||||||
|
// The private key
|
||||||
|
PrivateKey string `json:"private_key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the SFTP server and add a persistent listener to handle inbound SFTP connections.
|
// Initialize the SFTP server and add a persistent listener to handle inbound SFTP connections.
|
||||||
@@ -76,17 +86,29 @@ func (c Configuration) Initialize(configDir string) error {
|
|||||||
ServerVersion: "SSH-2.0-" + c.Banner,
|
ServerVersion: "SSH-2.0-" + c.Banner,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat(filepath.Join(configDir, "id_rsa")); os.IsNotExist(err) {
|
if len(c.Keys) == 0 {
|
||||||
logger.Info(logSender, "creating new private key for server")
|
autoFile := filepath.Join(configDir, defaultPrivateKeyName)
|
||||||
logger.InfoToConsole("id_rsa does not exist, creating new private key for server")
|
if _, err := os.Stat(autoFile); os.IsNotExist(err) {
|
||||||
if err := c.generatePrivateKey(configDir); err != nil {
|
logger.Info(logSender, "No host keys configured and %s does not exist; creating new private key for server", autoFile)
|
||||||
|
logger.InfoToConsole("No host keys configured and %s does not exist; creating new private key for server", autoFile)
|
||||||
|
if err := c.generatePrivateKey(autoFile); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
privateBytes, err := ioutil.ReadFile(filepath.Join(configDir, "id_rsa"))
|
c.Keys = append(c.Keys, Key{PrivateKey: defaultPrivateKeyName})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range c.Keys {
|
||||||
|
privateFile := k.PrivateKey
|
||||||
|
if !filepath.IsAbs(privateFile) {
|
||||||
|
privateFile = filepath.Join(configDir, privateFile)
|
||||||
|
}
|
||||||
|
logger.Info(logSender, "Loading private key: %s", privateFile)
|
||||||
|
|
||||||
|
privateBytes, err := ioutil.ReadFile(privateFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -98,6 +120,7 @@ func (c Configuration) Initialize(configDir string) error {
|
|||||||
|
|
||||||
// Add our private key to the server configuration.
|
// Add our private key to the server configuration.
|
||||||
serverConfig.AddHostKey(private)
|
serverConfig.AddHostKey(private)
|
||||||
|
}
|
||||||
|
|
||||||
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", c.BindAddress, c.BindPort))
|
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", c.BindAddress, c.BindPort))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -273,13 +296,13 @@ func (c Configuration) validatePasswordCredentials(conn ssh.ConnMetadata, pass [
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generates a private key that will be used by the SFTP server.
|
// Generates a private key that will be used by the SFTP server.
|
||||||
func (c Configuration) generatePrivateKey(configDir string) error {
|
func (c Configuration) generatePrivateKey(file string) error {
|
||||||
key, err := rsa.GenerateKey(rand.Reader, 4096)
|
key, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
o, err := os.OpenFile(filepath.Join(configDir, "id_rsa"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
o, err := os.OpenFile(file, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user