bastion 0.53.2
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
//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
|
||||
}
|
||||
Reference in New Issue
Block a user