Rack state re-pins rollcall.ca in stored boot entries
CI / agent (push) Successful in 3s
CI / site (push) Successful in 3s
CI / go (push) Successful in 41s

Commission, deploy and stateless entries are stored with the root's
fingerprint of the moment; after a rotation switch a machine booting
from one refused its rack. The state now writes the root the rack serves.
Found in the lab rotation (node002's entry on pxe-rack2).

Refs #12

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Ilker ManapandClaude Opus 5.5 committed 2026-10-31 16:40:13 +01:00
1 parent 2f708e8f69
commit a9235c85d8
3 files changed
+25

No files matched your search

+6
View File
@@ -33,6 +33,7 @@ import (
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"time"
@@ -671,3 +672,8 @@ func (r *rackd) stageBundle(t *rackTrustState, trust []*x509.Certificate) error
}
return writeAtomic(filepath.Join(dir, "ca-ready"), []byte("1\n"), 0o644)
}
var caWordRe = regexp.MustCompile(`\brollcall\.ca=[0-9a-fA-F]{64}\b`)
// pinCA points the rollcall.ca= words of a boot entry at the root fp.
func pinCA(entry, fp string) string { return caWordRe.ReplaceAllString(entry, "rollcall.ca="+fp) }
+12
View File
@@ -176,3 +176,15 @@ func TestCARefreshScript(t *testing.T) {
t.Fatal("shQuote")
}
}
func TestPinCA(t *testing.T) {
old, nw := strings.Repeat("7e", 32), strings.Repeat("a1", 32)
in := "LABEL deploy\n APPEND initrd=x rollcall.tls=https://10.213.78.1 rollcall.ca=" + old + " quiet\n APPEND rollcall.ca=" + old + "\n"
got := pinCA(in, nw)
if strings.Contains(got, old) || strings.Count(got, "rollcall.ca="+nw) != 2 || !strings.Contains(got, "rollcall.tls=https://10.213.78.1 rollcall.ca="+nw+" quiet") {
t.Fatalf("%q", got)
}
if pinCA("APPEND rollcall.server=x", nw) != "APPEND rollcall.server=x" {
t.Fatal("changed an entry without rollcall.ca")
}
}
+7
View File
@@ -15,6 +15,7 @@ package main
import (
"context"
"crypto/subtle"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
@@ -488,6 +489,12 @@ func (a *App) rackState(ctx context.Context, name string) (*rackState, error) {
if st.PXE, err = a.store.PXEFiles(ctx, name); err != nil {
return nil, err
}
if a.ca != nil { // entries keep the root they were written with; a rotation moves it
fp := certFingerprint(a.serverCAFor(base))
for k, v := range st.PXE {
st.PXE[k] = pinCA(v, hex.EncodeToString(fp[:]))
}
}
subnets, err := a.store.Subnets(ctx)
if err != nil {
return nil, err