diff --git a/cmd/rollcall/carot.go b/cmd/rollcall/carot.go index 9ffe1a6..a7b1ce2 100644 --- a/cmd/rollcall/carot.go +++ b/cmd/rollcall/carot.go @@ -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) } diff --git a/cmd/rollcall/carot_test.go b/cmd/rollcall/carot_test.go index b54fe9b..b1883d4 100644 --- a/cmd/rollcall/carot_test.go +++ b/cmd/rollcall/carot_test.go @@ -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") + } +} diff --git a/cmd/rollcall/rackctl.go b/cmd/rollcall/rackctl.go index 997d428..4adfeac 100644 --- a/cmd/rollcall/rackctl.go +++ b/cmd/rollcall/rackctl.go @@ -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