mounts: take an SMB source's username as a setting and its password as its own secret, and wait for them
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
The one INI secret smb-credentials could not be given through the desk prompt, which takes one hidden line, and a missing login was said as a fault: the first-node gate put the build back and the operator was told nothing was theirs to do. The username is now the setting smb-users and each password the one-line secret smb-password-<source>; a source missing either answers waits through mesh-sdk's health answer (hq ADR 0283, issue 386, issue 379).
This commit is contained in:
@@ -19,7 +19,9 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net"
|
||||
"os"
|
||||
"os/user"
|
||||
@@ -28,6 +30,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/health"
|
||||
)
|
||||
|
||||
// LookEvery is how often the process looks.
|
||||
@@ -48,6 +52,9 @@ const (
|
||||
StateAbsent = "absent" // an occasional source that is not there: a state, not a fault
|
||||
StateUnreachable = "unreachable" // should be there and is not
|
||||
StateFailed = "failed" // tried and refused, or not set up; why says which
|
||||
// StateWaiting is a source that cannot be set up until the operator gives its username or password
|
||||
// (novox/hq ADR 0283): not a fault, said with what it waits for.
|
||||
StateWaiting = "waiting"
|
||||
)
|
||||
|
||||
// ExportsAnswer is what the bundle left from the sharing machine's `node-nfs-server.exports`.
|
||||
@@ -74,6 +81,8 @@ type ShareStatus struct {
|
||||
Why string `json:"why,omitempty"`
|
||||
MountUnit string `json:"mount_unit,omitempty"`
|
||||
AutoUnit string `json:"automount_unit,omitempty"`
|
||||
// Waits is what a waiting source waits for: the setting smb-users, or its secret smb-password-<source>.
|
||||
Waits []health.Wait `json:"waits,omitempty"`
|
||||
}
|
||||
|
||||
// Status is what one look found.
|
||||
@@ -272,23 +281,6 @@ func (a *Applier) Look(ctx context.Context) Status {
|
||||
}
|
||||
}
|
||||
|
||||
var creds map[string]SMBCredential
|
||||
needCreds := false
|
||||
for _, s := range cfg.Sources {
|
||||
needCreds = needCreds || s.Kind == "smb"
|
||||
}
|
||||
if needCreds {
|
||||
secret, err := a.M.read(cfg.Secret)
|
||||
if cfg.Secret == "" || err != nil {
|
||||
st.Problems = append(st.Problems, "the module's secret smb-credentials is not on this machine")
|
||||
} else {
|
||||
var p []string
|
||||
creds, p = ParseSMBCredentials(secret)
|
||||
for _, x := range p {
|
||||
st.Problems = append(st.Problems, "smb-credentials: "+x)
|
||||
}
|
||||
}
|
||||
}
|
||||
uid, gid := -1, -1
|
||||
if cfg.Account != "" && a.Lookup != nil {
|
||||
if u, g, err := a.Lookup(cfg.Account); err == nil {
|
||||
@@ -300,11 +292,22 @@ func (a *Applier) Look(ctx context.Context) Status {
|
||||
case "disk":
|
||||
pairs = append(pairs, DiskPair(s))
|
||||
case "smb":
|
||||
c, ok := creds[s.Name]
|
||||
if !ok || cfg.State == "" {
|
||||
c, waits, why := a.login(cfg, s.Name)
|
||||
switch {
|
||||
case why != "":
|
||||
notSet[s.Name] = ShareStatus{Name: s.Name, Kind: "smb", Occasional: true, Mountpoint: s.Mountpoint, State: StateFailed, Why: why}
|
||||
continue
|
||||
case len(waits) > 0:
|
||||
var gives []string
|
||||
for _, w := range waits {
|
||||
gives = append(gives, w.What+" ("+w.Names()+")")
|
||||
}
|
||||
notSet[s.Name] = ShareStatus{Name: s.Name, Kind: "smb", Occasional: true, Mountpoint: s.Mountpoint, State: StateWaiting,
|
||||
Why: "it waits for the operator to give " + strings.Join(gives, " and "), Waits: waits}
|
||||
continue
|
||||
case cfg.State == "":
|
||||
notSet[s.Name] = ShareStatus{Name: s.Name, Kind: "smb", Occasional: true, Mountpoint: s.Mountpoint, State: StateFailed,
|
||||
Why: fmt.Sprintf("its login is not given: at the desk, `secret ask <node> mounts smb-credentials` and type one line `[%s] username=… password=…`; "+
|
||||
"or put a [%s] section in a file and `secret accept <node> mounts smb-credentials --from <file>`", s.Name, s.Name)}
|
||||
Why: "the module's state directory is not named in its configuration, so no credentials file can be written"}
|
||||
continue
|
||||
}
|
||||
file := filepath.Join(cfg.State, "smb-"+s.Name+".credentials")
|
||||
@@ -701,3 +704,44 @@ func orInterval(d, otherwise time.Duration) time.Duration {
|
||||
}
|
||||
return otherwise
|
||||
}
|
||||
|
||||
// login is an SMB source's login, or what it waits for, or why it cannot be read. The password is read as
|
||||
// one line from the source's own secret; it is never said, and neither is anything the file holds.
|
||||
func (a *Applier) login(cfg Config, source string) (SMBCredential, []health.Wait, string) {
|
||||
var waits []health.Wait
|
||||
part := "the source " + source
|
||||
user, hasUser := cfg.SMBUsers[source]
|
||||
if !hasUser {
|
||||
waits = append(waits, health.Wait{Part: part, Setting: "smb-users", What: "the username of the source " + source})
|
||||
}
|
||||
var password string
|
||||
raw, err := a.M.read(PasswordFile(cfg.Passwords, source))
|
||||
switch {
|
||||
case cfg.Passwords == "" || 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)
|
||||
default:
|
||||
pw, given, perr := ReadPassword(raw)
|
||||
if perr != nil {
|
||||
return SMBCredential{}, nil, "its password " + PasswordSecret(source) + " " + perr.Error() + ": give it again"
|
||||
}
|
||||
if !given {
|
||||
waits = append(waits, health.Wait{Part: part, Secret: PasswordSecret(source), What: "the password of the source " + source})
|
||||
}
|
||||
password = pw
|
||||
}
|
||||
if len(waits) > 0 {
|
||||
return SMBCredential{}, waits, ""
|
||||
}
|
||||
return SMBCredential{Username: user, Password: password}, nil, ""
|
||||
}
|
||||
|
||||
// bare is an error without the path it names: the reason alone.
|
||||
func bare(err error) string {
|
||||
var pe *fs.PathError
|
||||
if errors.As(err, &pe) {
|
||||
return pe.Err.Error()
|
||||
}
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user