Merge main (hq ADR 0266) into the mesh-cli answer, and close what the confirmation review found
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery covered: a later merge that contains it was delivered: novox/mesh-controller@14ab2ddd9b49 (merged as 63e85b25 into main, walk plan-17915459…
mesh/merge-gate pass: builds build-agent, mesh-controller, route-proxy → ace, g14, novox, shanks; no bus step; every machine composes with the change as it…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery covered: a later merge that contains it was delivered: novox/mesh-controller@14ab2ddd9b49 (merged as 63e85b25 into main, walk plan-17915459…
- The generic command verb only reads now (commandReads) and terminal-only commands are refused through any verb (terminalOnly). mesh-cli's ordinary line made neither check: `node account`, `token issue` and `secret export` from another node would have run. It now meets both, in the one function the command verb shares. - The serving controller marks itself and its children never the terminal (ADR 0266); a line mesh-cli runs as the terminal drops that mark and carries MESH_CLI_TERMINAL, so it reads as the terminal it is. - Two withholding tests searched the answer's text while JSON writes bytes as base64, so they held nothing. They search both now, each proved by disabling what it guards (Shown, the bus withholding, `calls` via Get). - The control-node refusal is tested through the assign and unassign acts.
This commit is contained in:
@@ -2,6 +2,7 @@ package link
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -203,13 +204,13 @@ func TestAMeshCLIRecordKeepsNeitherItsLineNorItsAnswerOnTheBus(t *testing.T) {
|
||||
asked, _ := json.Marshal(CLIAsked{Line: []string{"settings", "set", "x", `{"password":"s3cret"}`}, Account: "op"})
|
||||
l.serveCall(CLISeat, "laptop", asked, "_INBOX.node.laptop.abcdefghijklmnopqrstuv",
|
||||
func(context.Context, json.RawMessage) (any, error) {
|
||||
return CLIAnswer{Stdout: []byte("token s3cret-join")}, nil
|
||||
return CLIAnswer{Stdout: []byte("s3cret-join")}, nil
|
||||
},
|
||||
a.respond, nil)
|
||||
_ = a.only()
|
||||
close(writes)
|
||||
for c := range writes {
|
||||
if strings.Contains(string(c.Args), "s3cret") || strings.Contains(string(c.Answer), "s3cret") {
|
||||
if carries(c.Args, "s3cret") || carries(c.Answer, "s3cret-join") {
|
||||
t.Fatalf("the bus was sent %s / %s", c.Args, c.Answer)
|
||||
}
|
||||
if !strings.Contains(string(c.Args), "settings") || !strings.Contains(string(c.Args), `"op"`) {
|
||||
@@ -234,7 +235,7 @@ func TestCallsNeverShowsAMeshCLILinesAnswer(t *testing.T) {
|
||||
if err != nil || !found {
|
||||
t.Fatalf("the line is not shown at all: %v %v", found, err)
|
||||
}
|
||||
if strings.Contains(string(shown.Answer), "s3cret-join") {
|
||||
if carries(shown.Answer, "s3cret-join") {
|
||||
t.Fatalf("calls shows a mesh-cli line's answer: %s", shown.Answer)
|
||||
}
|
||||
b := newAnswers(t)
|
||||
@@ -246,3 +247,19 @@ func TestCallsNeverShowsAMeshCLILinesAnswer(t *testing.T) {
|
||||
t.Fatalf("another call's answer is withheld: %s", shown.Answer)
|
||||
}
|
||||
}
|
||||
|
||||
// carries says whether a record holds a secret as text or as the base64 JSON writes bytes in: a line's output is
|
||||
// bytes, so a search for its text alone finds nothing whatever the record keeps (review of ADR 0272).
|
||||
func carries(body []byte, secret string) bool {
|
||||
return strings.Contains(string(body), secret) ||
|
||||
strings.Contains(string(body), base64.StdEncoding.EncodeToString([]byte(secret)))
|
||||
}
|
||||
|
||||
// The two tests above hold what they claim: each fails when the protection it names is taken away.
|
||||
func TestTheWithholdingTestsHoldSomething(t *testing.T) {
|
||||
// The answer as a mesh-cli line's record would carry it, were it kept: the search finds it.
|
||||
body, _ := json.Marshal(map[string]any{"result": CLIAnswer{Stdout: []byte("s3cret-join")}})
|
||||
if !carries(body, "s3cret-join") {
|
||||
t.Fatalf("a record carrying the answer is not found carrying it: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ package link
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"github.com/novox/mesh-host/rootsearch"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -420,6 +421,29 @@ const LivenessContract = 1
|
||||
// because an older one parses strictly and would refuse the whole declaration for it.
|
||||
const ReadinessContract = 2
|
||||
|
||||
// RootContract is the statement of an engine that also judges a user's declared `root` (novox/hq ADR 0266):
|
||||
// whether an account declared never to become root without a person can — uid 0, a group that grants root,
|
||||
// a sudo rule, a secret of the mesh it may read. Only to such an engine is the field sent: an older one
|
||||
// parses strictly and would refuse the whole declaration for it.
|
||||
const RootContract = 3
|
||||
|
||||
// ReasonRoot starts the reason of an account verdict that found a way to root (ADR 0266); the node-engine's
|
||||
// own words (mesh-host internal/accounts ReasonRoot).
|
||||
const ReasonRoot = "can become root without a person"
|
||||
|
||||
// ReasonRootPending starts the reason of an account verdict the node-engine cannot give yet because its search
|
||||
// for setuid programs, started when the engine started, has not finished: not judged yet, said as such, never a
|
||||
// pass. The node-engine's own words (mesh-host rootsearch.ReasonPending), read from it rather than copied.
|
||||
const ReasonRootPending = rootsearch.ReasonPending
|
||||
|
||||
// RootSearchBound is the longest the node-engine lets one search for setuid programs run (mesh-host
|
||||
// rootsearch.Bound): the one value both read.
|
||||
const RootSearchBound = rootsearch.Bound
|
||||
|
||||
// RootNever is the value of a user's `root`, and of a verdict's Root, that the account must never become
|
||||
// root without a person (ADR 0266).
|
||||
const RootNever = "never"
|
||||
|
||||
// Health is one statement of a machine's long-running resources (to-be 48 §4): in every report, as the
|
||||
// event HealthSubject between reports on each change, and again every minute while one is not healthy.
|
||||
// The node-engine's own (mesh-host internal/link Health); a test on each side holds the field names.
|
||||
@@ -550,6 +574,10 @@ type ResourceHealth struct {
|
||||
// manager, and the account itself for a resource of kind KindAccount (novox/hq ADR 0254). Empty from an
|
||||
// engine older than that, and for anything the machine's own manager or runtime runs.
|
||||
Account string `json:"account,omitempty"`
|
||||
// Root is "never" on a verdict of kind KindAccount whose account is declared never to become root
|
||||
// without a person (novox/hq ADR 0266): the engine judged that too, and a healthy verdict says it cannot.
|
||||
// Empty from an engine older than RootContract, and on every other verdict.
|
||||
Root string `json:"root,omitempty"`
|
||||
}
|
||||
|
||||
// HealthSaid is the health event's body: the machine and its statement. The machine is read from the
|
||||
|
||||
Reference in New Issue
Block a user