94 lines
3.3 KiB
Go
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
|
|
}
|