mounts: read a password only from the directory the configuration names, and say a domain goes in the username
mesh/merge-gate fail: builds mounts → shanks; no bus step; a manifest the change touches fails the module check: modules/mounts/module.json: this manifest …
mesh/repo-check fail: its merge-check.sh failed: files asking for a setting without saying whether it is trusted: 24
mesh/delivery superseded: a newer head of the same pull request

A configuration without the passwords directory would have read a path
relative to the process's working directory; it is now refused as a fault.
Review of mesh-catalog#190 (issue 386, ADR 0283).
This commit is contained in:
jochen
2026-10-10 21:14:03 +02:00
parent bbb3161ade
commit c620173966
3 changed files with 41 additions and 2 deletions
+2 -1
View File
@@ -69,7 +69,8 @@ All three are preferences whose default, `none`, names nothing (ADR 0262 allows
For example `games=smb://192.0.2.76/Share@/mnt/games`.
- `smb-users`: the username each SMB source logs in with, space-separated `name=username`, for example
`games=root`. A username is not a secret, so it is a setting (novox/hq ADR 0283). It holds no space,
comma or `=`.
comma or `=`. A device that needs a domain takes it in the username, as `DOMAIN\user` or `user@domain`:
the credentials file has no `domain=` line.
A mount point is an absolute, plainly written path, not a directory of the system's own (`/etc`, `/usr`,
`/run`, …). An entry that cannot be read is said, and only that entry is left out.
+6 -1
View File
@@ -714,10 +714,15 @@ func (a *Applier) login(cfg Config, source string) (SMBCredential, []health.Wait
if !hasUser {
waits = append(waits, health.Wait{Part: part, Setting: "smb-users", What: "the username of the source " + source})
}
// Never a relative path: without the directory the mesh places the passwords in, nothing is read.
if !filepath.IsAbs(cfg.Passwords) {
return SMBCredential{}, nil, "the module's configuration names no directory for its passwords, so " +
PasswordSecret(source) + " cannot be read"
}
var password string
raw, err := a.M.read(PasswordFile(cfg.Passwords, source))
switch {
case cfg.Passwords == "" || errors.Is(err, fs.ErrNotExist):
case errors.Is(err, fs.ErrNotExist):
waits = append(waits, health.Wait{Part: part, Secret: PasswordSecret(source), What: "the password of the source " + source})
case err != nil:
return SMBCredential{}, nil, "its password " + PasswordSecret(source) + " could not be read: " + bare(err)
+33
View File
@@ -681,3 +681,36 @@ func TestAWaitBesideAFaultIsUnhealthy(t *testing.T) {
t.Errorf("a wait alone: %+v", h)
}
}
// A password that exists and cannot be read, and a configuration naming no directory for the passwords, are
// refused as faults: said without the value and without the path, and nothing is read from a relative path.
func TestAPasswordThatCannotBeReadIsAFaultSaidWithoutValueOrPath(t *testing.T) {
// A directory where the file should be: read fails, and not as "not there" (as root a mode would not stop it).
f := newSystem(t)
f.write(ConfigFile, "sources=games=smb://192.0.2.76/Share@/mnt/games\nsmb-users=games=root\npasswords=/var/lib/mesh/mounts\nstate=/var/lib/mesh/mounts\n")
f.write("/var/lib/mesh/mounts/smb-password-games.secret/inside", testPassword)
st := f.applier(time.Now()).Look(context.Background())
if len(st.Shares) != 1 || st.Shares[0].State != StateFailed || !strings.Contains(st.Shares[0].Why, "could not be read") ||
strings.Contains(st.Shares[0].Why, "/var/lib") || strings.Contains(st.Shares[0].Why, testPassword) {
t.Fatalf("an unreadable password: %+v", st.Shares)
}
// No passwords= and no state=: the source is not set up, and no relative path is read.
for _, cfg := range []string{
"sources=games=smb://192.0.2.76/Share@/mnt/games\nsmb-users=games=root\n",
"sources=games=smb://192.0.2.76/Share@/mnt/games\nsmb-users=games=root\npasswords=relative/dir\n",
} {
g := newSystem(t)
g.write(ConfigFile, cfg)
g.write("smb-password-games.secret", testPassword)
g.write("relative/dir/smb-password-games.secret", testPassword)
st := g.applier(time.Now()).Look(context.Background())
if len(st.Shares) != 1 || st.Shares[0].State != StateFailed || !strings.Contains(st.Shares[0].Why, "names no directory for its passwords") ||
strings.Contains(st.Shares[0].Why, testPassword) || strings.Contains(st.Shares[0].Why, "relative") {
t.Errorf("%q: %+v", cfg, st.Shares)
}
if _, err := os.Stat(filepath.Join(g.root, "/var/lib/mesh/mounts/smb-games.credentials")); !os.IsNotExist(err) {
t.Errorf("%q: a credentials file was written", cfg)
}
}
}