2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-29 08:14:03 +00:00

Merge pull request #2592 from greatroar/sftp-ipv6

Support IPv6 in SFTP backend
This commit is contained in:
Alexander Neumann 2020-03-01 19:38:44 +01:00 committed by GitHub
commit 0c03a80fc4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 69 additions and 26 deletions

View 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

View File

@ -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 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.
The backend config string does not allow specifying a port. If you need If you need to specify a port number or IPv6 address, you'll need to use
to contact an sftp server on a different port, you can create an entry URL syntax. E.g., the repository ``/srv/restic-repo`` on ``[::1]`` (localhost)
in the ``ssh`` file, usually located in your user's home directory at at port 2222 with username ``user`` can be specified as
``~/.ssh/config`` or in ``/etc/ssh/ssh_config``:
::
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``:
:: ::

View File

@ -11,9 +11,10 @@ import (
// Config collects all information required to connect to an sftp server. // Config collects all information required to connect to an sftp server.
type Config struct { type Config struct {
User, Host, Path string 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"` 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() { func init() {
@ -21,12 +22,12 @@ func init() {
} }
// ParseConfig parses the string s and extracts the sftp config. The // 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 // and sftp:user@host:directory. The directory will be path Cleaned and can
// be an absolute path if it starts with a '/' (e.g. // be an absolute path if it starts with a '/' (e.g.
// sftp://user@host//absolute and sftp:user@host:/absolute). // sftp://user@host//absolute and sftp:user@host:/absolute).
func ParseConfig(s string) (interface{}, error) { func ParseConfig(s string) (interface{}, error) {
var user, host, dir string var user, host, port, dir string
switch { switch {
case strings.HasPrefix(s, "sftp://"): case strings.HasPrefix(s, "sftp://"):
// parse the "sftp://user@host/path" url format // parse the "sftp://user@host/path" url format
@ -37,7 +38,8 @@ func ParseConfig(s string) (interface{}, error) {
if url.User != nil { if url.User != nil {
user = url.User.Username() user = url.User.Username()
} }
host = url.Host host = url.Hostname()
port = url.Port()
dir = url.Path dir = url.Path
if dir == "" { if dir == "" {
return nil, errors.Errorf("invalid backend %q, no directory specified", s) return nil, errors.Errorf("invalid backend %q, no directory specified", s)
@ -76,6 +78,7 @@ func ParseConfig(s string) (interface{}, error) {
return Config{ return Config{
User: user, User: user,
Host: host, Host: host,
Port: port,
Path: p, Path: p,
}, nil }, nil
} }

View File

@ -23,11 +23,11 @@ var configTests = []struct {
}, },
{ {
"sftp://host:10022//dir/subdir", "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", "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", "sftp://user@host/dir/subdir/../other",
@ -38,6 +38,17 @@ var configTests = []struct {
Config{User: "user", Host: "host", Path: "dir/subdir"}, 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 // second form, user specified sftp:user@host:/dir
{ {
"sftp:user@host:/dir/subdir", "sftp:user@host:/dir/subdir",

View File

@ -8,7 +8,6 @@ import (
"os" "os"
"os/exec" "os/exec"
"path" "path"
"strings"
"time" "time"
"github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/errors"
@ -190,10 +189,8 @@ func buildSSHCommand(cfg Config) (cmd string, args []string, err error) {
cmd = "ssh" cmd = "ssh"
hostport := strings.Split(cfg.Host, ":") if cfg.Port != "" {
args = []string{hostport[0]} args = append(args, "-p", cfg.Port)
if len(hostport) > 1 {
args = append(args, "-p", hostport[1])
} }
if cfg.User != "" { if cfg.User != "" {
args = append(args, "-l") 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, "-s")
args = append(args, "sftp") args = append(args, "sftp")
args = append(args, "--", cfg.Host)
return cmd, args, nil return cmd, args, nil
} }

View File

@ -13,31 +13,43 @@ var sshcmdTests = []struct {
{ {
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{"-l", "user", "-s", "sftp", "--", "host"},
}, },
{ {
Config{Host: "host", Path: "dir/subdir"}, Config{Host: "host", Path: "dir/subdir"},
"ssh", "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", "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", "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) { func TestBuildSSHCommand(t *testing.T) {
for _, 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 err != nil {
t.Fatal(err) t.Fatalf("%v in test %d", err, i)
} }
if cmd != test.cmd { if cmd != test.cmd {
@ -45,7 +57,8 @@ func TestBuildSSHCommand(t *testing.T) {
} }
if !reflect.DeepEqual(test.args, args) { 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)
} }
}) })
} }