v0.326.0: FileBrowser's default password is checked at every FileBrowser start (R-977)
gates / gates (push) Successful in 49s
gates / gates (push) Successful in 49s
The probe ran once per box; a re-created FileBrowser data volume came back on admin/admin and was never probed again (measured on scratch 9202 with 0.324.0). Now once per FileBrowser run (its StartedAt): the default found -> a generated password, verified and stored encrypted; any other password -> nothing changes. TestFileBrowserAdmin_R977_DefaultBackOnANewRunIsReplaced, red-proved. The comment "before a claim there is no tunnel" was false and is corrected; the tunnel is not changed. Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
@@ -30,11 +30,22 @@ import (
|
||||
// THE DECISION (one mechanism for fresh and existing boxes): the API path, never the config key. The
|
||||
// key would silently undo the password the operator set by hand on the HP and the N100 (2026-09-15)
|
||||
// and any a household sets later. Cost: on a brand-new box admin/admin works from FileBrowser's first
|
||||
// start until the next base-stack tick sets the password (seconds; before a claim there is no tunnel).
|
||||
// start until the next base-stack tick sets the password (seconds). CORRECTED 2026-10-17 (R-977): this used to add
|
||||
// „before a claim there is no tunnel" — false: EnsureBaseStack starts cloudflared from the token with no claim check,
|
||||
// and the claim itself is reached through the tunnel (the claim mail links https://felhom.<domain>). The window is
|
||||
// seconds, and it is now re-checked at EVERY FileBrowser start (below), not only once per box.
|
||||
//
|
||||
// The probe: login admin/admin → 200 ⇒ generate (password:16), PUT, verify new=200 AND admin=401, then
|
||||
// record "generated" with the encrypted value; 401 ⇒ record "operator" (somebody set it — leave it).
|
||||
// Anything else (container starting, network) ⇒ record nothing and try again next tick.
|
||||
//
|
||||
// R-977 (v0.326.0): the decision used to be made ONCE per box — any recorded state returned at once — so a FileBrowser
|
||||
// whose data volume was re-created (a lost volume, a rebuild with settings restored) came back with admin/admin and was
|
||||
// never probed again: measured on scratch 9202 2026-10-17 (state „generated", default login 200 after the volume was
|
||||
// re-created by the box). Now the probe runs once per FileBrowser RUN (its container's StartedAt): admin/admin
|
||||
// refused → nothing changes (a recorded state stays — the household's own password is never touched); admin/admin
|
||||
// accepted → a generated password is set, exactly as on a new box, whatever was recorded before. Pinned by
|
||||
// TestFileBrowserAdmin_R977_DefaultBackOnANewRunIsReplaced.
|
||||
|
||||
const fileBrowserBaseURL = "http://filebrowser:80"
|
||||
|
||||
@@ -65,14 +76,32 @@ func fbLogin(do fbHTTPDo, base, password string) (string, int, error) {
|
||||
return strings.Trim(strings.TrimSpace(string(b)), `"`), resp.StatusCode, nil
|
||||
}
|
||||
|
||||
// EnsureFileBrowserAdminPassword makes the one-time decision. Safe to call every tick: it returns at
|
||||
// once when a decision is recorded. Returns an error only for logging.
|
||||
// fbStartedAt identifies FileBrowser's current run (its container's .State.StartedAt); "" when unknown. Tests set
|
||||
// fbStarted; with a test FileBrowser (fbHTTP / fbBaseURL) and no fbStarted it is "" — a test never reaches docker.
|
||||
func (m *Manager) fbStartedAt() string {
|
||||
if m.fbStarted != nil {
|
||||
return m.fbStarted()
|
||||
}
|
||||
if m.fbHTTP != nil || m.fbBaseURL != "" {
|
||||
return ""
|
||||
}
|
||||
out, err := dockerexec.Command("docker", "inspect", "--format", "{{.State.StartedAt}}", fileBrowserContainer).Output()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(string(out))
|
||||
}
|
||||
|
||||
// EnsureFileBrowserAdminPassword probes FileBrowser's default login once per FileBrowser run (R-977) and replaces it
|
||||
// when it works. Safe to call every tick: a run already probed returns at once. Returns an error only for logging.
|
||||
func (m *Manager) EnsureFileBrowserAdminPassword() error {
|
||||
if m.settings == nil || len(m.encKey) == 0 {
|
||||
return nil
|
||||
}
|
||||
if state, _, _ := m.settings.GetFileBrowserAdmin(); state != "" {
|
||||
return nil
|
||||
state, _, _ := m.settings.GetFileBrowserAdmin()
|
||||
run := m.fbStartedAt()
|
||||
if state != "" && (run == "" || run == m.fbProbedRun) {
|
||||
return nil // decided, and this FileBrowser run was probed already (or its run cannot be told)
|
||||
}
|
||||
do := m.fbDo()
|
||||
base := fileBrowserBaseURL
|
||||
@@ -87,8 +116,15 @@ func (m *Manager) EnsureFileBrowserAdminPassword() error {
|
||||
}
|
||||
switch code {
|
||||
case http.StatusOK:
|
||||
// default login still works — set a generated password below
|
||||
// default login works — set a generated password below
|
||||
if state != "" {
|
||||
m.logger.Printf("[WARN] [infra] filebrowser: admin/admin logs in again on a new FileBrowser run (recorded state %q) — its data was re-created; replacing it (R-977)", state)
|
||||
}
|
||||
case http.StatusUnauthorized, http.StatusForbidden:
|
||||
m.fbProbedRun = run
|
||||
if state != "" {
|
||||
return nil // this run is not on the default: the recorded decision stands
|
||||
}
|
||||
m.logger.Printf("[INFO] [infra] filebrowser: admin/admin is refused (HTTP %d) — the password was set by someone; leaving it and recording \"operator\"", code)
|
||||
return m.settings.SetFileBrowserAdmin(settings.FileBrowserAdminOperator, "", now)
|
||||
default:
|
||||
@@ -138,6 +174,7 @@ func (m *Manager) EnsureFileBrowserAdminPassword() error {
|
||||
m.logger.Printf("[ERROR] [infra] filebrowser: password CHANGED but settings save FAILED: %v — the generated password is lost; reset it with the filebrowser CLI", err)
|
||||
return err
|
||||
}
|
||||
m.fbProbedRun = run
|
||||
m.logger.Printf("[INFO] [infra] filebrowser: admin/admin replaced by a generated password (value never logged); verified new=200 admin=401")
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user