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
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:
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user