diff --git a/docs/content/sftp.md b/docs/content/sftp.md index d173705d2..188a8b5fc 100644 --- a/docs/content/sftp.md +++ b/docs/content/sftp.md @@ -21,17 +21,14 @@ Here is an example of making a SFTP configuration. First run rclone config -This will guide you through an interactive setup process. You will -need your account number (a short hex number) and key (a long hex -number) which you can get from the SFTP control panel. +This will guide you through an interactive setup process. + ``` No remotes found - make a new one n) New remote -r) Rename remote -c) Copy remote s) Set configuration password q) Quit config -n/r/c/s/q> n +n/s/q> n name> remote Type of storage to configure. 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" 14 / Yandex Disk \ "yandex" +15 / http Connection + \ "http" Storage> sftp SSH host to connect to 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" host> example.com SSH username, leave blank for current username, ncw -user> +user> sftpuser SSH port, leave blank to use default (22) port> -SSH password, leave blank to use ssh-agent +SSH password, leave blank to use ssh-agent. y) Yes type in my own password g) Generate random password n) No leave this optional password blank y/g/n> n +Path to unencrypted PEM-encoded private key file, leave blank to use ssh-agent. +key_file> Remote config -------------------- [remote] host = example.com -user = +user = sftpuser port = pass = +key_file = -------------------- y) Yes this is OK e) Edit this remote @@ -111,6 +113,34 @@ excess files in the 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 times are stored on the server to 1 second precision. diff --git a/sftp/sftp.go b/sftp/sftp.go index 77df26365..a0f6254b6 100644 --- a/sftp/sftp.go +++ b/sftp/sftp.go @@ -6,6 +6,7 @@ package sftp import ( "io" + "io/ioutil" "os" "path" "time" @@ -40,9 +41,13 @@ func init() { Optional: true, }, { Name: "pass", - Help: "SSH password, leave blank to use ssh-agent", + Help: "SSH password, leave blank to use ssh-agent.", Optional: 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) @@ -79,6 +84,7 @@ func NewFs(name, root string) (fs.Fs, error) { host := fs.ConfigFileGet(name, "host") port := fs.ConfigFileGet(name, "port") pass := fs.ConfigFileGet(name, "pass") + keyFile := fs.ConfigFileGet(name, "key_file") if user == "" { user = os.Getenv("USER") } @@ -91,7 +97,9 @@ func NewFs(name, root string) (fs.Fs, error) { HostKeyCallback: ssh.InsecureIgnoreHostKey(), Timeout: fs.Config.ConnectTimeout, } - if pass == "" { + + // Add ssh agent-auth if no password or file specified + if pass == "" && keyFile == "" { sshAgentClient, _, err := sshagent.New() if err != nil { 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 { 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...)) - } 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) if err != nil { return nil, err } config.Auth = append(config.Auth, ssh.Password(clearpass)) } + sshClient, err := ssh.Dial("tcp", host+":"+port, config) if err != nil { return nil, errors.Wrap(err, "couldn't connect ssh")