Bind the ssh server to the newt ip

This commit is contained in:
Owen
2026-05-21 20:30:17 -07:00
parent 388795ecf4
commit 9640ada8b7
3 changed files with 74 additions and 24 deletions
+44
View File
@@ -19,6 +19,7 @@ import (
newtDevice "github.com/fosrl/newt/device"
"github.com/fosrl/newt/holepunch"
"github.com/fosrl/newt/logger"
"github.com/fosrl/newt/nativessh"
"github.com/fosrl/newt/netstack2"
"github.com/fosrl/newt/network"
"github.com/fosrl/newt/util"
@@ -108,6 +109,8 @@ type WireGuardService struct {
sharedBind *bind.SharedBind
holePunchManager *holepunch.Manager
useNativeInterface bool
// SSH server running on the clients' netstack
sshServer *sshServerHandle
// Direct UDP relay from main tunnel to clients' WireGuard
directRelayStop chan struct{}
directRelayWg sync.WaitGroup
@@ -211,6 +214,12 @@ func (s *WireGuardService) Close() {
s.stopGetConfig = nil
}
// Stop SSH server before tearing down the netstack
if s.sshServer != nil {
s.sshServer.stop()
s.sshServer = nil
}
// Flush access logs before tearing down the tunnel
if s.tnet != nil {
if ph := s.tnet.GetProxyHandler(); ph != nil {
@@ -890,6 +899,13 @@ func (s *WireGuardService) ensureWireguardInterface(wgconfig WgConfig) error {
logger.Error("Failed to start WireGuard tester server: %v", err)
}
// Start the SSH server on the clients' netstack (port 22).
if h, sshErr := startSSHOnNetstack(s.tnet); sshErr != nil {
logger.Warn("nativessh: not starting SSH server on clients netstack: %v", sshErr)
} else {
s.sshServer = h
}
// Note: we already unlocked above, so don't use defer unlock
return nil
}
@@ -1563,3 +1579,31 @@ func (s *WireGuardService) filterReadOnlyFields(config string) string {
return strings.Join(filteredLines, "\n")
}
// sshServerHandle holds the listener so the SSH server can be stopped by
// closing it.
type sshServerHandle struct {
ln net.Listener
}
func (h *sshServerHandle) stop() {
_ = h.ln.Close()
}
// startSSHOnNetstack creates a TCP listener on port 22 of the clients' netstack
// and starts serving SSH connections on it in the background. The returned
// handle can be used to stop the server by closing the listener.
func startSSHOnNetstack(tnet *netstack2.Net) (*sshServerHandle, error) {
srv := nativessh.NewServer(nativessh.ServerConfig{})
ln, err := tnet.ListenTCP(&net.TCPAddr{Port: 22})
if err != nil {
return nil, fmt.Errorf("listen on netstack port 22: %w", err)
}
h := &sshServerHandle{ln: ln}
go func() {
if err := srv.Serve(ln); err != nil {
logger.Debug("nativessh: clients netstack server stopped: %v", err)
}
}()
return h, nil
}
-8
View File
@@ -26,7 +26,6 @@ import (
"github.com/fosrl/newt/docker"
"github.com/fosrl/newt/healthcheck"
"github.com/fosrl/newt/logger"
"github.com/fosrl/newt/nativessh"
"github.com/fosrl/newt/proxy"
"github.com/fosrl/newt/updates"
"github.com/fosrl/newt/util"
@@ -536,13 +535,6 @@ func runNewtMain(ctx context.Context) {
}
}
// Start native SSH server for testing (listens on :2222).
go func() {
srv := nativessh.NewServer(nativessh.ServerConfig{})
if err := srv.ListenAndServe(); err != nil {
logger.Error("Native SSH server error: %v", err)
}
}()
logger.GetLogger().SetLevel(loggerLevel)
// Initialize telemetry after flags are parsed (so flags override env)
+30 -16
View File
@@ -58,42 +58,56 @@ func NewServer(cfg ServerConfig) *Server {
return &Server{cfg: cfg}
}
// ListenAndServe starts the SSH server and blocks until the listener is closed.
func (s *Server) ListenAndServe() error {
// buildSSHConfig loads keys/principals and builds the ssh.ServerConfig.
func (s *Server) buildSSHConfig() (*ssh.ServerConfig, error) {
caKey, err := loadCAPublicKey(s.cfg.CAKeyPath)
if err != nil {
return fmt.Errorf("load CA public key from %s: %w", s.cfg.CAKeyPath, err)
return nil, fmt.Errorf("load CA public key from %s: %w", s.cfg.CAKeyPath, err)
}
principals, err := loadPrincipals(s.cfg.PrincipalsPath)
if err != nil {
return fmt.Errorf("load principals from %s: %w", s.cfg.PrincipalsPath, err)
return nil, fmt.Errorf("load principals from %s: %w", s.cfg.PrincipalsPath, err)
}
hostSigner, err := generateHostKey()
if err != nil {
return fmt.Errorf("host key: %w", err)
return nil, fmt.Errorf("host key: %w", err)
}
sshCfg := &ssh.ServerConfig{
cfg := &ssh.ServerConfig{
PublicKeyCallback: makeCertAuthCallback(caKey, principals),
}
sshCfg.AddHostKey(hostSigner)
cfg.AddHostKey(hostSigner)
return cfg, nil
}
// Serve accepts connections on ln and handles them. It returns when ln is
// closed or a non-temporary Accept error occurs.
func (s *Server) Serve(ln net.Listener) error {
sshCfg, err := s.buildSSHConfig()
if err != nil {
return err
}
log.Printf("nativessh: server listening on %s", ln.Addr())
for {
conn, err := ln.Accept()
if err != nil {
return err
}
go s.handleConn(conn, sshCfg)
}
}
// ListenAndServe starts the SSH server on the host network and blocks until
// the listener is closed.
func (s *Server) ListenAndServe() error {
ln, err := net.Listen("tcp", s.cfg.ListenAddr)
if err != nil {
return fmt.Errorf("listen %s: %w", s.cfg.ListenAddr, err)
}
defer ln.Close()
log.Printf("nativessh: server listening on %s", s.cfg.ListenAddr)
for {
conn, err := ln.Accept()
if err != nil {
return fmt.Errorf("accept: %w", err)
}
go s.handleConn(conn, sshCfg)
}
return s.Serve(ln)
}
func (s *Server) handleConn(conn net.Conn, cfg *ssh.ServerConfig) {