Refuse a placement at the machine's own directories, and use a found directory as found
mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request

A module's places setting can move a directory anywhere, and the engine
chowned whatever it was pointed at as root: a directory at /etc owned by the
agent account would hand it /etc (hq ADR 0266). Refuse the machine's roots,
the kernel's and the engine's trees, another account's home, and an archive
or written-into file below an agent's home; and leave the owner and mode of a
directory the mesh did not make.
This commit is contained in:
jochen
2026-10-08 21:50:23 +02:00
parent c74cf16b75
commit b1cb9542cc
4 changed files with 417 additions and 3 deletions
+54 -3
View File
@@ -75,6 +75,8 @@ type Outcome struct {
groups []string
// unpacked is, for an archive, what it put on the machine (novox/hq issue 162).
unpacked *store.Unpacked
// asFound is, for a directory, that it is used as it was found (novox/hq ADR 0266).
asFound bool
// reads is, for a container, the digest of each file it was created reading, by path — so
// the next apply can say which one changed (novox/hq 04-ISSUES/103).
reads map[string]string
@@ -459,9 +461,15 @@ func ApplyMindingWindows(
// And one patience with the artifact store for the whole apply (novox/hq issue 291): a fetch it
// does not answer during a maintenance window waits for the window instead of failing.
in := inputs{declares: map[string]string{}, known: &known, windows: windows, away: newStoreAway(windows, log),
groups: wanted}
groups: wanted, agentHomes: rootNeverHomes(d)}
in.dirsBefore = map[string]bool{}
for _, resource := range d.Resources {
in.declares[resource.Identity()] = declaredDigest(resource)
if dir, ok := resource.(*declaration.Directory); ok {
if _, err := os.Lstat(dir.Path); err == nil {
in.dirsBefore[filepath.Clean(dir.Path)] = true
}
}
}
// Everything is attempted, and every failure is reported.
@@ -725,6 +733,7 @@ func ApplyMindingWindows(
Linger: outcome.linger,
Groups: outcome.groups,
Unpacked: outcome.unpacked,
AsFound: outcome.asFound,
Holds: holds(resource),
})
if outcome.found != nil {
@@ -919,15 +928,19 @@ func applyOne(ctx context.Context, sys system.System, r declaration.Resource, ru
changed map[string]bool, in inputs, previous store.Applied,
unseal Unseal, keepFound Keep) (Outcome, error) {
// Nothing below a home is touched through a link an account put there (novox/hq ADR 0266).
// And nothing is placed where no module places anything, whoever asked (placement_guard.go).
switch r.(type) {
case *declaration.Directory, *declaration.File, *declaration.Archive:
if err := refusePlacement(r, in.agentHomes); err != nil {
return begin(r), err
}
if err := refuseLinksUnderHome(r.Target()); err != nil {
return begin(r), err
}
}
switch res := r.(type) {
case *declaration.Directory:
return applyDirectory(res)
return applyDirectory(res, previous, in.dirsBefore[filepath.Clean(res.Path)])
case *declaration.File:
return applyFile(res, previous, unseal, keepFound)
case *declaration.Service:
@@ -972,7 +985,15 @@ func modeOf(spec string, fallback os.FileMode) (os.FileMode, error) {
return os.FileMode(parsed), nil
}
func applyDirectory(r *declaration.Directory) (Outcome, error) {
// applyDirectory makes a directory what was declared.
//
// **A directory found here, that the mesh did not make, is used as found** (novox/hq ADR 0266): its owner and
// mode are left, and the outcome says what was declared and what was found. Changing them would be root
// handing a directory it never made — wherever a declaration pointed it — to whatever account was named. A
// directory is the mesh's when its record says the mesh applied it before (a record from before this rule
// counts, which is every directory on a running machine), or when it already has the declared owner and mode,
// so a person who sets them by hand at the machine hands it to the mesh.
func applyDirectory(r *declaration.Directory, previous store.Applied, wasBefore bool) (Outcome, error) {
out := begin(r)
mode, err := modeOf(r.Mode, 0o755)
if err != nil {
@@ -987,6 +1008,26 @@ func applyDirectory(r *declaration.Directory) (Outcome, error) {
if existed && !before.IsDir() {
return out, fmt.Errorf("%s exists and is not a directory", r.Path)
}
if existed && wasBefore {
ours := previous.Target != "" && filepath.Clean(previous.Target) == filepath.Clean(r.Path) && !previous.AsFound
if !ours {
owned, err := ownedBy(r.Path, r.Owner)
if err != nil {
return out, err
}
if !owned || before.Mode().Perm() != mode.Perm() {
out.Action, out.asFound = "unchanged", true
owner := r.Owner
if owner == "" {
owner = "root"
}
out.Detail = fmt.Sprintf("found here before the mesh and used as found: its owner and mode %o are "+
"left, though %s and %o were declared (novox/hq ADR 0266); set them by hand to hand it to the mesh",
before.Mode().Perm(), owner, mode.Perm())
return out, nil
}
}
}
if !existed {
if err := makeDirs(r.Path, mode, r.Owner); err != nil {
@@ -1647,6 +1688,10 @@ func remove(ctx context.Context, sys system.System, a store.Applied, run Runner,
// This is the host's own line, applied to the one shape where getting it wrong is not
// recoverable: it removes what it made and leaves what it merely configured. An empty
// directory is what it made. A full one is not.
if a.AsFound {
// Found here before the mesh, and never the mesh's (novox/hq ADR 0266).
return "forgotten", "found here before the mesh and used as found: left as it is", nil
}
entries, err := os.ReadDir(a.Target)
if errors.Is(err, os.ErrNotExist) {
return "forgotten", "no longer there", nil
@@ -1874,6 +1919,12 @@ type inputs struct {
// groups is every group the declaration asks an account to be in, and by which resources
// (novox/hq ADR 0252): a group one user resource stops asking for stays while another asks.
groups Wanted
// agentHomes is the home of every account the declaration says never becomes root (novox/hq ADR
// 0266): nothing is unpacked or written into below one (placement_guard.go).
agentHomes []string
// dirsBefore is every declared directory that was on the machine when this apply began (novox/hq ADR
// 0266): one the apply itself made — a file's parent — is the mesh's, one that was there may not be.
dirsBefore map[string]bool
}
// fileDigest is what a file the container reads holds, by digest.