diff --git a/client/cmd/ssh.go b/client/cmd/ssh.go index 81e6c255a17..f9dbc26fc37 100644 --- a/client/cmd/ssh.go +++ b/client/cmd/ssh.go @@ -9,7 +9,6 @@ import ( "strings" "syscall" - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/netbirdio/netbird/client/internal" @@ -73,7 +72,7 @@ var sshCmd = &cobra.Command{ go func() { // blocking if err := runSSH(sshctx, host, []byte(config.SSHKey), cmd); err != nil { - log.Debug(err) + cmd.Printf("Error: %v\n", err) os.Exit(1) } cancel() diff --git a/client/internal/engine.go b/client/internal/engine.go index b50532b7de4..a5247bc2799 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -27,6 +27,7 @@ import ( "github.com/netbirdio/netbird/client/iface" "github.com/netbirdio/netbird/client/iface/bind" "github.com/netbirdio/netbird/client/iface/device" + "github.com/netbirdio/netbird/client/iface/netstack" "github.com/netbirdio/netbird/client/internal/acl" "github.com/netbirdio/netbird/client/internal/dns" "github.com/netbirdio/netbird/client/internal/dnsfwd" @@ -699,18 +700,22 @@ func (e *Engine) updateSSH(sshConf *mgmProto.SSHConfig) error { } else { if sshConf.GetSshEnabled() { - if runtime.GOOS == "windows" || runtime.GOOS == "freebsd" { + if runtime.GOOS == "windows" { log.Warnf("running SSH server on %s is not supported", runtime.GOOS) return nil } // start SSH server if it wasn't running if isNil(e.sshServer) { + listenAddr := fmt.Sprintf("%s:%d", e.wgInterface.Address().IP.String(), nbssh.DefaultSSHPort) + if netstack.IsEnabled() { + listenAddr = fmt.Sprintf("127.0.0.1:%d", nbssh.DefaultSSHPort) + } // nil sshServer means it has not yet been started var err error - e.sshServer, err = e.sshServerFunc(e.config.SSHKey, - fmt.Sprintf("%s:%d", e.wgInterface.Address().IP.String(), nbssh.DefaultSSHPort)) + e.sshServer, err = e.sshServerFunc(e.config.SSHKey, listenAddr) + if err != nil { - return err + return fmt.Errorf("create ssh server: %w", err) } go func() { // blocking @@ -759,7 +764,7 @@ func (e *Engine) updateConfig(conf *mgmProto.PeerConfig) error { if conf.GetSshConfig() != nil { err := e.updateSSH(conf.GetSshConfig()) if err != nil { - log.Warnf("failed handling SSH server setup %v", err) + log.Warnf("failed handling SSH server setup: %v", err) } } diff --git a/client/internal/engine_test.go b/client/internal/engine_test.go index 1deea1cb84e..ca49eca09f6 100644 --- a/client/internal/engine_test.go +++ b/client/internal/engine_test.go @@ -71,8 +71,7 @@ func TestMain(m *testing.M) { } func TestEngine_SSH(t *testing.T) { - // todo resolve test execution on freebsd - if runtime.GOOS == "windows" || runtime.GOOS == "freebsd" { + if runtime.GOOS == "windows" { t.Skip("skipping TestEngine_SSH") } diff --git a/client/ssh/login.go b/client/ssh/login.go index e6019578d61..d1d56ceb02f 100644 --- a/client/ssh/login.go +++ b/client/ssh/login.go @@ -2,14 +2,29 @@ package ssh import ( "fmt" - "github.com/netbirdio/netbird/util" "net" "net/netip" + "os" "os/exec" "runtime" + + "github.com/netbirdio/netbird/util" ) +func isRoot() bool { + return os.Geteuid() == 0 +} + func getLoginCmd(user string, remoteAddr net.Addr) (loginPath string, args []string, err error) { + if !isRoot() { + shell := getUserShell(user) + if shell == "" { + shell = "/bin/sh" + } + + return shell, []string{"-l"}, nil + } + loginPath, err = exec.LookPath("login") if err != nil { return "", nil, err @@ -20,17 +35,17 @@ func getLoginCmd(user string, remoteAddr net.Addr) (loginPath string, args []str return "", nil, err } - if runtime.GOOS == "linux" { - + switch runtime.GOOS { + case "linux": if util.FileExists("/etc/arch-release") && !util.FileExists("/etc/pam.d/remote") { - // detect if Arch Linux return loginPath, []string{"-f", user, "-p"}, nil } - return loginPath, []string{"-f", user, "-h", addrPort.Addr().String(), "-p"}, nil - } else if runtime.GOOS == "darwin" { + case "darwin": return loginPath, []string{"-fp", "-h", addrPort.Addr().String(), user}, nil + case "freebsd": + return loginPath, []string{"-f", user, "-h", addrPort.Addr().String(), "-p"}, nil + default: + return "", nil, fmt.Errorf("unsupported platform: %s", runtime.GOOS) } - - return "", nil, fmt.Errorf("unsupported platform") } diff --git a/client/ssh/lookup.go b/client/ssh/lookup.go index 7acef8f0b61..9a7f6ff2eef 100644 --- a/client/ssh/lookup.go +++ b/client/ssh/lookup.go @@ -6,5 +6,9 @@ package ssh import "os/user" func userNameLookup(username string) (*user.User, error) { + if username == "" || (username == "root" && !isRoot()) { + return user.Current() + } + return user.Lookup(username) } diff --git a/client/ssh/lookup_darwin.go b/client/ssh/lookup_darwin.go index e6f3c3b9300..913d049dcce 100644 --- a/client/ssh/lookup_darwin.go +++ b/client/ssh/lookup_darwin.go @@ -12,6 +12,10 @@ import ( ) func userNameLookup(username string) (*user.User, error) { + if username == "" || (username == "root" && !isRoot()) { + return user.Current() + } + var userObject *user.User userObject, err := user.Lookup(username) if err != nil && err.Error() == user.UnknownUserError(username).Error() { diff --git a/client/ssh/server.go b/client/ssh/server.go index a390302b704..1f2001d0f61 100644 --- a/client/ssh/server.go +++ b/client/ssh/server.go @@ -168,8 +168,12 @@ func (srv *DefaultServer) sessionHandler(session ssh.Session) { cmd := exec.Command(loginCmd, loginArgs...) go func() { <-session.Context().Done() + if cmd.Process == nil { + return + } err := cmd.Process.Kill() if err != nil { + log.Debugf("failed killing SSH process %v", err) return } }() @@ -185,7 +189,7 @@ func (srv *DefaultServer) sessionHandler(session ssh.Session) { log.Debugf("Login command: %s", cmd.String()) file, err := pty.Start(cmd) if err != nil { - log.Errorf("failed starting SSH server %v", err) + log.Errorf("failed starting SSH server: %v", err) } go func() { diff --git a/go.mod b/go.mod index 0c6d6be99d2..1d738dbaebb 100644 --- a/go.mod +++ b/go.mod @@ -19,8 +19,8 @@ require ( github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/vishvananda/netlink v1.2.1-beta.2 - golang.org/x/crypto v0.31.0 - golang.org/x/sys v0.28.0 + golang.org/x/crypto v0.32.0 + golang.org/x/sys v0.29.0 golang.zx2c4.com/wireguard v0.0.0-20230704135630-469159ecf7d1 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 golang.zx2c4.com/wireguard/windows v0.5.3 @@ -41,7 +41,7 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/eko/gocache/v3 v3.1.1 github.com/fsnotify/fsnotify v1.7.0 - github.com/gliderlabs/ssh v0.3.4 + github.com/gliderlabs/ssh v0.3.8 github.com/godbus/dbus/v5 v5.1.0 github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.6.0 @@ -94,7 +94,7 @@ require ( golang.org/x/net v0.30.0 golang.org/x/oauth2 v0.19.0 golang.org/x/sync v0.10.0 - golang.org/x/term v0.27.0 + golang.org/x/term v0.28.0 google.golang.org/api v0.177.0 gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/mysql v1.5.7 diff --git a/go.sum b/go.sum index f8b6c208b9b..5ca6c007e3c 100644 --- a/go.sum +++ b/go.sum @@ -212,8 +212,8 @@ github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2H github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do= -github.com/gliderlabs/ssh v0.3.4 h1:+AXBtim7MTKaLVPgvE+3mhewYRawNLTd+jEEz/wExZw= -github.com/gliderlabs/ssh v0.3.4/go.mod h1:ZSS+CUoKHDrqVakTfTWUlKSr9MtMFkC4UvtQKD7O914= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk= github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -776,14 +776,13 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -982,8 +981,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -991,8 +990,8 @@ golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=