Refuse a converged genesis on a machine in use, naming every container and listener counted (hq ADR 0100)
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/novox/mesh-host/internal/reachable"
|
||||
"github.com/novox/mesh-host/internal/store"
|
||||
)
|
||||
|
||||
// quietUDP are processes whose UDP sockets every fresh machine has — name resolution, address
|
||||
// configuration, time — and which serve nobody. ss names a process by its first fifteen characters,
|
||||
// so both spellings are here.
|
||||
//
|
||||
// **Still to be measured** (novox/hq ADR 0100): this list is what a fresh machine is expected to
|
||||
// hold, and it must be checked against a freshly installed lab machine before it is trusted.
|
||||
var quietUDP = map[string]bool{
|
||||
"systemd-resolved": true, "systemd-resolve": true,
|
||||
"systemd-networkd": true, "systemd-network": true,
|
||||
"systemd-timesyncd": true, "systemd-timesyn": true,
|
||||
"dhcpcd": true,
|
||||
}
|
||||
|
||||
// InUse says what makes this machine a machine in use (novox/hq ADR 0100): every running container
|
||||
// no host made, and every socket listening on an address other than loopback that is not ssh's — a
|
||||
// UDP one only when it is held by something other than what every fresh machine runs. ours names
|
||||
// what the mesh itself runs, which a re-run of genesis finds and does not count.
|
||||
func InUse(ctx context.Context, run Runner, ours func(name string) bool) ([]string, []reachable.Reach, error) {
|
||||
var containers []string
|
||||
out, err := run(ctx, "docker", "ps", "--format", "{{.Names}}\t{{.Label \"mesh-host.spec\"}}")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot ask the container runtime what is running here: %w", err)
|
||||
}
|
||||
for _, line := range strings.Split(out, "\n") {
|
||||
name, label, _ := strings.Cut(strings.TrimSpace(line), "\t")
|
||||
label = strings.TrimSpace(label)
|
||||
if name == "" || (label != "" && label != "<no value>") || ours(name) {
|
||||
continue
|
||||
}
|
||||
containers = append(containers, name)
|
||||
}
|
||||
|
||||
listening, err := run(ctx, "ss", "-Hltunp")
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot read what listens on this machine: %w", err)
|
||||
}
|
||||
var listeners []reachable.Reach
|
||||
for _, r := range reachable.Sockets(listening) {
|
||||
if counts(r) && !ours(r.By) {
|
||||
listeners = append(listeners, r)
|
||||
}
|
||||
}
|
||||
return containers, listeners, nil
|
||||
}
|
||||
|
||||
func counts(r reachable.Reach) bool {
|
||||
if ip := net.ParseIP(r.Address); ip != nil && ip.IsLoopback() {
|
||||
return false
|
||||
}
|
||||
switch r.Protocol {
|
||||
case "tcp":
|
||||
return r.By != "sshd" && !(r.By == "" && r.Port == 22)
|
||||
case "udp":
|
||||
return !quietUDP[r.By]
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RefuseAMachineInUse is the check a converged genesis makes before changing anything: a machine
|
||||
// in use is refused, naming every container and listener counted, because raising the foundation's
|
||||
// filter there would close what it serves — a forgotten --adopted must not close a working machine.
|
||||
// An adopted genesis is told what it found, and goes on.
|
||||
func RefuseAMachineInUse(ctx context.Context, o Options, run Runner, say func(string)) error {
|
||||
known, err := store.Load(o.State)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(known.Resources) > 0 {
|
||||
// What genesis raised on an earlier run is the mesh's, and it is what the machine now
|
||||
// serves; the question was answered the first time.
|
||||
say(" in use not asked: this machine carries what an earlier genesis raised")
|
||||
return nil
|
||||
}
|
||||
containers, listeners, err := InUse(ctx, run, func(string) bool { return false })
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(containers) == 0 && len(listeners) == 0 {
|
||||
say(" in use no: no container runs and nothing listens beyond ssh")
|
||||
return nil
|
||||
}
|
||||
var named []string
|
||||
for _, c := range containers {
|
||||
named = append(named, "container "+c)
|
||||
}
|
||||
for _, l := range listeners {
|
||||
by := l.By
|
||||
if by == "" {
|
||||
by = "an unnamed process"
|
||||
}
|
||||
named = append(named, fmt.Sprintf("%s %s:%d by %s", l.Protocol, l.Address, l.Port, by))
|
||||
}
|
||||
if o.Adopted {
|
||||
say(fmt.Sprintf(" in use yes, and adopted: %d thing(s) found are kept", len(named)))
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("this machine is in use, and a converged genesis would close what it serves:\n - %s\n"+
|
||||
"If it is meant to join the mesh keeping what it runs, pass --adopted: its firewall stays in "+
|
||||
"force and every module is taken on it one at a time. Nothing was changed",
|
||||
strings.Join(named, "\n - "))
|
||||
}
|
||||
Reference in New Issue
Block a user