Through the settings verb a caller could place a module's directory at /etc with an owner of its own and have root hand it over at the next send, or mount any of the machine's paths into a container (hq ADR 0266). Both keys are now the terminal's and never at the machine's own trees; a plans line that acts is refused wherever its subcommand stands; and a line break in a setting, which a file it is written into reads as a directive, is refused where it is kept and where it is composed.
158 lines
7.4 KiB
Go
158 lines
7.4 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// The account agents run as (novox/hq ADR 0266): a module names it as a machine fact — the agent account
|
|
// where the node names one, the operator's otherwise — and asks the node-engine to judge it never to become
|
|
// root only where it is the agents' own.
|
|
|
|
func TestTheAgentAccountFactFallsBackToTheOperatorAndIsNeverRootOnlyWhenItsOwn(t *testing.T) {
|
|
facts := machineFacts(Resolution{Node: "anchor", Account: "ops"}, nil, "")
|
|
if facts["agent-account"] != "ops" || facts["agent-home"] != "/home/ops" || facts["agent-root"] != "" {
|
|
t.Errorf("with no agent account named, agents run as the operator: %v", facts)
|
|
}
|
|
facts = machineFacts(Resolution{Node: "anchor", Account: "ops", AccountHome: "/srv/ops"}, nil, "")
|
|
if facts["agent-home"] != "/srv/ops" {
|
|
t.Errorf("the operator's stated home is the agent's home when they are one account: %v", facts)
|
|
}
|
|
facts = machineFacts(Resolution{Node: "anchor", Account: "ops", AgentAccount: "agent"}, nil, "")
|
|
if facts["agent-account"] != "agent" || facts["agent-home"] != "/home/agent" || facts["agent-root"] != RootNever {
|
|
t.Errorf("a named agent account is the agents', never root: %v", facts)
|
|
}
|
|
if facts["account"] != "ops" {
|
|
t.Errorf("the operator account is still the operator's: %v", facts)
|
|
}
|
|
facts = machineFacts(Resolution{Node: "anchor", AgentAccount: "agent", AgentAccountHome: "/var/lib/agent"}, nil, "")
|
|
if facts["agent-home"] != "/var/lib/agent" || facts["agent-root"] != RootNever {
|
|
t.Errorf("an agent account with a stated home on a machine with no operator: %v", facts)
|
|
}
|
|
if _, has := machineFacts(Resolution{Node: "anchor"}, nil, "")["agent-account"]; has {
|
|
t.Error("a machine with no account at all names an agent account")
|
|
}
|
|
}
|
|
|
|
// The agent's module, in the shape the catalogue's declares it: the account, never root where it is its
|
|
// own; its directory under that home, owned by it.
|
|
const agentModule = `{"module": "agent", "version": "1", "resources": [
|
|
{"id": "account", "type": "user", "name": "${machine:agent-account}", "root": "${machine:agent-root}"},
|
|
{"id": "home", "type": "directory", "path": "${machine:agent-home}/.agent", "mode": "0700",
|
|
"owner": "${machine:agent-account}"}
|
|
]}`
|
|
|
|
func TestTheAgentAccountIsDeclaredNeverRootOnlyToAnEngineThatJudgesIt(t *testing.T) {
|
|
m, err := ParseManifest([]byte(agentModule))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
compose := func(r Resolution, with Rendering) (user, home map[string]any) {
|
|
t.Helper()
|
|
r.Node, r.Modules = "anchor", []Manifest{m}
|
|
out, err := r.Declaration(with)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return fileNamed(out, "agent.account"), fileNamed(out, "agent.home")
|
|
}
|
|
|
|
user, home := compose(Resolution{Account: "ops", AgentAccount: "agent"}, Rendering{JudgesRoot: true})
|
|
if user["name"] != "agent" || user[RootField] != RootNever {
|
|
t.Errorf("an engine that judges root is sent the agent account never to become root: %v", user)
|
|
}
|
|
if home["path"] != "/home/agent/.agent" || home["owner"] != "agent" {
|
|
t.Errorf("the agent's directory is under its own home, its own: %v", home)
|
|
}
|
|
|
|
user, _ = compose(Resolution{Account: "ops", AgentAccount: "agent"}, Rendering{})
|
|
if _, sent := user[RootField]; sent || user["name"] != "agent" {
|
|
t.Errorf("an older engine, which parses strictly, is sent root: %v", user)
|
|
}
|
|
|
|
user, home = compose(Resolution{Account: "ops"}, Rendering{JudgesRoot: true})
|
|
if _, sent := user[RootField]; sent || user["name"] != "ops" {
|
|
t.Errorf("where agents run as the operator, root asserts nothing and is not sent: %v", user)
|
|
}
|
|
if home["path"] != "/home/ops/.agent" || home["owner"] != "ops" {
|
|
t.Errorf("with no agent account, the agent's directory is the operator's: %v", home)
|
|
}
|
|
}
|
|
|
|
func TestTheRuntimeIsToldTheAgentAccount(t *testing.T) {
|
|
with := Rendering{ArtifactStore: "anchor.internal:5101",
|
|
Needed: map[string]map[string]string{RuntimeModule: {"broker": "sealed-credential"}}}
|
|
envOf := func(r Resolution) map[string]string {
|
|
t.Helper()
|
|
r.Node, r.Modules = "anchor", []Manifest{aToolsModule(t, "nftables", "tools/index.js"), theRuntime(t)}
|
|
out, err := r.Declaration(with)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
process := fileNamed(out, RuntimeModule+"."+RuntimeProcessID())
|
|
if process == nil {
|
|
t.Fatal("no runtime process was composed")
|
|
}
|
|
return process["env"].(map[string]string)
|
|
}
|
|
env := envOf(Resolution{Account: "ops", AgentAccount: "agent"})
|
|
if env[RuntimeAgentAccount] != "agent" || env[RuntimeAgentHome] != "/home/agent" || env[RuntimeOperatorAccount] != "ops" {
|
|
t.Errorf("the runtime is not told whom agents run as: %v", env)
|
|
}
|
|
env = envOf(Resolution{Account: "ops"})
|
|
if env[RuntimeAgentAccount] != "ops" || env[RuntimeAgentHome] != "/home/ops" {
|
|
t.Errorf("with no agent account, agents run as the operator: %v", env)
|
|
}
|
|
env = envOf(Resolution{})
|
|
if _, set := env[RuntimeAgentAccount]; set {
|
|
t.Errorf("a machine with no account names an agent account: %v", env)
|
|
}
|
|
if problems := bundleEnvProblems("x", Artifact{Name: "b", Kind: ArtifactBundle, Loads: []string{"x"},
|
|
Env: map[string]string{RuntimeAgentAccount: "me"}}); len(problems) == 0 {
|
|
t.Error("a bundle may tell the runtime whom agents run as")
|
|
}
|
|
}
|
|
|
|
// No placement and no access at the machine's own system or the mesh's state, however it is spelled (novox/hq
|
|
// ADR 0266); a module's own place elsewhere is taken.
|
|
func TestAPlacementOrAnAccessAtTheMachinesOwnIsRefused(t *testing.T) {
|
|
m := Manifest{Module: "notes", Resources: []map[string]any{{"id": "data", "type": "directory"}},
|
|
Accesses: []Access{{ID: "media"}}}
|
|
for _, path := range []string{"/", "/etc", "/etc/sudoers.d", "/usr/bin", "/root", "/var/lib", "/home",
|
|
"/var/lib/mesh/x", "/var/lib/mesh-host", "/srv/../etc", "/proc/1", "/dev"} {
|
|
layers := []Layer{{From: "laptop", Values: map[string]any{PlacesSetting: map[string]any{"data": path}}}}
|
|
if _, err := Places(m, layers); err == nil {
|
|
t.Errorf("a place at %s was taken", path)
|
|
}
|
|
layers = []Layer{{From: "laptop", Values: map[string]any{AccessesSetting: map[string]any{"media": path}}}}
|
|
if _, err := AccessPlaces(m, layers); err == nil {
|
|
t.Errorf("an access at %s was taken", path)
|
|
}
|
|
}
|
|
for _, path := range []string{"/srv/notes", "/mnt/plex/data", "/storage/media", "/home/restic", "/var/lib/notes/data"} {
|
|
layers := []Layer{{From: "laptop", Values: map[string]any{PlacesSetting: map[string]any{"data": path}}}}
|
|
if got, err := Places(m, layers); err != nil || got["data"].Path != path {
|
|
t.Errorf("a place at %s: %v %v", path, got, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A line break or a NUL in any string of any setting is refused, at any depth; PEM blocks alone may hold lines.
|
|
func TestASettingHoldsOneLine(t *testing.T) {
|
|
m := Manifest{Module: "mailu"}
|
|
for _, v := range []any{"a\nDEBUG=1", "a\rb", "a\x00b", map[string]any{"k": []any{"ok", "x\ny"}},
|
|
map[string]any{"k\nx": "v"}} {
|
|
if err := JudgeSettings(m, []Layer{{From: "home", Values: map[string]any{"v": v}}}, false); err == nil ||
|
|
!strings.Contains(err.Error(), "line break") {
|
|
t.Errorf("%q: %v", v, err)
|
|
}
|
|
}
|
|
pem := "-----BEGIN CERTIFICATE-----\nMIIBeDCCAR2gAwIBAgIQ\n-----END CERTIFICATE-----\n"
|
|
if err := JudgeSettings(m, []Layer{{From: "home", Values: map[string]any{"root": pem}}}, false); err != nil {
|
|
t.Errorf("a PEM block: %v", err)
|
|
}
|
|
if err := JudgeSettings(m, []Layer{{From: "home", Values: map[string]any{"root": pem + "PATH=/tmp evil\n"}}}, false); err == nil {
|
|
t.Error("a PEM block with a line of something else after it was taken")
|
|
}
|
|
}
|