forked from TrueCloudLab/restic
Merge pull request #2592 from greatroar/sftp-ipv6
Support IPv6 in SFTP backend
This commit is contained in:
commit
0c03a80fc4
6 changed files with 69 additions and 26 deletions
6
changelog/unreleased/pull-2592
Normal file
6
changelog/unreleased/pull-2592
Normal file
|
@ -0,0 +1,6 @@
|
|||
Bugfix: SFTP backend supports IPv6 addresses
|
||||
|
||||
The SFTP backend now supports IPv6 addresses natively, without relying on
|
||||
aliases in the external SSH configuration.
|
||||
|
||||
https://github.com/restic/restic/pull/2592
|
|
@ -86,10 +86,21 @@ specify the user this way: ``user@domain@host``.
|
|||
want to specify a path relative to the user's home directory, pass a
|
||||
relative path to the sftp backend.
|
||||
|
||||
The backend config string does not allow specifying a port. If you need
|
||||
to contact an sftp server on a different port, you can create an entry
|
||||
in the ``ssh`` file, usually located in your user's home directory at
|
||||
``~/.ssh/config`` or in ``/etc/ssh/ssh_config``:
|
||||
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)
|
||||
at port 2222 with username ``user`` can be specified as
|
||||
|
||||
::
|
||||
|
||||
sftp://user@[::1]:2222//srv/restic-repo
|
||||
|
||||
Note the double slash: the first slash separates the connection settings from
|
||||
the path, while the second is the start of the path. To specify a relative
|
||||
path, use one slash.
|
||||
|
||||
Alternatively, you can create an entry in the ``ssh`` configuration file,
|
||||
usually located in your home directory at ``~/.ssh/config`` or in
|
||||
``/etc/ssh/ssh_config``:
|
||||
|
||||
::
|
||||
|
||||
|
|
|
@ -11,9 +11,10 @@ import (
|
|||
|
||||
// Config collects all information required to connect to an sftp server.
|
||||
type Config struct {
|
||||
User, Host, Path string
|
||||
Layout string `option:"layout" help:"use this backend directory layout (default: auto-detect)"`
|
||||
Command string `option:"command" help:"specify command to create sftp connection"`
|
||||
User, Host, Port, Path string
|
||||
|
||||
Layout string `option:"layout" help:"use this backend directory layout (default: auto-detect)"`
|
||||
Command string `option:"command" help:"specify command to create sftp connection"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -21,12 +22,12 @@ func init() {
|
|||
}
|
||||
|
||||
// ParseConfig parses the string s and extracts the sftp config. The
|
||||
// supported configuration formats are sftp://user@host/directory
|
||||
// supported configuration formats are sftp://user@host[:port]/directory
|
||||
// and sftp:user@host:directory. The directory will be path Cleaned and can
|
||||
// be an absolute path if it starts with a '/' (e.g.
|
||||
// sftp://user@host//absolute and sftp:user@host:/absolute).
|
||||
func ParseConfig(s string) (interface{}, error) {
|
||||
var user, host, dir string
|
||||
var user, host, port, dir string
|
||||
switch {
|
||||
case strings.HasPrefix(s, "sftp://"):
|
||||
// parse the "sftp://user@host/path" url format
|
||||
|
@ -37,7 +38,8 @@ func ParseConfig(s string) (interface{}, error) {
|
|||
if url.User != nil {
|
||||
user = url.User.Username()
|
||||
}
|
||||
host = url.Host
|
||||
host = url.Hostname()
|
||||
port = url.Port()
|
||||
dir = url.Path
|
||||
if dir == "" {
|
||||
return nil, errors.Errorf("invalid backend %q, no directory specified", s)
|
||||
|
@ -76,6 +78,7 @@ func ParseConfig(s string) (interface{}, error) {
|
|||
return Config{
|
||||
User: user,
|
||||
Host: host,
|
||||
Port: port,
|
||||
Path: p,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -23,11 +23,11 @@ var configTests = []struct {
|
|||
},
|
||||
{
|
||||
"sftp://host:10022//dir/subdir",
|
||||
Config{Host: "host:10022", Path: "/dir/subdir"},
|
||||
Config{Host: "host", Port: "10022", Path: "/dir/subdir"},
|
||||
},
|
||||
{
|
||||
"sftp://user@host:10022//dir/subdir",
|
||||
Config{User: "user", Host: "host:10022", Path: "/dir/subdir"},
|
||||
Config{User: "user", Host: "host", Port: "10022", Path: "/dir/subdir"},
|
||||
},
|
||||
{
|
||||
"sftp://user@host/dir/subdir/../other",
|
||||
|
@ -38,6 +38,17 @@ var configTests = []struct {
|
|||
Config{User: "user", Host: "host", Path: "dir/subdir"},
|
||||
},
|
||||
|
||||
// IPv6 address.
|
||||
{
|
||||
"sftp://user@[::1]/dir",
|
||||
Config{User: "user", Host: "::1", Path: "dir"},
|
||||
},
|
||||
// IPv6 address with port.
|
||||
{
|
||||
"sftp://user@[::1]:22/dir",
|
||||
Config{User: "user", Host: "::1", Port: "22", Path: "dir"},
|
||||
},
|
||||
|
||||
// second form, user specified sftp:user@host:/dir
|
||||
{
|
||||
"sftp:user@host:/dir/subdir",
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
|
@ -190,10 +189,8 @@ func buildSSHCommand(cfg Config) (cmd string, args []string, err error) {
|
|||
|
||||
cmd = "ssh"
|
||||
|
||||
hostport := strings.Split(cfg.Host, ":")
|
||||
args = []string{hostport[0]}
|
||||
if len(hostport) > 1 {
|
||||
args = append(args, "-p", hostport[1])
|
||||
if cfg.Port != "" {
|
||||
args = append(args, "-p", cfg.Port)
|
||||
}
|
||||
if cfg.User != "" {
|
||||
args = append(args, "-l")
|
||||
|
@ -201,6 +198,8 @@ func buildSSHCommand(cfg Config) (cmd string, args []string, err error) {
|
|||
}
|
||||
args = append(args, "-s")
|
||||
args = append(args, "sftp")
|
||||
|
||||
args = append(args, "--", cfg.Host)
|
||||
return cmd, args, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -13,31 +13,43 @@ var sshcmdTests = []struct {
|
|||
{
|
||||
Config{User: "user", Host: "host", Path: "dir/subdir"},
|
||||
"ssh",
|
||||
[]string{"host", "-l", "user", "-s", "sftp"},
|
||||
[]string{"-l", "user", "-s", "sftp", "--", "host"},
|
||||
},
|
||||
{
|
||||
Config{Host: "host", Path: "dir/subdir"},
|
||||
"ssh",
|
||||
[]string{"host", "-s", "sftp"},
|
||||
[]string{"-s", "sftp", "--", "host"},
|
||||
},
|
||||
{
|
||||
Config{Host: "host:10022", Path: "/dir/subdir"},
|
||||
Config{Host: "host", Port: "10022", Path: "/dir/subdir"},
|
||||
"ssh",
|
||||
[]string{"host", "-p", "10022", "-s", "sftp"},
|
||||
[]string{"-p", "10022", "-s", "sftp", "--", "host"},
|
||||
},
|
||||
{
|
||||
Config{User: "user", Host: "host:10022", Path: "/dir/subdir"},
|
||||
Config{User: "user", Host: "host", Port: "10022", Path: "/dir/subdir"},
|
||||
"ssh",
|
||||
[]string{"host", "-p", "10022", "-l", "user", "-s", "sftp"},
|
||||
[]string{"-p", "10022", "-l", "user", "-s", "sftp", "--", "host"},
|
||||
},
|
||||
{
|
||||
// IPv6 address.
|
||||
Config{User: "user", Host: "::1", Path: "dir"},
|
||||
"ssh",
|
||||
[]string{"-l", "user", "-s", "sftp", "--", "::1"},
|
||||
},
|
||||
{
|
||||
// IPv6 address with zone and port.
|
||||
Config{User: "user", Host: "::1%lo0", Port: "22", Path: "dir"},
|
||||
"ssh",
|
||||
[]string{"-p", "22", "-l", "user", "-s", "sftp", "--", "::1%lo0"},
|
||||
},
|
||||
}
|
||||
|
||||
func TestBuildSSHCommand(t *testing.T) {
|
||||
for _, test := range sshcmdTests {
|
||||
for i, test := range sshcmdTests {
|
||||
t.Run("", func(t *testing.T) {
|
||||
cmd, args, err := buildSSHCommand(test.cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
t.Fatalf("%v in test %d", err, i)
|
||||
}
|
||||
|
||||
if cmd != test.cmd {
|
||||
|
@ -45,7 +57,8 @@ func TestBuildSSHCommand(t *testing.T) {
|
|||
}
|
||||
|
||||
if !reflect.DeepEqual(test.args, args) {
|
||||
t.Fatalf("wrong args, want:\n %v\ngot:\n %v", test.args, args)
|
||||
t.Fatalf("wrong args in test %d, want:\n %v\ngot:\n %v",
|
||||
i, test.args, args)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue