v0.63.0: B3+B2 fresh-install fixes — TokenStore reload-on-miss + guesthook snippets dir

B3: Lookup re-reads the append-only store once on a miss (cross-process
coherence with the one-shot provisioner; size short-circuit bounds the cost;
behind the TokenAuthority seam). B2: fenced mkdir -p /var/lib/vz/snippets
before the snippet install + the one narrow sudoers grant. Both red-proofed;
drill findings DRILL-day0-cleanroom-2026-07-03 B3/B2.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-03 15:48:24 +02:00
parent 84f3f7ddb1
commit f31a76f788
8 changed files with 254 additions and 8 deletions
+39
View File
@@ -36,6 +36,9 @@ type TokenStore struct {
f *os.File
byHash map[string]int // tokenHash(hex) -> vmid
byVMID map[int]string // vmid -> current tokenHash(hex)
loadedSize int64 // size of the file at the last (re)load — the append-only short-circuit
reloads int // count of reload-on-miss re-reads (test-visible bound, B3)
}
// tokenRecord is one durable line: VMID v gets token-hash h (last write per v wins).
@@ -71,6 +74,19 @@ func OpenTokenStore(path string) (*TokenStore, error) {
}
func (s *TokenStore) load() error {
return s.reloadLocked()
}
// reloadLocked rebuilds both indexes from the on-disk log. Caller holds the mutex (or is in
// single-threaded open). Because the file is append-only and apply is last-write-wins, a full
// re-read is idempotent — it can only converge the index to the file's current truth. A missing
// file loads as empty (the store file IS the authority). Used at open AND by Lookup's
// reload-on-miss (B3, DRILL-day0-cleanroom-2026-07-03): a token minted by ANOTHER process (the
// one-shot provisioner) after this daemon opened its store becomes visible without a restart.
func (s *TokenStore) reloadLocked() error {
clear(s.byHash)
clear(s.byVMID)
s.loadedSize = 0
b, err := os.ReadFile(s.path)
if errors.Is(err, fs.ErrNotExist) {
return nil
@@ -78,6 +94,7 @@ func (s *TokenStore) load() error {
if err != nil {
return err
}
s.loadedSize = int64(len(b))
for _, line := range bytes.Split(b, []byte("\n")) {
line = bytes.TrimSpace(line)
if len(line) == 0 {
@@ -129,12 +146,20 @@ func (s *TokenStore) Mint(vmid int) (string, error) {
return "", fmt.Errorf("localapi: token store sync: %w", err)
}
s.apply(tokenRecord{VMID: vmid, Hash: hash})
s.loadedSize += int64(len(rec)) // keep the append-only size short-circuit accurate
return token, nil
}
// Lookup resolves a presented bearer token to its guest VMID. It hashes the candidate and
// looks it up; the per-candidate comparison is constant-time to avoid a timing oracle on the
// stored hash. ok is false for an unknown/empty token.
//
// Reload-on-miss (B3): the store FILE is shared across processes — the one-shot provisioner
// (`--selftest=provision`) Mints into it while the long-lived daemon serves Lookup from an index
// built at open. On a miss, re-read the file ONCE and re-check, so a token minted after this
// process started authorizes without a daemon restart (the drill's fresh-install 401). The
// append-only log makes an unchanged file size proof of no new records, so a genuinely unknown
// token costs at most one stat once the index is current — never a reload loop.
func (s *TokenStore) Lookup(token string) (int, bool) {
if token == "" {
return 0, false
@@ -150,6 +175,20 @@ func (s *TokenStore) Lookup(token string) (int, bool) {
return vmid, true
}
}
// Miss: skip the re-read when the append-only log has not grown (nothing new to see).
// A stat error falls through to the reload, which handles a missing file as empty.
if st, err := os.Stat(s.path); err == nil && st.Size() == s.loadedSize {
return 0, false
}
s.reloads++
if err := s.reloadLocked(); err != nil {
return 0, false // unreadable store: fail closed, never crash the auth path
}
if vmid, ok := s.byHash[want]; ok {
if subtle.ConstantTimeCompare([]byte(want), []byte(s.byVMID[vmid])) == 1 {
return vmid, true
}
}
return 0, false
}