v0.324.0: the database password comes back with the data (R-1106, R-127b, R-926); restic 0.19.1 (R-1107); R-1108, R-1065, R-1017; dashboard hardening (R-983, R-976)
gates / gates (push) Successful in 44s

This commit is contained in:
2026-10-16 22:46:00 +02:00
parent 00a0d91976
commit f75b3aa778
212 changed files with 2414 additions and 398 deletions
+37 -4
View File
@@ -53,10 +53,11 @@ const (
)
type gateState struct {
once sync.Once
key []byte
mu sync.Mutex
used map[string]time.Time // token nonces already swapped, until they expire
once sync.Once
keyMu sync.RWMutex // guards key: rotateGateKey replaces it while requests read it
key []byte
mu sync.Mutex
used map[string]time.Time // token nonces already swapped, until they expire
}
// gateKey loads the gate's HMAC key from the data dir, or makes one. No data dir → a key for this run only.
@@ -82,9 +83,41 @@ func (s *Server) gateKey() []byte {
}
}
})
s.gate.keyMu.RLock()
defer s.gate.keyMu.RUnlock()
return s.gate.key
}
// rotateGateKey replaces the gate's key (R-976, v0.324.0). Every cookie and token the key signed stops opening
// anything at once: each setup-gate pass, each family app cookie, each sign-in token in flight. The household's
// browser passes again through its dashboard session. The new key is saved tmp+rename; when that fails the old file
// is REMOVED, so a restart makes a fresh key rather than bringing the old one — and every cookie it signed — back.
func (s *Server) rotateGateKey() {
s.gateKey()
k := make([]byte, 32)
_, _ = rand.Read(k)
s.gate.keyMu.Lock()
s.gate.key = k
s.gate.keyMu.Unlock()
if s.cfg == nil || s.cfg.Paths.DataDir == "" {
return
}
p := filepath.Join(s.cfg.Paths.DataDir, "setup-gate.key")
tmp := p + ".tmp"
err := os.WriteFile(tmp, []byte(hex.EncodeToString(k)), 0o600)
if err == nil {
err = os.Rename(tmp, p)
}
if err != nil {
_ = os.Remove(tmp)
if rerr := os.Remove(p); rerr != nil && !os.IsNotExist(rerr) {
s.logger.Printf("[ERROR] [web] gate key: the new key could not be saved (%v) and the old one could not be removed (%v) — a restart would accept passes signed before the password change", err, rerr)
return
}
s.logger.Printf("[WARN] [web] gate key: the new key could not be saved (%v) — the old one was removed, so a restart makes a fresh key", err)
}
}
func (s *Server) gateMAC(parts ...string) string {
m := hmac.New(sha256.New, s.gateKey())
m.Write([]byte(strings.Join(parts, "\x00")))