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:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package localapi
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -110,6 +111,128 @@ func TestTokenStore_SurvivesReopen(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// B3 Scenario A (DRILL-day0-cleanroom-2026-07-03): the provisioner is a SEPARATE one-shot process
|
||||
// that Mints into the SAME file the long-lived daemon serves Lookup from. A token minted after the
|
||||
// daemon built its index must authorize WITHOUT a restart — the reload-on-miss re-reads the file.
|
||||
// Pre-fix wrong outcome: (0,false) — the fresh-install 401 the drill hit on /controller/swap.
|
||||
func TestTokenStore_ReloadOnMiss_CrossProcessMint(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "tokens.log")
|
||||
daemon, err := OpenTokenStore(path) // index built now (empty file)
|
||||
if err != nil {
|
||||
t.Fatalf("open daemon store: %v", err)
|
||||
}
|
||||
defer daemon.Close()
|
||||
|
||||
minter, err := OpenTokenStore(path) // the --selftest=provision process
|
||||
if err != nil {
|
||||
t.Fatalf("open minter store: %v", err)
|
||||
}
|
||||
defer minter.Close()
|
||||
tok, err := minter.Mint(120)
|
||||
if err != nil {
|
||||
t.Fatalf("cross-process mint: %v", err)
|
||||
}
|
||||
|
||||
vmid, ok := daemon.Lookup(tok)
|
||||
if !ok || vmid != 120 {
|
||||
t.Fatalf("daemon.Lookup(token minted after daemon start) = (%d,%v), want (120,true) — the B3 fresh-install 401", vmid, ok)
|
||||
}
|
||||
}
|
||||
|
||||
// B3 Scenario B: a genuinely unknown token still returns (0,false), and the miss path re-reads the
|
||||
// file AT MOST once per Lookup — and not at all when the append-only log has not grown (the size
|
||||
// short-circuit). Guards against a reload loop / per-candidate re-read.
|
||||
func TestTokenStore_ReloadOnMiss_BoundedReloads(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "tokens.log")
|
||||
daemon, err := OpenTokenStore(path)
|
||||
if err != nil {
|
||||
t.Fatalf("open daemon store: %v", err)
|
||||
}
|
||||
defer daemon.Close()
|
||||
minter, err := OpenTokenStore(path)
|
||||
if err != nil {
|
||||
t.Fatalf("open minter store: %v", err)
|
||||
}
|
||||
defer minter.Close()
|
||||
if _, err := minter.Mint(5); err != nil { // grow the file behind the daemon's back
|
||||
t.Fatalf("mint: %v", err)
|
||||
}
|
||||
|
||||
base := daemon.reloads
|
||||
if vmid, ok := daemon.Lookup("garbage-never-minted"); ok {
|
||||
t.Fatalf("unknown token authorized: (%d,%v)", vmid, ok)
|
||||
}
|
||||
if got := daemon.reloads - base; got != 1 {
|
||||
t.Fatalf("first miss after an external append: %d reloads, want exactly 1", got)
|
||||
}
|
||||
if vmid, ok := daemon.Lookup("garbage-never-minted"); ok {
|
||||
t.Fatalf("unknown token authorized on retry: (%d,%v)", vmid, ok)
|
||||
}
|
||||
if got := daemon.reloads - base; got != 1 {
|
||||
t.Fatalf("second miss on an UNCHANGED store re-read the file: %d reloads total, want still 1 (size short-circuit)", got)
|
||||
}
|
||||
}
|
||||
|
||||
// B3 Scenario C: a cross-process re-mint (rotation) stays coherent through a reload — apply's
|
||||
// last-write-wins holds after the full re-read: the new hash resolves, the rotated-out one 401s,
|
||||
// and the indexes agree.
|
||||
func TestTokenStore_ReloadOnMiss_RemintCoherence(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "tokens.log")
|
||||
daemon, err := OpenTokenStore(path)
|
||||
if err != nil {
|
||||
t.Fatalf("open daemon store: %v", err)
|
||||
}
|
||||
defer daemon.Close()
|
||||
minter, err := OpenTokenStore(path)
|
||||
if err != nil {
|
||||
t.Fatalf("open minter store: %v", err)
|
||||
}
|
||||
defer minter.Close()
|
||||
|
||||
tok1, _ := minter.Mint(120)
|
||||
if vmid, ok := daemon.Lookup(tok1); !ok || vmid != 120 { // daemon absorbs tok1 via miss-reload
|
||||
t.Fatalf("tok1 lookup: (%d,%v), want (120,true)", vmid, ok)
|
||||
}
|
||||
tok2, _ := minter.Mint(120) // rotation, appended externally
|
||||
|
||||
if vmid, ok := daemon.Lookup(tok2); !ok || vmid != 120 {
|
||||
t.Fatalf("rotated token lookup: (%d,%v), want (120,true)", vmid, ok)
|
||||
}
|
||||
if vmid, ok := daemon.Lookup(tok1); ok {
|
||||
t.Fatalf("rotated-OUT token still authorizes vmid %d after reload — last-write-wins broken", vmid)
|
||||
}
|
||||
daemon.mu.Lock()
|
||||
gotHash, gotVMID := daemon.byVMID[120], daemon.byHash[hashToken(tok2)]
|
||||
oldGone := daemon.byHash[hashToken(tok1)]
|
||||
daemon.mu.Unlock()
|
||||
if gotHash != hashToken(tok2) || gotVMID != 120 || oldGone != 0 {
|
||||
t.Fatalf("index incoherent after reload: byVMID[120]=%.8s byHash[tok2]=%d byHash[tok1]=%d", gotHash, gotVMID, oldGone)
|
||||
}
|
||||
}
|
||||
|
||||
// §8 edge: the store file deleted between open and a miss — reload treats it as empty; Lookup
|
||||
// fails closed, no crash.
|
||||
func TestTokenStore_ReloadOnMiss_MissingFile(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("windows cannot unlink the store while its O_APPEND handle is open; production target is linux")
|
||||
}
|
||||
path := filepath.Join(t.TempDir(), "tokens.log")
|
||||
s, err := OpenTokenStore(path)
|
||||
if err != nil {
|
||||
t.Fatalf("open: %v", err)
|
||||
}
|
||||
defer s.Close()
|
||||
if _, err := s.Mint(1); err != nil {
|
||||
t.Fatalf("mint: %v", err)
|
||||
}
|
||||
if err := os.Remove(path); err != nil {
|
||||
t.Fatalf("remove store: %v", err)
|
||||
}
|
||||
if vmid, ok := s.Lookup("garbage-never-minted"); ok {
|
||||
t.Fatalf("lookup on a deleted store authorized (%d,%v)", vmid, ok)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenStore_Uniqueness(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "tokens.log")
|
||||
s, err := OpenTokenStore(path)
|
||||
|
||||
Reference in New Issue
Block a user