Files
mesh-controller/internal/inventory/agent_account_test.go
T
jochen c30b79dd2a Name the account agents run as on a node, and say whether it can become root
On the control node every agent ran as the operator's account, which has
passwordless sudo, so an agent could become root without a person (hq ADR
0266). A node now names an agent account at the controller's terminal only;
the agent's module declares it never to become root, the node-engine judges
that, and the self-check (DA) raises agent-can-become-root while it does not
hold, so ADR 0259's router can rest on it.
2026-10-08 21:46:10 +02:00

77 lines
2.6 KiB
Go

package inventory
import (
"context"
"errors"
"strings"
"testing"
)
// The agent account (novox/hq ADR 0266): recorded and read back with every node, its home derived when
// not stated, cleared by an empty name — and refused when it is root, the operator's own account, or no
// login at all, because each of those would say agents have an account of their own while they do not.
func TestAgentAccountIsRecordedAndRefusedWhereItWouldNotConfine(t *testing.T) {
inv := ForTest(t)
ctx := context.Background()
if _, err := inv.AddNode(ctx, "anchor"); err != nil {
t.Fatal(err)
}
if err := inv.SetAccount(ctx, "anchor", "operator", ""); err != nil {
t.Fatal(err)
}
n, err := inv.NodeByName(ctx, "anchor")
if err != nil {
t.Fatal(err)
}
if n.AgentAccount != "" || n.AgentHome() != "" {
t.Fatalf("a node that names none has agent account %q, home %q", n.AgentAccount, n.AgentHome())
}
if err := inv.SetAgentAccount(ctx, "anchor", "agent", ""); err != nil {
t.Fatal(err)
}
n, _ = inv.NodeByName(ctx, "anchor")
if n.AgentAccount != "agent" || n.AgentHome() != "/home/agent" {
t.Fatalf("agent account %q, home %q; want agent, /home/agent", n.AgentAccount, n.AgentHome())
}
all, err := inv.Nodes(ctx)
if err != nil || len(all) != 1 || all[0].AgentAccount != "agent" {
t.Fatalf("the listing does not carry the agent account: %+v %v", all, err)
}
if err := inv.SetAgentAccount(ctx, "anchor", "agent", "/srv/agent"); err != nil {
t.Fatal(err)
}
if n, _ = inv.NodeByName(ctx, "anchor"); n.AgentHome() != "/srv/agent" {
t.Fatalf("the stated home is %q", n.AgentHome())
}
for _, c := range []struct{ account, home, says string }{
{"root", "", "may not run as root"},
{"operator", "", "operator account"},
{"Agent", "", "not a login name"},
{"9agent", "", "not a login name"},
{"agent", "relative", "absolute"},
{"", "/home/x", "without an agent account"},
} {
err := inv.SetAgentAccount(ctx, "anchor", c.account, c.home)
if err == nil || !strings.Contains(err.Error(), c.says) {
t.Errorf("%q %q: %v; want a refusal saying %q", c.account, c.home, err, c.says)
}
}
if n, _ = inv.NodeByName(ctx, "anchor"); n.AgentAccount != "agent" {
t.Fatalf("a refusal changed the record: %q", n.AgentAccount)
}
if err := inv.SetAgentAccount(ctx, "anchor", "", ""); err != nil {
t.Fatal(err)
}
if n, _ = inv.NodeByName(ctx, "anchor"); n.AgentAccount != "" || n.AgentAccountHome != "" {
t.Fatalf("clearing left %q %q", n.AgentAccount, n.AgentAccountHome)
}
if err := inv.SetAgentAccount(ctx, "nowhere", "agent", ""); !errors.Is(err, ErrNoSuchNode) {
t.Fatalf("an unknown node: %v", err)
}
}