Files
privatae-bastion/client-go/coder_socket_peer_linux.go
T
Privatae e4c9b69b46
ci / personal-data (push) Successful in 5s
ci / test (push) Successful in 3m4s
ci / fuzz (push) Successful in 3m45s
bastion 0.53.2
2026-10-20 04:42:14 +00:00

94 lines
3.3 KiB
Go

//go:build linux
package main
import (
"context"
"crypto/tls"
"net"
"os"
"syscall"
"time"
)
// coderLaneProbeTimeout bounds the lane handshake one run socket connection
// waits on. The lane door is on this machine and answers in milliseconds.
const coderLaneProbeTimeout = 3 * time.Second
// coderPeerRefusal is the run socket's second wall: WHO connected. A run
// socket is served to the group every Host on this machine is in, so the
// pairing check alone (coderSocketRefusal) would let Host B reach Host A's run
// on a machine paired with both. The process that connects must have the uid
// of the process serving the pairing's lane, read on every connection because
// the Host re-creates its lane socket when it restarts:
//
// - the kernel's record of who connected (SO_PEERCRED, servicePeerUID) must
// be the owner of the lane socket (Lstat, never followed), which refuses
// another uid at once;
// - and the process listening on the lane socket must prove it is the
// pairing's Host: the lanes' own mutual TLS handshake (the Host's pinned
// CA, the pairing's host name) must complete, and the kernel's record of
// who listens there must be that same uid.
//
// WHY the handshake. The shared directory is group-writable (1770), so while
// Host A is down or restarting and its lane socket is gone, Host B can bind a
// socket of its own at that path. Its owner is then B, and B would pass the
// first check; it cannot pass the handshake, which needs A's lane key.
//
// The uid is the whole of what this tells apart: two Hosts that run as the
// same uid are the same process here (each one's own bind-mounted shared
// directory is what keeps them apart). No lane registered refuses: there is
// nothing to compare against.
func coderPeerRefusal(self string, c net.Conn) string {
coderSockMu.Lock()
lane, ok := coderSockLanes[self]
coderSockMu.Unlock()
if !ok || lane.path == "" || lane.dial == nil || lane.tls == nil {
return "no lane socket is known for this pairing"
}
fi, err := os.Lstat(lane.path)
if err != nil || fi.Mode()&os.ModeSocket == 0 {
return "the pairing's lane socket is not there"
}
st, ok := fi.Sys().(*syscall.Stat_t)
if !ok {
return "the pairing's lane socket's owner could not be read"
}
uid, err := servicePeerUID(c)
if err != nil {
return "who connected could not be read"
}
if uid != st.Uid {
return "the process that connected is not the one serving this pairing's lane"
}
served, err := coderLaneServerUID(lane)
if err != nil {
return "the pairing's lane socket is not served by the pairing's Host"
}
if served != uid {
return "the process that connected is not the one serving this pairing's lane"
}
return ""
}
// coderLaneServerUID dials the pairing's lane socket the way its lanes do,
// completes the mutual TLS handshake (so the listener holds the pairing's
// Host's lane key), and returns the uid the kernel recorded for that listener.
func coderLaneServerUID(lane coderLane) (uint32, error) {
ctx, cancel := context.WithTimeout(context.Background(), coderLaneProbeTimeout)
defer cancel()
raw, err := lane.dial(ctx)
if err != nil {
return 0, err
}
defer raw.Close()
uid, err := servicePeerUID(raw)
if err != nil {
return 0, err
}
if err := tls.Client(raw, lane.tls.Clone()).HandshakeContext(ctx); err != nil {
return 0, err
}
return uid, nil
}