Write a filled file on the provisioner client's request, and nothing else
The provisioner's client (ADR 0349 §4) fills a module's templates and needs the node-engine to write them: it must not hold the right to write the machine's files itself. A second local socket accepts a write only from the account the declaration names as the provisioner client's (SO_PEERCRED, same namespaces), only for a clean path the declaration names as filled, under the apply's own locks, and writes it through the apply as the declared file would be, firing restart-on, reload-on and container reads. Issue 668. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
package declaration
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Defends novox/hq ADR 0349 §4 (issue 668): the declaration names the account the provisioner's client runs as,
|
||||
// the only one whose write of a filled file the node-engine accepts. Read here before any controller sends it.
|
||||
|
||||
func TestTheDeclarationNamesTheProvisionersAccount(t *testing.T) {
|
||||
d, err := Parse([]byte(`{"declaration":1,"provisioner_account":"mesh-node-provisioner","filled_files":["/etc/a"],
|
||||
"resources":[{"id":"a","type":"file","path":"/etc/a","content":"a","owner-at-runtime":"owned"}]}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if d.Provisioner != "mesh-node-provisioner" {
|
||||
t.Errorf("the provisioner's account read as %q", d.Provisioner)
|
||||
}
|
||||
d, err = Parse([]byte(`{"declaration":1,"resources":[{"id":"a","type":"file","path":"/etc/a","content":"a"}]}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if d.Provisioner != "" {
|
||||
t.Errorf("a declaration naming no account names %q", d.Provisioner)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAProvisionersAccountThatIsNoAccountIsRefused(t *testing.T) {
|
||||
for name, account := range map[string]string{"root": "root", "not a name": "a b", "a path": "../x"} {
|
||||
_, err := Parse([]byte(`{"declaration":1,"provisioner_account":"` + account + `",
|
||||
"resources":[{"id":"a","type":"file","path":"/etc/a","content":"a"}]}`))
|
||||
if err == nil || !strings.Contains(err.Error(), "provisioner_account") {
|
||||
t.Errorf("%s: want a refusal naming provisioner_account, got %v", name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package meshcli
|
||||
|
||||
import "net"
|
||||
|
||||
// The kernel's word on who is at the other end of a socket, shared with the node-engine's other local socket, the
|
||||
// write of a filled file (internal/filling, novox/hq ADR 0349 §4): one reading of it, so the two never judge a peer
|
||||
// differently.
|
||||
|
||||
// PeerCredentials is the user id and process id of the process at the other end, as the kernel recorded them when
|
||||
// it connected (SO_PEERCRED). Nothing the process says is read.
|
||||
func PeerCredentials(conn *net.UnixConn) (uint32, int32, error) { return peerCredentials(conn) }
|
||||
|
||||
// SameNamespaces refuses a process whose mount or user namespace is not the engine's: a user id is this machine's
|
||||
// account only in this machine's namespaces.
|
||||
func SameNamespaces(pid int32) error { return namespacesOf(pid) }
|
||||
|
||||
// NotOwnDirectory says what is wrong with a socket's directory, or nothing: it must be a directory, not a link,
|
||||
// owned by the account the engine runs as.
|
||||
func NotOwnDirectory(dir string) string { return notOwnDirectory(dir) }
|
||||
Reference in New Issue
Block a user