Files
egress-proxies-operator/internal/provider/kvm2/sshserver_test.go
T
kacerrandClaude Fable 5.1 b45f83327a kvm2: SSH transport, ctx-first libvirt seam, host table and span decorator
Every go-libvirt call runs under its own budget and a cancelled or overlong
call kills the connection; credentials are read on every dial; one span and
one log line per RPC come from a decorator applied where connections are
made.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 17:43:27 +00:00

290 lines
6.5 KiB
Go

package kvm2
import (
"bytes"
"crypto/ed25519"
"crypto/rand"
"encoding/pem"
"errors"
"io"
"net"
"os"
"path/filepath"
"sync"
"testing"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/knownhosts"
)
// testSSHServer is an in-process x/crypto/ssh server that accepts one
// authorised public key and forwards direct-streamlocal@openssh.com channels
// for socketPath to that unix socket, rejecting every other channel.
type testSSHServer struct {
addr string
hostKey ssh.PublicKey
socketPath string
listener net.Listener
mu sync.Mutex
conns []net.Conn
wg sync.WaitGroup
stopOnce sync.Once
reqMu sync.Mutex
requested []string
}
func startSSHServer(t *testing.T, authorized ssh.PublicKey, socketPath string) *testSSHServer {
t.Helper()
_, hostPriv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("generating host key: %v", err)
}
hostSigner, err := ssh.NewSignerFromKey(hostPriv)
if err != nil {
t.Fatalf("host signer: %v", err)
}
cfg := &ssh.ServerConfig{
PublicKeyCallback: func(_ ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
if bytes.Equal(key.Marshal(), authorized.Marshal()) {
return &ssh.Permissions{}, nil
}
return nil, errors.New("key not authorised")
},
}
cfg.AddHostKey(hostSigner)
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen: %v", err)
}
s := &testSSHServer{addr: ln.Addr().String(), hostKey: hostSigner.PublicKey(), socketPath: socketPath, listener: ln}
s.wg.Add(1)
go s.serve(cfg)
t.Cleanup(s.stop)
return s
}
func (s *testSSHServer) serve(cfg *ssh.ServerConfig) {
defer s.wg.Done()
for {
conn, err := s.listener.Accept()
if err != nil {
return
}
s.mu.Lock()
s.conns = append(s.conns, conn)
s.mu.Unlock()
s.wg.Add(1)
go s.handle(conn, cfg)
}
}
func (s *testSSHServer) handle(conn net.Conn, cfg *ssh.ServerConfig) {
defer s.wg.Done()
sc, chans, reqs, err := ssh.NewServerConn(conn, cfg)
if err != nil {
_ = conn.Close()
return
}
defer sc.Close()
go ssh.DiscardRequests(reqs)
for nc := range chans {
if nc.ChannelType() != "direct-streamlocal@openssh.com" {
_ = nc.Reject(ssh.UnknownChannelType, "only direct-streamlocal@openssh.com is served")
continue
}
var open struct {
SocketPath string
Reserved0 string
Reserved1 uint32
}
if err := ssh.Unmarshal(nc.ExtraData(), &open); err != nil {
_ = nc.Reject(ssh.ConnectionFailed, "bad direct-streamlocal payload")
continue
}
s.reqMu.Lock()
s.requested = append(s.requested, open.SocketPath)
s.reqMu.Unlock()
if open.SocketPath != s.socketPath {
_ = nc.Reject(ssh.ConnectionFailed, "no such socket")
continue
}
target, err := net.Dial("unix", s.socketPath)
if err != nil {
_ = nc.Reject(ssh.ConnectionFailed, "socket unavailable")
continue
}
ch, chReqs, err := nc.Accept()
if err != nil {
_ = target.Close()
continue
}
go ssh.DiscardRequests(chReqs)
s.wg.Add(1)
go func() {
defer s.wg.Done()
proxy(ch, target)
}()
}
}
func proxy(ch ssh.Channel, target net.Conn) {
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
_, _ = io.Copy(target, ch)
_ = target.Close()
}()
go func() {
defer wg.Done()
_, _ = io.Copy(ch, target)
_ = ch.Close()
}()
wg.Wait()
}
func (s *testSSHServer) stop() {
s.stopOnce.Do(func() {
_ = s.listener.Close()
s.mu.Lock()
for _, c := range s.conns {
_ = c.Close()
}
s.mu.Unlock()
s.wg.Wait()
})
}
func (s *testSSHServer) requestedPaths() []string {
s.reqMu.Lock()
defer s.reqMu.Unlock()
return append([]string(nil), s.requested...)
}
// startEchoSocket listens on a unix socket that echoes what it reads. The
// path is kept short: unix socket paths are limited to about 100 bytes and
// t.TempDir() names are long.
func startEchoSocket(t *testing.T) string {
t.Helper()
dir, err := os.MkdirTemp("", "kvm2")
if err != nil {
t.Fatalf("temp dir: %v", err)
}
t.Cleanup(func() { _ = os.RemoveAll(dir) })
path := filepath.Join(dir, "libvirt-sock")
ln, err := net.Listen("unix", path)
if err != nil {
t.Fatalf("listen unix: %v", err)
}
t.Cleanup(func() { _ = ln.Close() })
go func() {
for {
c, err := ln.Accept()
if err != nil {
return
}
go func() {
_, _ = io.Copy(c, c)
_ = c.Close()
}()
}
}()
return path
}
// silentListener accepts TCP connections and never speaks, for handshake
// timeouts. It returns the address.
func silentListener(t *testing.T) string {
t.Helper()
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen: %v", err)
}
var conns []net.Conn
var mu sync.Mutex
go func() {
for {
c, err := ln.Accept()
if err != nil {
return
}
mu.Lock()
conns = append(conns, c)
mu.Unlock()
}
}()
t.Cleanup(func() {
_ = ln.Close()
mu.Lock()
defer mu.Unlock()
for _, c := range conns {
_ = c.Close()
}
})
return ln.Addr().String()
}
// closedPort returns an address nothing listens on.
func closedPort(t *testing.T) string {
t.Helper()
ln, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen: %v", err)
}
addr := ln.Addr().String()
_ = ln.Close()
return addr
}
// writeKeyFile generates an ed25519 key, writes it in OpenSSH format and
// returns the path and the public half.
func writeKeyFile(t *testing.T, dir, name string) (string, ssh.PublicKey) {
t.Helper()
pub, priv, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("generating key: %v", err)
}
block, err := ssh.MarshalPrivateKey(priv, "kvm2 test")
if err != nil {
t.Fatalf("marshalling key: %v", err)
}
path := writeFile(t, dir, name, string(pem.EncodeToMemory(block)))
sshPub, err := ssh.NewPublicKey(pub)
if err != nil {
t.Fatalf("public key: %v", err)
}
return path, sshPub
}
// writeKnownHosts writes one known_hosts line for addr and key.
func writeKnownHosts(t *testing.T, dir, name, addr string, key ssh.PublicKey) string {
t.Helper()
return writeFile(t, dir, name, knownhosts.Line([]string{addr}, key)+"\n")
}
func writeFile(t *testing.T, dir, name, content string) string {
t.Helper()
path := filepath.Join(dir, name)
if err := os.WriteFile(path, []byte(content), 0o600); err != nil {
t.Fatalf("writing %s: %v", path, err)
}
return path
}
// otherPublicKey returns a public key unrelated to any file, for mismatch
// cases.
func otherPublicKey(t *testing.T) ssh.PublicKey {
t.Helper()
pub, _, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
t.Fatalf("generating key: %v", err)
}
sshPub, err := ssh.NewPublicKey(pub)
if err != nil {
t.Fatalf("public key: %v", err)
}
return sshPub
}