Merge pull request #4519 from adamantike/backend/sftp/add-args-option

backend/sftp: Add sftp.args option
This commit is contained in:
Michael Eischer 2023-10-21 17:37:46 +00:00 committed by GitHub
commit ab6defbace
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 66 additions and 11 deletions

View File

@ -0,0 +1,16 @@
Enhancement: Add config option to set SFTP command arguments
The `sftp.args` option can be passed to restic (using `-o`) to specify
custom arguments to be used by the SSH command executed by the SFTP
backend.
Before this change, a common scenario where a custom identity file was
needed for the SSH connection, required the full command to be
specified:
`-o sftp.command='ssh user@host:port -i /ssh/my_private_key -s sftp'`
With this new configuration option:
`-o sftp.args='-i /ssh/my_private_key'`
https://github.com/restic/restic/pull/4519
https://github.com/restic/restic/issues/4241

View File

@ -119,10 +119,10 @@ user's home directory.
Also, if the SFTP server is enforcing domain-confined users, you can Also, if the SFTP server is enforcing domain-confined users, you can
specify the user this way: ``user@domain@host``. specify the user this way: ``user@domain@host``.
.. note:: Please be aware that sftp servers do not expand the tilde character .. note:: Please be aware that SFTP servers do not expand the tilde character
(``~``) normally used as an alias for a user's home directory. If you (``~``) normally used as an alias for a user's home directory. If you
want to specify a path relative to the user's home directory, pass a want to specify a path relative to the user's home directory, pass a
relative path to the sftp backend. relative path to the SFTP backend.
If you need to specify a port number or IPv6 address, you'll need to use If you need to specify a port number or IPv6 address, you'll need to use
URL syntax. E.g., the repository ``/srv/restic-repo`` on ``[::1]`` (localhost) URL syntax. E.g., the repository ``/srv/restic-repo`` on ``[::1]`` (localhost)
@ -172,9 +172,11 @@ Then use it in the backend specification:
Last, if you'd like to use an entirely different program to create the Last, if you'd like to use an entirely different program to create the
SFTP connection, you can specify the command to be run with the option SFTP connection, you can specify the command to be run with the option
``-o sftp.command="foobar"``. ``-o sftp.command="foobar"``. Alternatively, ``-o sftp.args`` allows
setting the arguments passed to the default SSH command (ignored when
``sftp.command`` is set)
.. note:: Please be aware that sftp servers close connections when no data is .. note:: Please be aware that SFTP servers close connections when no data is
received by the client. This can happen when restic is processing huge received by the client. This can happen when restic is processing huge
amounts of unchanged data. To avoid this issue add the following lines amounts of unchanged data. To avoid this issue add the following lines
to the client's .ssh/config file: to the client's .ssh/config file:

View File

@ -13,8 +13,9 @@ import (
type Config struct { type Config struct {
User, Host, Port, Path string User, Host, Port, Path string
Layout string `option:"layout" help:"use this backend directory layout (default: auto-detect)"` Layout string `option:"layout" help:"use this backend directory layout (default: auto-detect)"`
Command string `option:"command" help:"specify command to create sftp connection"` Command string `option:"command" help:"specify command to create sftp connection"`
Args string `option:"args" help:"specify arguments for ssh"`
Connections uint `option:"connections" help:"set a limit for the number of concurrent connections (default: 5)"` Connections uint `option:"connections" help:"set a limit for the number of concurrent connections (default: 5)"`
} }

View File

@ -213,6 +213,9 @@ func buildSSHCommand(cfg Config) (cmd string, args []string, err error) {
if err != nil { if err != nil {
return "", nil, err return "", nil, err
} }
if cfg.Args != "" {
return "", nil, errors.New("cannot specify both sftp.command and sftp.args options")
}
return args[0], args[1:], nil return args[0], args[1:], nil
} }
@ -226,11 +229,19 @@ func buildSSHCommand(cfg Config) (cmd string, args []string, err error) {
args = append(args, "-p", port) args = append(args, "-p", port)
} }
if cfg.User != "" { if cfg.User != "" {
args = append(args, "-l") args = append(args, "-l", cfg.User)
args = append(args, cfg.User)
} }
args = append(args, "-s")
args = append(args, "sftp") if cfg.Args != "" {
a, err := backend.SplitShellStrings(cfg.Args)
if err != nil {
return "", nil, err
}
args = append(args, a...)
}
args = append(args, "-s", "sftp")
return cmd, args, nil return cmd, args, nil
} }

View File

@ -9,38 +9,57 @@ var sshcmdTests = []struct {
cfg Config cfg Config
cmd string cmd string
args []string args []string
err string
}{ }{
{ {
Config{User: "user", Host: "host", Path: "dir/subdir"}, Config{User: "user", Host: "host", Path: "dir/subdir"},
"ssh", "ssh",
[]string{"host", "-l", "user", "-s", "sftp"}, []string{"host", "-l", "user", "-s", "sftp"},
"",
}, },
{ {
Config{Host: "host", Path: "dir/subdir"}, Config{Host: "host", Path: "dir/subdir"},
"ssh", "ssh",
[]string{"host", "-s", "sftp"}, []string{"host", "-s", "sftp"},
"",
}, },
{ {
Config{Host: "host", Port: "10022", Path: "/dir/subdir"}, Config{Host: "host", Port: "10022", Path: "/dir/subdir"},
"ssh", "ssh",
[]string{"host", "-p", "10022", "-s", "sftp"}, []string{"host", "-p", "10022", "-s", "sftp"},
"",
}, },
{ {
Config{User: "user", Host: "host", Port: "10022", Path: "/dir/subdir"}, Config{User: "user", Host: "host", Port: "10022", Path: "/dir/subdir"},
"ssh", "ssh",
[]string{"host", "-p", "10022", "-l", "user", "-s", "sftp"}, []string{"host", "-p", "10022", "-l", "user", "-s", "sftp"},
"",
},
{
Config{User: "user", Host: "host", Port: "10022", Path: "/dir/subdir", Args: "-i /path/to/id_rsa"},
"ssh",
[]string{"host", "-p", "10022", "-l", "user", "-i", "/path/to/id_rsa", "-s", "sftp"},
"",
},
{
Config{Command: "ssh something", Args: "-i /path/to/id_rsa"},
"",
nil,
"cannot specify both sftp.command and sftp.args options",
}, },
{ {
// IPv6 address. // IPv6 address.
Config{User: "user", Host: "::1", Path: "dir"}, Config{User: "user", Host: "::1", Path: "dir"},
"ssh", "ssh",
[]string{"::1", "-l", "user", "-s", "sftp"}, []string{"::1", "-l", "user", "-s", "sftp"},
"",
}, },
{ {
// IPv6 address with zone and port. // IPv6 address with zone and port.
Config{User: "user", Host: "::1%lo0", Port: "22", Path: "dir"}, Config{User: "user", Host: "::1%lo0", Port: "22", Path: "dir"},
"ssh", "ssh",
[]string{"::1%lo0", "-p", "22", "-l", "user", "-s", "sftp"}, []string{"::1%lo0", "-p", "22", "-l", "user", "-s", "sftp"},
"",
}, },
} }
@ -48,8 +67,14 @@ func TestBuildSSHCommand(t *testing.T) {
for i, test := range sshcmdTests { for i, test := range sshcmdTests {
t.Run("", func(t *testing.T) { t.Run("", func(t *testing.T) {
cmd, args, err := buildSSHCommand(test.cfg) cmd, args, err := buildSSHCommand(test.cfg)
if err != nil { if test.err != "" {
t.Fatalf("%v in test %d", err, i) if err.Error() != test.err {
t.Fatalf("expected error %v got %v", test.err, err.Error())
}
} else {
if err != nil {
t.Fatalf("%v in test %d", err, i)
}
} }
if cmd != test.cmd { if cmd != test.cmd {