mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-25 00:50:31 +00: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:
parent
1566e43cd7
commit
bd7b6a785e
4 changed files with 125 additions and 91 deletions
72
README.md
72
README.md
|
@ -49,7 +49,7 @@ Alternately you can use distro packages:
|
|||
|
||||
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-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
|
||||
|
@ -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-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.
|
||||
|
||||
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`
|
||||
- `path`
|
||||
- `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
|
||||
- `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.
|
||||
|
@ -105,36 +109,44 @@ Here is a full example showing the default config:
|
|||
|
||||
```json
|
||||
{
|
||||
"sftpd":{
|
||||
"bind_port":2022,
|
||||
"bind_address":"",
|
||||
"idle_timeout":15,
|
||||
"max_auth_tries":0,
|
||||
"umask":"0022",
|
||||
"banner":"SFTPGo",
|
||||
"actions":{
|
||||
"execute_on":[],
|
||||
"command":"",
|
||||
"http_notification_url":""
|
||||
}
|
||||
},
|
||||
"data_provider":{
|
||||
"driver":"sqlite",
|
||||
"name":"sftpgo.db",
|
||||
"host":"",
|
||||
"port":5432,
|
||||
"username":"",
|
||||
"password":"",
|
||||
"sslmode":0,
|
||||
"connection_string":"",
|
||||
"users_table":"users",
|
||||
"manage_users":1,
|
||||
"track_quota":2
|
||||
"sftpd": {
|
||||
"bind_port": 2022,
|
||||
"bind_address": "",
|
||||
"idle_timeout": 15,
|
||||
"max_auth_tries": 0,
|
||||
"umask": "0022",
|
||||
"banner": "SFTPGo",
|
||||
"actions": {
|
||||
"execute_on": [],
|
||||
"command": "",
|
||||
"http_notification_url": ""
|
||||
},
|
||||
"httpd":{
|
||||
"bind_port":8080,
|
||||
"bind_address":"127.0.0.1"
|
||||
}
|
||||
"keys": [
|
||||
{
|
||||
"private_key": "id_rsa"
|
||||
},
|
||||
{
|
||||
"private_key": "id_ecdsa"
|
||||
}
|
||||
]
|
||||
},
|
||||
"data_provider": {
|
||||
"driver": "sqlite",
|
||||
"name": "sftpgo.db",
|
||||
"host": "",
|
||||
"port": 5432,
|
||||
"username": "",
|
||||
"password": "",
|
||||
"sslmode": 0,
|
||||
"connection_string": "",
|
||||
"users_table": "users",
|
||||
"manage_users": 1,
|
||||
"track_quota": 2
|
||||
},
|
||||
"httpd": {
|
||||
"bind_port": 8080,
|
||||
"bind_address": "127.0.0.1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
|
5
main.go
5
main.go
|
@ -36,9 +36,8 @@ func main() {
|
|||
logCompress bool
|
||||
logVerbose bool
|
||||
)
|
||||
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. "+
|
||||
"The server private key will be autogenerated if the user that executes SFTPGo has write access to the config-dir")
|
||||
flag.StringVar(&configDir, "config-dir", ".", "Location for SFTPGo config dir. It must contain sftpgo.conf "+
|
||||
"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).")
|
||||
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(&logMaxBackups, "log-max-backups", 5, "Maximum number of old log files to retain")
|
||||
|
|
|
@ -25,6 +25,8 @@ import (
|
|||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
const defaultPrivateKeyName = "id_rsa"
|
||||
|
||||
// Configuration for the SFTP server
|
||||
type Configuration struct {
|
||||
// Identification string used by the server
|
||||
|
@ -43,6 +45,14 @@ type Configuration struct {
|
|||
Umask string `json:"umask"`
|
||||
// Actions to execute on SFTP create, download, delete and rename
|
||||
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.
|
||||
|
@ -76,28 +86,41 @@ func (c Configuration) Initialize(configDir string) error {
|
|||
ServerVersion: "SSH-2.0-" + c.Banner,
|
||||
}
|
||||
|
||||
if _, err := os.Stat(filepath.Join(configDir, "id_rsa")); os.IsNotExist(err) {
|
||||
logger.Info(logSender, "creating new private key for server")
|
||||
logger.InfoToConsole("id_rsa does not exist, creating new private key for server")
|
||||
if err := c.generatePrivateKey(configDir); err != nil {
|
||||
if len(c.Keys) == 0 {
|
||||
autoFile := filepath.Join(configDir, defaultPrivateKeyName)
|
||||
if _, err := os.Stat(autoFile); os.IsNotExist(err) {
|
||||
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
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
|
||||
c.Keys = append(c.Keys, Key{PrivateKey: defaultPrivateKeyName})
|
||||
}
|
||||
|
||||
privateBytes, err := ioutil.ReadFile(filepath.Join(configDir, "id_rsa"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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)
|
||||
|
||||
private, err := ssh.ParsePrivateKey(privateBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
privateBytes, err := ioutil.ReadFile(privateFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add our private key to the server configuration.
|
||||
serverConfig.AddHostKey(private)
|
||||
private, err := ssh.ParsePrivateKey(privateBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add our private key to the server configuration.
|
||||
serverConfig.AddHostKey(private)
|
||||
}
|
||||
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", c.BindAddress, c.BindPort))
|
||||
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.
|
||||
func (c Configuration) generatePrivateKey(configDir string) error {
|
||||
func (c Configuration) generatePrivateKey(file string) error {
|
||||
key, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
|
58
sftpgo.conf
58
sftpgo.conf
|
@ -1,32 +1,32 @@
|
|||
{
|
||||
"sftpd":{
|
||||
"bind_port":2022,
|
||||
"bind_address":"",
|
||||
"idle_timeout":15,
|
||||
"max_auth_tries":0,
|
||||
"umask":"0022",
|
||||
"banner":"SFTPGo",
|
||||
"actions":{
|
||||
"execute_on":[],
|
||||
"command":"",
|
||||
"http_notification_url":""
|
||||
}
|
||||
},
|
||||
"data_provider":{
|
||||
"driver":"sqlite",
|
||||
"name":"sftpgo.db",
|
||||
"host":"",
|
||||
"port":5432,
|
||||
"username":"",
|
||||
"password":"",
|
||||
"sslmode":0,
|
||||
"connection_string":"",
|
||||
"users_table":"users",
|
||||
"manage_users":1,
|
||||
"track_quota":2
|
||||
},
|
||||
"httpd":{
|
||||
"bind_port":8080,
|
||||
"bind_address":"127.0.0.1"
|
||||
"sftpd": {
|
||||
"bind_port": 2022,
|
||||
"bind_address": "",
|
||||
"idle_timeout": 15,
|
||||
"max_auth_tries": 0,
|
||||
"umask": "0022",
|
||||
"banner": "SFTPGo",
|
||||
"actions": {
|
||||
"execute_on": [],
|
||||
"command": "",
|
||||
"http_notification_url": ""
|
||||
}
|
||||
},
|
||||
"data_provider": {
|
||||
"driver": "sqlite",
|
||||
"name": "sftpgo.db",
|
||||
"host": "",
|
||||
"port": 5432,
|
||||
"username": "",
|
||||
"password": "",
|
||||
"sslmode": 0,
|
||||
"connection_string": "",
|
||||
"users_table": "users",
|
||||
"manage_users": 1,
|
||||
"track_quota": 2
|
||||
},
|
||||
"httpd": {
|
||||
"bind_port": 8080,
|
||||
"bind_address": "127.0.0.1"
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue