diff --git a/README.md b/README.md index 93ca58c..6c83a4e 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ See the [serial](https://gitea.elara.ws/Elara6331/seashell/wiki/Backends#serial) Seashell can proxy another SSH server. In this case, your client will authenticate to seashell and then seashell will authenticate to the target server, so you should provide seashell with a private key to use for authentication and encryption. If you don't provide this, seashell will ask the authenticating user for the target server's password. -The proxy backend takes no extra arguments, so the `ssh` command only requires your username and the routing path: +Here's an example command: ```bash ssh user:myproxy@ssh.example.com diff --git a/internal/backends/proxy.go b/internal/backends/proxy.go index 65bfb2a..88dd8db 100644 --- a/internal/backends/proxy.go +++ b/internal/backends/proxy.go @@ -27,6 +27,8 @@ import ( "io" "net" "os" + "path" + "strconv" "strings" "github.com/gliderlabs/ssh" @@ -41,8 +43,8 @@ import ( // proxySettings represents settings for the proxy backend. type proxySettings struct { - Server string `cty:"server"` - Port *uint `cty:"port"` + Host *string `cty:"host"` + Hosts *cty.Value `cty:"hosts"` User *string `cty:"user"` PrivkeyPath *string `cty:"privkey"` UserMap *cty.Value `cty:"user_map"` @@ -78,14 +80,51 @@ func Proxy(route config.Route) router.Handler { opts.User = &user.Name } } - - if opts.Port == nil { - port := uint(22) - opts.Port = &port + + var matched bool + var addr, portstr string + if opts.Host == nil { + hosts := ctyTupleToStrings(opts.Hosts) + if len(hosts) == 0 { + return errors.New("no host configuration provided") + } + + for _, hostPattern := range hosts { + addr, portstr, ok = strings.Cut(hostPattern, ":") + if !ok { + // addr is already set by the above statement, so just set the default port + portstr = "22" + } + + matched, err = path.Match(addr, arg) + if err != nil { + return err + } + + if matched { + addr = arg + break + } + } + } else { + addr, portstr, ok = strings.Cut(*opts.Host, ":") + if !ok { + // addr is already set by the above statement, so just set the default port + portstr = "22" + } + } + + if !matched { + return errors.New("provided argument doesn't match any host patterns in configuration") + } + + port, err := strconv.ParseUint(portstr, 10, 16) + if err != nil { + return err } auth := goph.Auth{ - gossh.PasswordCallback(requestPassword(opts, sess)), + gossh.PasswordCallback(requestPassword(opts, sess, addr)), } if opts.PrivkeyPath != nil { @@ -105,8 +144,8 @@ func Proxy(route config.Route) router.Handler { c, err := goph.NewConn(&goph.Config{ Auth: auth, User: *opts.User, - Addr: opts.Server, - Port: *opts.Port, + Addr: addr, + Port: uint(port), Callback: func(host string, remote net.Addr, key gossh.PublicKey) error { found, err := goph.CheckKnownHost(host, remote, key, "") if !found { @@ -173,9 +212,9 @@ func Proxy(route config.Route) router.Handler { } // requestPassword asks the client for the remote server's password -func requestPassword(opts proxySettings, sess ssh.Session) func() (secret string, err error) { +func requestPassword(opts proxySettings, sess ssh.Session, addr string) func() (secret string, err error) { return func() (secret string, err error) { - _, err = fmt.Fprintf(sess.Stderr(), "Password for %s@%s: ", *opts.User, opts.Server) + _, err = fmt.Fprintf(sess.Stderr(), "Password for %s@%s: ", *opts.User, addr) if err != nil { return "", err } diff --git a/seashell.ex.hcl b/seashell.ex.hcl index 4dfdae7..f7a0a33 100644 --- a/seashell.ex.hcl +++ b/seashell.ex.hcl @@ -20,7 +20,16 @@ route "srv" { backend = "proxy" match = "srv" settings = { - server = "1.2.3.4" + host = "1.2.3.4" + privkey = "/home/elara/.ssh/id_ed25519" + } +} + +route "cluster" { + backend = "proxy" + match = "cluster\\.(.+)" + settings = { + hosts = ["node*", "nas", "192.168.1.*"] privkey = "/home/elara/.ssh/id_ed25519" } }