Refuse a placement or mount at the machine's own trees, 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 fail: its merge-check.sh failed: --- FAIL: TestBelowAHomeOnlyThatAccountsDirectoryIsPlaced (0.00s)
mesh/delivery superseded: a newer head of the same pull request
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 fail: its merge-check.sh failed: --- FAIL: TestBelowAHomeOnlyThatAccountsDirectoryIsPlaced (0.00s)
mesh/delivery superseded: a newer head of the same pull request
A module's places setting could point a directory anywhere, with an owner it named, and the engine chowned and chmodded whatever it was pointed at as root, through a link too; its accesses setting could mount any path into a container (hq issue 339). The engine now refuses, as a failed resource with its reason, a directory, access or mount source that is or holds one of the machine's roots, anything placed in the kernel's, the boot loader's, root's or the engine's own trees, a non-root directory below /etc, /usr or /run, an access there, another account's directory below a home, and a container mounting anything it refused, judged with every link resolved. A directory whose last component is a link is refused, and one found here that the mesh did not make keeps its owner and mode and is never removed.
This commit is contained in:
+72
-3
@@ -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 issue 339).
|
||||
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
|
||||
@@ -460,8 +462,17 @@ func ApplyMindingWindows(
|
||||
// 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}
|
||||
// Where nothing is placed or mounted from, judged before anything is applied (novox/hq issue 339), and
|
||||
// which declared directories were already here: one this apply makes is the mesh's, one found may not be.
|
||||
in.refused = refusedPlaces(d.Resources)
|
||||
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 +736,7 @@ func ApplyMindingWindows(
|
||||
Linger: outcome.linger,
|
||||
Groups: outcome.groups,
|
||||
Unpacked: outcome.unpacked,
|
||||
AsFound: outcome.asFound,
|
||||
Holds: holds(resource),
|
||||
})
|
||||
if outcome.found != nil {
|
||||
@@ -918,9 +930,21 @@ type Unseal func(sealed string) ([]byte, error)
|
||||
func applyOne(ctx context.Context, sys system.System, r declaration.Resource, run Runner,
|
||||
changed map[string]bool, in inputs, previous store.Applied,
|
||||
unseal Unseal, keepFound Keep) (Outcome, error) {
|
||||
// Nothing is placed in or mounted from the machine's own trees, whoever asked (novox/hq issue 339): a
|
||||
// refusal is this resource failing, with its reason, in the node's report.
|
||||
switch res := r.(type) {
|
||||
case *declaration.Directory, *declaration.Access:
|
||||
if err := in.refused[filepath.Clean(r.Target())]; err != nil {
|
||||
return begin(r), err
|
||||
}
|
||||
case *declaration.Container:
|
||||
if err := refuseMounts(res, in.refused); 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:
|
||||
@@ -965,21 +989,56 @@ 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.
|
||||
//
|
||||
// **The path is not followed** (novox/hq issue 339): a directory whose last component is a link is refused,
|
||||
// because a chmod and a chown through it would change wherever the link points — /etc, as root.
|
||||
//
|
||||
// **A directory found here, that the mesh did not make, is used as found** (novox/hq issue 339): 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 at this path before (a record from before
|
||||
// this rule counts, which is every directory on a running machine), when this apply made it, or when it
|
||||
// already has the declared owner and mode, so a person who sets them by hand at the machine hands it over.
|
||||
func applyDirectory(r *declaration.Directory, previous store.Applied, wasBefore bool) (Outcome, error) {
|
||||
out := begin(r)
|
||||
mode, err := modeOf(r.Mode, 0o755)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
before, err := os.Stat(r.Path)
|
||||
before, err := os.Lstat(r.Path)
|
||||
existed := err == nil
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return out, err
|
||||
}
|
||||
if existed && before.Mode()&os.ModeSymlink != 0 {
|
||||
return out, &PlacementRefusedError{Path: r.Path, Why: "it is a symbolic link, and the node-engine " +
|
||||
"follows no link to a directory it owns"}
|
||||
}
|
||||
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 issue 339); 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 {
|
||||
@@ -1623,6 +1682,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 issue 339).
|
||||
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
|
||||
@@ -1850,6 +1913,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
|
||||
// refused is every directory and access the engine will not place, by path, with why (novox/hq issue 339):
|
||||
// each fails, and a container mounting one fails with it.
|
||||
refused map[string]error
|
||||
// dirsBefore is every declared directory that was on the machine when this apply began (novox/hq issue
|
||||
// 339): 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.
|
||||
|
||||
Reference in New Issue
Block a user