Compare commits

...

2 Commits

Author SHA1 Message Date
70788ba261 Expose proxy host to permissions system
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-08-12 17:43:12 -07:00
e7994824a5 Add the ability to specify multiple proxy hosts
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-08-12 17:20:14 -07:00
3 changed files with 66 additions and 16 deletions

View File

@ -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

View File

@ -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"`
@ -53,9 +55,6 @@ type proxySettings struct {
func Proxy(route config.Route) router.Handler {
return func(sess ssh.Session, arg string) error {
user, _ := sshctx.GetUser(sess.Context())
if !route.Permissions.IsAllowed(user, "*") {
return router.ErrUnauthorized
}
var opts proxySettings
err := gocty.FromCtyValue(route.Settings, &opts)
@ -78,14 +77,56 @@ func Proxy(route config.Route) router.Handler {
opts.User = &user.Name
}
}
if opts.Port == nil {
port := uint(22)
opts.Port = &port
matched := false
addr := arg
var portstr, pattern string
if opts.Host == nil {
hosts := ctyTupleToStrings(opts.Hosts)
if len(hosts) == 0 {
return errors.New("no host configuration provided")
}
for _, hostPattern := range hosts {
pattern, 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(pattern, 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 !route.Permissions.IsAllowed(user, addr) {
return router.ErrUnauthorized
}
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 +146,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 +214,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
}

View File

@ -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"
}
}