// testsshd is a minimal SSH server for rsend integration tests: one host key, // an authorized_keys allowlist, and command execution via /bin/sh (so it runs // the remote rsync). It is not a product component; it stands in for a real // sshd so the full push path can be exercised on a device or in CI. package main import ( "flag" "io" "log" "net" "os" "os/exec" "golang.org/x/crypto/ssh" ) func main() { addr := flag.String("addr", "127.0.0.1:2222", "listen address") hostKeyPath := flag.String("hostkey", "", "OpenSSH private host key (required)") authPath := flag.String("authorized", "", "authorized_keys file (required)") flag.Parse() if *hostKeyPath == "" || *authPath == "" { log.Fatal("testsshd: -hostkey and -authorized are required") } hostKey := mustSigner(*hostKeyPath) allowed := mustAuthorized(*authPath) cfg := &ssh.ServerConfig{ PublicKeyCallback: func(_ ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) { if allowed[string(key.Marshal())] { return &ssh.Permissions{}, nil } return nil, io.EOF }, } cfg.AddHostKey(hostKey) ln, err := net.Listen("tcp", *addr) if err != nil { log.Fatalf("testsshd: listen: %v", err) } log.Printf("testsshd: listening on %s", ln.Addr()) for { c, err := ln.Accept() if err != nil { log.Fatalf("testsshd: accept: %v", err) } go handle(c, cfg) } } func mustSigner(path string) ssh.Signer { b, err := os.ReadFile(path) if err != nil { log.Fatalf("testsshd: read host key: %v", err) } s, err := ssh.ParsePrivateKey(b) if err != nil { log.Fatalf("testsshd: parse host key: %v", err) } return s } func mustAuthorized(path string) map[string]bool { b, err := os.ReadFile(path) if err != nil { log.Fatalf("testsshd: read authorized_keys: %v", err) } m := map[string]bool{} for len(b) > 0 { key, _, _, rest, err := ssh.ParseAuthorizedKey(b) if err != nil { break } m[string(key.Marshal())] = true b = rest } if len(m) == 0 { log.Fatal("testsshd: no authorized keys") } return m } func handle(nConn net.Conn, cfg *ssh.ServerConfig) { conn, chans, reqs, err := ssh.NewServerConn(nConn, cfg) if err != nil { return } defer conn.Close() go ssh.DiscardRequests(reqs) for nc := range chans { if nc.ChannelType() != "session" { nc.Reject(ssh.UnknownChannelType, "only session") continue } ch, requests, err := nc.Accept() if err != nil { return } go session(ch, requests) } } func session(ch ssh.Channel, requests <-chan *ssh.Request) { for req := range requests { if req.Type != "exec" { if req.WantReply { req.Reply(false, nil) } continue } var payload struct{ Command string } ssh.Unmarshal(req.Payload, &payload) if req.WantReply { req.Reply(true, nil) } code := run(payload.Command, ch) ch.SendRequest("exit-status", false, ssh.Marshal(struct{ Status uint32 }{uint32(code)})) ch.Close() return } } func run(cmd string, ch ssh.Channel) int { c := exec.Command("/bin/sh", "-c", cmd) c.Stdout = ch c.Stderr = ch.Stderr() in, err := c.StdinPipe() if err != nil { return 1 } if err := c.Start(); err != nil { return 1 } go func() { io.Copy(in, ch) in.Close() }() if err := c.Wait(); err != nil { if ee, ok := err.(*exec.ExitError); ok { return ee.ExitCode() } return 1 } return 0 }