96 comments across the two repos named records that no longer exist. Each now points at the consolidated record that holds its reasoning -- ADR 0034 (a test defends a decision) is 0017, the eight host records are 0005, the four lab records are 0016. Worth noting for next time: these are references from outside HQ, so renumbering there is not free. It cost 38 files here.
207 lines
7.4 KiB
Go
207 lines
7.4 KiB
Go
package profile
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
// Named capabilities. Constants rather than strings at the call site, because a capability
|
|
// nothing declares is a capability nothing can require, and a typo would produce exactly that.
|
|
const (
|
|
CapContainerRuntime = "container-runtime"
|
|
CapPackageManager = "package-manager"
|
|
CapServiceManager = "service-manager"
|
|
CapFirewall = "firewall"
|
|
CapOverlay = "overlay"
|
|
CapGraphicalSession = "graphical-session"
|
|
CapPrivileged = "privileged"
|
|
)
|
|
|
|
// commandCapability is the shape most detectors take: run something, and treat a working
|
|
// invocation as evidence.
|
|
//
|
|
// It runs a command that only succeeds if the thing is FUNCTIONING, never `--version` alone.
|
|
// A version string proves a binary is on disk, which is the assumption 04-ISSUES/007 records
|
|
// as false: the package was installed and the daemon was not running.
|
|
type commandCapability struct {
|
|
name string
|
|
command string
|
|
args []string
|
|
// why describes what a success actually proves, and is reported as the detector's `How`.
|
|
why string
|
|
// interpret decides the verdict from what the command said and how it exited.
|
|
//
|
|
// Exists because "exit zero" is not a universal answer. A degraded service manager reports
|
|
// its state on stdout and exits non-zero — it is running, and reading only the exit code
|
|
// declared no service manager on a machine whose init it was. That is 04-ISSUES/007 in the
|
|
// mirror: 007 is installed-but-broken reported present; this is working-but-imperfect
|
|
// reported absent. Both place work wrongly, and this one was only visible by running
|
|
// against a real machine.
|
|
//
|
|
// nil means the ordinary rule: success is exit zero.
|
|
interpret func(stdout string, err error) (present bool, detail string)
|
|
runner Runner
|
|
}
|
|
|
|
func (c commandCapability) Name() string { return c.name }
|
|
|
|
func (c commandCapability) Detect(ctx context.Context) Verdict {
|
|
out, err := c.runner(ctx, c.command, c.args...)
|
|
|
|
interpret := c.interpret
|
|
if interpret == nil {
|
|
interpret = exitZero
|
|
}
|
|
present, detail := interpret(out, err)
|
|
|
|
if strings.TrimSpace(detail) == "" {
|
|
// A verdict with no reason is the fault in a new place: something nobody can act on.
|
|
// Reached when a command fails silently, which systemctl does.
|
|
if present {
|
|
detail = "responded"
|
|
} else {
|
|
detail = fmt.Sprintf("%s gave no reason", c.command)
|
|
}
|
|
}
|
|
return Verdict{Name: c.name, Present: present, Detail: firstLine(detail), How: c.why}
|
|
}
|
|
|
|
// exitZero is the ordinary rule: the command worked, so the capability is there.
|
|
func exitZero(stdout string, err error) (bool, string) {
|
|
if err != nil {
|
|
return false, err.Error()
|
|
}
|
|
return true, stdout
|
|
}
|
|
|
|
// systemRunning reads what an init system says about itself rather than how it exited.
|
|
//
|
|
// `is-system-running` exits non-zero for every state except `running` — including `degraded`,
|
|
// which means units failed and the init is emphatically present. Treating that as absent made
|
|
// a machine running systemd report no service manager.
|
|
func systemRunning(stdout string, err error) (bool, string) {
|
|
state := strings.TrimSpace(firstLine(stdout))
|
|
switch state {
|
|
case "running", "degraded", "starting", "maintenance", "stopping":
|
|
return true, state
|
|
case "":
|
|
if err != nil {
|
|
return false, err.Error()
|
|
}
|
|
return false, "said nothing"
|
|
default:
|
|
// `offline` and `unknown` mean it is not managing this machine.
|
|
return false, state
|
|
}
|
|
}
|
|
|
|
func firstLine(s string) string {
|
|
s = strings.TrimSpace(s)
|
|
if i := strings.IndexByte(s, '\n'); i >= 0 {
|
|
s = s[:i]
|
|
}
|
|
if len(s) > 200 {
|
|
s = s[:200] + "…"
|
|
}
|
|
return s
|
|
}
|
|
|
|
// privileged reports whether the host can change this machine at all.
|
|
//
|
|
// Reported as a capability rather than checked at startup on purpose: a host that cannot act
|
|
// is still a host that can report, and novox/hq ADR 0004 says what varies between nodes lives
|
|
// here rather than in the definition of a node.
|
|
type privileged struct{}
|
|
|
|
func (privileged) Name() string { return CapPrivileged }
|
|
|
|
func (privileged) Detect(context.Context) Verdict {
|
|
uid := os.Geteuid()
|
|
if uid == 0 {
|
|
return Verdict{
|
|
Name: CapPrivileged, Present: true,
|
|
Detail: "effective uid 0",
|
|
How: "effective uid — the host changes a machine, which needs root",
|
|
}
|
|
}
|
|
return Verdict{
|
|
Name: CapPrivileged, Present: false,
|
|
Detail: fmt.Sprintf("effective uid %d, not 0", uid),
|
|
How: "effective uid — the host changes a machine, which needs root",
|
|
}
|
|
}
|
|
|
|
// graphicalSession reports whether anything could display a window here.
|
|
//
|
|
// Environment rather than a probe, because a display server is reachable through a socket a
|
|
// detector would have to guess at, and the variables are what an application would use anyway.
|
|
// Stated so the limit is visible: this detects that a session is ADVERTISED, which is weaker
|
|
// than the other detectors here.
|
|
type graphicalSession struct{}
|
|
|
|
func (graphicalSession) Name() string { return CapGraphicalSession }
|
|
|
|
func (graphicalSession) Detect(context.Context) Verdict {
|
|
const how = "DISPLAY / WAYLAND_DISPLAY — weaker than the other checks: advertised, not probed"
|
|
if d := os.Getenv("WAYLAND_DISPLAY"); d != "" {
|
|
return Verdict{Name: CapGraphicalSession, Present: true, Detail: "wayland: " + d, How: how}
|
|
}
|
|
if d := os.Getenv("DISPLAY"); d != "" {
|
|
return Verdict{Name: CapGraphicalSession, Present: true, Detail: "x11: " + d, How: how}
|
|
}
|
|
return Verdict{
|
|
Name: CapGraphicalSession, Present: false,
|
|
Detail: "neither DISPLAY nor WAYLAND_DISPLAY is set",
|
|
How: how,
|
|
}
|
|
}
|
|
|
|
// Default returns the detectors the host runs when nobody says otherwise.
|
|
//
|
|
// Each command is chosen to prove the thing WORKS rather than exists:
|
|
// - the container runtime is asked for server-side information, which fails when the daemon
|
|
// is down even though the client is installed — the exact shape of 04-ISSUES/007;
|
|
// - the service manager is asked whether it is the running init, not whether it is present;
|
|
// - the firewall is asked to list a ruleset, which needs both the tool and the permission.
|
|
func Default(runner Runner) []Detector {
|
|
if runner == nil {
|
|
runner = ExecRunner
|
|
}
|
|
return []Detector{
|
|
privileged{},
|
|
graphicalSession{},
|
|
commandCapability{
|
|
name: CapContainerRuntime, command: "docker", args: []string{"info", "--format", "{{.ServerVersion}}"},
|
|
why: "asks the daemon for its version — a running daemon, not an installed client",
|
|
runner: runner,
|
|
},
|
|
commandCapability{
|
|
name: CapPackageManager, command: "pacman", args: []string{"-Q", "pacman"},
|
|
why: "queries the package database — a working database, not a binary on disk",
|
|
runner: runner,
|
|
},
|
|
commandCapability{
|
|
name: CapServiceManager, command: "systemctl", args: []string{"is-system-running"},
|
|
why: "reads the init's own account of its state — degraded is still running",
|
|
interpret: systemRunning,
|
|
runner: runner,
|
|
},
|
|
commandCapability{
|
|
name: CapFirewall, command: "nft", args: []string{"list", "ruleset"},
|
|
why: "lists the ruleset — needs the tool AND the privilege to use it",
|
|
runner: runner,
|
|
},
|
|
commandCapability{
|
|
name: CapOverlay, command: "wg", args: []string{"show", "interfaces"},
|
|
why: "asks the kernel for interfaces — needs the module, not just the tool",
|
|
runner: runner,
|
|
},
|
|
}
|
|
}
|
|
|
|
// isRoot is the same question `privileged` answers, exposed for tests that must check the
|
|
// detector against something other than itself.
|
|
func isRoot() bool { return os.Geteuid() == 0 }
|