sftp: add support for using ssh key files #1494

Update docs about macOS and ssh-agent #1218
This commit is contained in:
Nick Craig-Wood 2017-06-23 16:25:35 +01:00
parent b44d0ea088
commit d55f8f0492
2 changed files with 67 additions and 20 deletions

View file

@ -21,17 +21,14 @@ Here is an example of making a SFTP configuration. First run
rclone config rclone config
This will guide you through an interactive setup process. You will This will guide you through an interactive setup process.
need your account number (a short hex number) and key (a long hex
number) which you can get from the SFTP control panel.
``` ```
No remotes found - make a new one No remotes found - make a new one
n) New remote n) New remote
r) Rename remote
c) Copy remote
s) Set configuration password s) Set configuration password
q) Quit config q) Quit config
n/r/c/s/q> n n/s/q> n
name> remote name> remote
Type of storage to configure. Type of storage to configure.
Choose a number from below, or type in your own value Choose a number from below, or type in your own value
@ -63,6 +60,8 @@ Choose a number from below, or type in your own value
\ "sftp" \ "sftp"
14 / Yandex Disk 14 / Yandex Disk
\ "yandex" \ "yandex"
15 / http Connection
\ "http"
Storage> sftp Storage> sftp
SSH host to connect to SSH host to connect to
Choose a number from below, or type in your own value Choose a number from below, or type in your own value
@ -70,21 +69,24 @@ Choose a number from below, or type in your own value
\ "example.com" \ "example.com"
host> example.com host> example.com
SSH username, leave blank for current username, ncw SSH username, leave blank for current username, ncw
user> user> sftpuser
SSH port, leave blank to use default (22) SSH port, leave blank to use default (22)
port> port>
SSH password, leave blank to use ssh-agent SSH password, leave blank to use ssh-agent.
y) Yes type in my own password y) Yes type in my own password
g) Generate random password g) Generate random password
n) No leave this optional password blank n) No leave this optional password blank
y/g/n> n y/g/n> n
Path to unencrypted PEM-encoded private key file, leave blank to use ssh-agent.
key_file>
Remote config Remote config
-------------------- --------------------
[remote] [remote]
host = example.com host = example.com
user = user = sftpuser
port = port =
pass = pass =
key_file =
-------------------- --------------------
y) Yes this is OK y) Yes this is OK
e) Edit this remote e) Edit this remote
@ -111,6 +113,34 @@ excess files in the directory.
rclone sync /home/local/directory remote:directory rclone sync /home/local/directory remote:directory
### SSH Authentication ###
The SFTP remote supports 3 authentication methods
* Password
* Key file
* ssh-agent
Key files should be unencrypted PEM-encoded private key files. For
instance `/home/$USER/.ssh/id_rsa`.
If you don't specify `pass` or `key_file` then it will attempt to
contact an ssh-agent.
### ssh-agent on macOS ###
Note that there seem to be various problems with using an ssh-agent on
macOS due to recent changes in the OS. The most effective work-around
seems to be to start an ssh-agent in each session, eg
eval `ssh-agent -s` && ssh-add -A
And then at the end of the session
eval `ssh-agent -k`
These commands can be used in scripts of course.
### Modified time ### ### Modified time ###
Modified times are stored on the server to 1 second precision. Modified times are stored on the server to 1 second precision.

View file

@ -6,6 +6,7 @@ package sftp
import ( import (
"io" "io"
"io/ioutil"
"os" "os"
"path" "path"
"time" "time"
@ -40,9 +41,13 @@ func init() {
Optional: true, Optional: true,
}, { }, {
Name: "pass", Name: "pass",
Help: "SSH password, leave blank to use ssh-agent", Help: "SSH password, leave blank to use ssh-agent.",
Optional: true, Optional: true,
IsPassword: true, IsPassword: true,
}, {
Name: "key_file",
Help: "Path to unencrypted PEM-encoded private key file, leave blank to use ssh-agent.",
Optional: true,
}}, }},
} }
fs.Register(fsi) fs.Register(fsi)
@ -79,6 +84,7 @@ func NewFs(name, root string) (fs.Fs, error) {
host := fs.ConfigFileGet(name, "host") host := fs.ConfigFileGet(name, "host")
port := fs.ConfigFileGet(name, "port") port := fs.ConfigFileGet(name, "port")
pass := fs.ConfigFileGet(name, "pass") pass := fs.ConfigFileGet(name, "pass")
keyFile := fs.ConfigFileGet(name, "key_file")
if user == "" { if user == "" {
user = os.Getenv("USER") user = os.Getenv("USER")
} }
@ -91,7 +97,9 @@ func NewFs(name, root string) (fs.Fs, error) {
HostKeyCallback: ssh.InsecureIgnoreHostKey(), HostKeyCallback: ssh.InsecureIgnoreHostKey(),
Timeout: fs.Config.ConnectTimeout, Timeout: fs.Config.ConnectTimeout,
} }
if pass == "" {
// Add ssh agent-auth if no password or file specified
if pass == "" && keyFile == "" {
sshAgentClient, _, err := sshagent.New() sshAgentClient, _, err := sshagent.New()
if err != nil { if err != nil {
return nil, errors.Wrap(err, "couldn't connect to ssh-agent") return nil, errors.Wrap(err, "couldn't connect to ssh-agent")
@ -100,22 +108,31 @@ func NewFs(name, root string) (fs.Fs, error) {
if err != nil { if err != nil {
return nil, errors.Wrap(err, "couldn't read ssh agent signers") return nil, errors.Wrap(err, "couldn't read ssh agent signers")
} }
/*
for i, signer := range signers {
if 2*i < len(signers) {
signers[i] = signers[len(signers)-i-1]
signers[len(signers)-i-1] = signer
}
}
*/
config.Auth = append(config.Auth, ssh.PublicKeys(signers...)) config.Auth = append(config.Auth, ssh.PublicKeys(signers...))
} else { }
// Load key file if specified
if keyFile != "" {
key, err := ioutil.ReadFile(keyFile)
if err != nil {
return nil, errors.Wrap(err, "failed to read private key file")
}
signer, err := ssh.ParsePrivateKey(key)
if err != nil {
return nil, errors.Wrap(err, "failed to parse private key file")
}
config.Auth = append(config.Auth, ssh.PublicKeys(signer))
}
// Auth from password if specified
if pass != "" {
clearpass, err := fs.Reveal(pass) clearpass, err := fs.Reveal(pass)
if err != nil { if err != nil {
return nil, err return nil, err
} }
config.Auth = append(config.Auth, ssh.Password(clearpass)) config.Auth = append(config.Auth, ssh.Password(clearpass))
} }
sshClient, err := ssh.Dial("tcp", host+":"+port, config) sshClient, err := ssh.Dial("tcp", host+":"+port, config)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "couldn't connect ssh") return nil, errors.Wrap(err, "couldn't connect ssh")