package host import ( "context" "crypto/ed25519" "encoding/json" "github.com/carlosframework/platform/internal/hostclaim" "github.com/carlosframework/platform/internal/hostguard" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/registry" "github.com/carlosframework/platform/internal/rootparticipation" "github.com/carlosframework/platform/internal/rootstate" "os" "path/filepath" "testing" "time" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/rollout" ) func TestPlan14SwapFailureSurvivesLegacyTampering(t *testing.T) { for _, attack := range []string{"erase", "rename", "retried", "epoch", "served"} { t.Run(attack, func(t *testing.T) { s := testRootFailureStore(t) b, err := rollout.InitializeRootFailures(s, "box/en/1", nil) if err != nil { t.Fatal(err) } _, err = b.Record(rollout.FailureRecord{Account: "bab", App: "km", Build: "v1", Host: "h.example.com", HeldDown: []string{"h.example.com"}}) if err != nil { t.Fatal(err) } dd := t.TempDir() raw := []byte(`{"schema":3,"records":[{"account":"bab","app":"km","build":"v1","host":"h.example.com","retried":["h.example.com"],"epochs":{"h.example.com":1}}]}`) if err := os.WriteFile(filepath.Join(dd, rollout.FailureFile), raw, 0600); err != nil { t.Fatal(err) } if attack == "erase" { if err := os.Remove(filepath.Join(dd, rollout.FailureFile)); err != nil { t.Fatal(err) } } if attack == "rename" { if err := os.Rename(dd, dd+"-old"); err != nil { t.Fatal(err) } t.Cleanup(func() { os.RemoveAll(dd + "-old") }) } legacy, err := rootFailureLegacy(dd) if err != nil { t.Fatal(err) } if err = b.ImportLegacy(legacy); err != nil { t.Fatal(err) } b, err = rollout.OpenRootFailures(s, "box/en/1") if err != nil { t.Fatal(err) } if !b.Blocks("bab", "km", "v1", "sibling.example.com", false) || !b.HeldDown("bab", "km", "v1", "h.example.com") { t.Fatal("root failure lost") } testRootSettingsPass(t, b, attack) }) } } func TestPlan14SwapFailureVerifiedRetry(t *testing.T) { s := testRootFailureStore(t) b, err := rollout.InitializeRootFailures(s, "box/en/1", nil) if err != nil { t.Fatal(err) } rec := rollout.FailureRecord{Account: "bab", App: "km", Build: "v1", Host: "h.example.com", HeldDown: []string{"h.example.com"}} if _, err = b.Record(rec); err != nil { t.Fatal(err) } seed, public, _ := release.GenerateKey() priv, _ := release.ParseSigningKey(seed, "test") pub, _ := release.ParsePublicKey(public, "test") wire, err := release.SignHostRetry(release.RootSigner(priv), "bab", "km", rec.Host, "v1", b.ID(), b.PublishedEpoch("bab", "km", "v1", rec.Host), time.Now()) if err != nil { t.Fatal(err) } if err = b.VerifiedRetry(pub, wire, "bab", "km", rec.Host, time.Now()); err != nil { t.Fatal(err) } if b.HeldDown("bab", "km", "v1", rec.Host) || b.Blocks("bab", "km", "v1", rec.Host, false) { t.Fatal("exact retry did not clear") } if !b.Blocks("bab", "km", "v1", "sibling.example.com", false) { t.Fatal("retry widened") } if _, err = b.Record(rec); err != nil { t.Fatal(err) } if err = b.VerifiedRetry(pub, wire, "bab", "km", rec.Host, time.Now()); err != nil { t.Fatal(err) } if !b.HeldDown("bab", "km", "v1", rec.Host) { t.Fatal("stale retry cleared second failure") } } func TestPlan14SwapFailureMigrationUnknown(t *testing.T) { s := testRootFailureStore(t) if _, err := rollout.OpenRootFailures(s, "box/en/1"); err == nil { t.Fatal("absent marker granted empty history") } b, err := rollout.InitializeRootFailures(s, "box/en/1", nil) if err != nil { t.Fatal(err) } if b.Blocks("bab", "km", "v2", "h.example.com", false) { t.Fatal("unknown history refused an unfailed signed target") } if _, err := rollout.OpenRootFailures(s, "other/en/1"); err == nil { t.Fatal("foreign migration accepted") } } func testRootSettingsPass(t *testing.T, book *rollout.RootFailures, attack string, activation ...bool) { t.Helper() ctx := context.Background() opt := t.TempDir() store := release.NewDirStore(t.TempDir()) seed, public, _ := release.GenerateKey() priv, _ := release.ParseSigningKey(seed, "test") pub, _ := release.ParsePublicKey(public, "test") for _, v := range []string{"v1", "v2"} { m := release.Manifest{Schema: 1, App: "km", Version: v, Kind: "binary", Artifacts: map[string]release.Artifact{runtimePlatform(): {}}} if err := release.Ship(ctx, store, "bab", m, map[string][]byte{runtimePlatform(): []byte(v)}); err != nil { t.Fatal(err) } if err := release.Promote(ctx, store, priv, "bab", "km", "canary/x", v); err != nil { t.Fatal(err) } } rec := provision.Record{Schema: 1, Host: "h.example.com", Account: "bab", App: "km", Channel: "canary/x", Pin: "v1", CreatedAt: time.Now().UTC()} body, _ := json.Marshal(rec) if _, err := store.Put(ctx, provision.RecordKey(rec.Account, rec.App, rec.Host), body, ""); err != nil { t.Fatal(err) } inc, _ := hostclaim.Incarnation(rec.Account, rec.App, rec.Host, rec.CreatedAt) state := &rollbackMemory{files: map[string][]byte{}} in := hostguard.RootInstallation{Host: rec.Host, Incarnation: inc, Stamp: 1, Account: rec.Account, App: rec.App, Record: provision.RecordKey(rec.Account, rec.App, rec.Host), Box: "box", Enrollment: "en", Migration: 1, InventoryRevision: 1} if err := hostguard.NewRootAuthority(state).RetainMigrationDeny(ctx, in, 1); err != nil { t.Fatal(err) } census := func() (rootparticipation.Census, error) { return rootparticipation.Census{Schema: 1, Box: "box", Enrollment: "en", Migration: 1, Complete: true}, nil } p := rootUpdateSource{enforceFailures: len(activation) == 0, source: rootRestartRequestSource{state: state, store: store, census: census}, state: state, book: book, store: store, pub: pub, binding: &proofBindingSource{}, data: t.TempDir()} if len(activation) != 0 { p.publish = func(string, rootRetryStatusRecord) error { return os.ErrPermission } } rt := provision.RouteFor(rec, p.data, "", false, nil) reg, err := registry.Open(filepath.Join(t.TempDir(), "r.db")) if err != nil { t.Fatal(err) } defer reg.Close() switch attack { case "empty", "erase", "rename": rt.UpdateSettings = "" case "object": rt.UpdateSettings = "{}" case "channel": rt.UpdateSettings = `{"updates":"auto","channel":"canary/forged"}` rt.Channel = "canary/forged" case "served", "retried", "epoch": rt.GoodVersion = "v1" case "digest": rt.GoodDigest = "forged" rt.GoodDemand = "forged" rt.ServingVersion = "v1" case "build": rt.BuildState = release.BuildStateCopy{Known: true, State: &release.BuildState{LastExecuted: "v1", Completed: 99}}.Column() case "combined": rt.UpdateSettings = "" rt.GoodVersion = "v1" rt.BuildState = release.BuildStateCopy{Known: true}.Column() } if err = reg.Upsert(rt); err != nil { t.Fatal(err) } provider := func(context.Context, release.Store, ed25519.PublicKey) (*release.RootUpdateInputs, error) { return &release.RootUpdateInputs{Resolve: p.resolve}, nil } effects := 0 pass := func() error { return release.Pass(ctx, reg, store, pub, opt, func(string) error { effects++; return nil }, updatesPassOption(p.data, true, provider)) } if err = pass(); err != nil { t.Fatal(err) } want := "" if len(activation) != 0 { want = "v1" } if v, _ := release.LiveVersion(opt, rt.Host); v != want || effects != 0 { t.Fatal("wrong activation failure policy", v, want, effects) } if len(activation) != 0 { if !book.HeldDown(rec.Account, rec.App, "v1", rec.Host) { t.Fatal("attribution erased") } if saved, err := p.resolve(ctx, rec.Host); err != nil || saved.Failed("v1") || saved.Failed("v2") { t.Fatal("sticky deny active", err) } return } raw, _ := state.Read(rootSettingsKey(rt.Host, rootHistoryInstallation(in))) var saved rootUpdateRecord if json.Unmarshal(raw, &saved) != nil || saved.Consumed == nil || saved.Consumed.Desired != "" || saved.Consumed.Completed != 0 { t.Fatal("forged completion", string(raw)) } if attack == "verified-retry" { legacy, err := rollout.OpenFailureBook(p.data) if err != nil { t.Fatal(err) } if _, err = legacy.Record(rollout.FailureRecord{Account: rec.Account, App: rec.App, Host: rec.Host, Build: "v1", HeldDown: []string{rec.Host}}); err != nil { t.Fatal(err) } p.legacy, err = rootFailureLegacy(p.data) if err != nil { t.Fatal(err) } wire, err := release.SignHostRetry(release.RootSigner(priv), rec.Account, rec.App, rec.Host, "v1", book.ID(), book.PublishedEpoch(rec.Account, rec.App, "v1", rec.Host), time.Now()) if err != nil { t.Fatal(err) } log := release.ScopedRestartGeneration{Schema: 1, Requests: []release.RestartRequest{{ID: "root-retry", RequestedAt: time.Now().UTC().Format(time.RFC3339), Scope: release.RestartScope{Kind: release.RestartScopeRetry, Value: rec.Host, Signed: wire}}}} raw, _ := json.Marshal(log) if _, err = store.Put(ctx, release.HostRetryKey(rec.Account, rec.App), raw, ""); err != nil { t.Fatal(err) } if err = pass(); err != nil { t.Fatal(err) } if v, _ := release.LiveVersion(opt, rt.Host); v != "v1" { t.Fatal("verified retry did not reach production adoption", v) } raw, _ = state.Read(rootSettingsKey(rt.Host, rootHistoryInstallation(in))) var mirror rootUpdateRecord if json.Unmarshal(raw, &mirror) != nil || mirror.RetryMirror == nil || mirror.RetryMirror.Book != legacy.ID() || mirror.RetryMirror.Epoch != legacy.PublishedEpoch(rec.Account, rec.App, "v1", rec.Host) { t.Fatal("root did not bind agent mirror receipt", string(raw)) } } if attack == "provider-missing" { if err := state.Remove(rec.Host + ".json"); err != nil { t.Fatal(err) } if err := pass(); err == nil { t.Fatal("missing root installation fell through") } return } // A different signed build remains available with unknown history. rec.Pin = "v2" body, _ = json.Marshal(rec) _, etag, _ := store.Get(ctx, provision.RecordKey(rec.Account, rec.App, rec.Host)) if _, err = store.Put(ctx, provision.RecordKey(rec.Account, rec.App, rec.Host), body, etag); err != nil { t.Fatal(err) } if err = pass(); err != nil { t.Fatal(err) } if v, _ := release.LiveVersion(opt, rt.Host); v != "v2" { t.Fatal("signed positive refused", v) } } func TestPlan14RootSettingsExactRegistryMutations(t *testing.T) { for _, attack := range []string{"empty", "object", "channel", "served", "digest", "build", "combined", "verified-retry", "provider-missing"} { t.Run(attack, func(t *testing.T) { s := testRootFailureStore(t) b, err := rollout.InitializeRootFailures(s, "box/en/1", nil) if err != nil { t.Fatal(err) } record := rollout.FailureRecord{Account: "bab", App: "km", Build: "v1", Host: "h.example.com", HeldDown: []string{"h.example.com"}} if attack == "served" || attack == "digest" || attack == "build" { record.HeldDown = nil } if _, err = b.Record(record); err != nil { t.Fatal(err) } testRootSettingsPass(t, b, attack) }) } } func testRootFailureStore(t *testing.T) rootstate.Store { t.Helper() if os.Geteuid() != 0 { return &rollbackMemory{files: map[string][]byte{}} } dir, err := os.MkdirTemp("/var/lib", "carlos-e3-book-") if err != nil { t.Fatal(err) } t.Cleanup(func() { os.RemoveAll(dir) }) s, err := rootstate.Open(dir, false, false) if err != nil { t.Fatal(err) } t.Cleanup(func() { s.Close() }) return s } func TestPlan14RootFailureActivationGate(t *testing.T) { for _, attack := range []string{"combined", "served", "channel", "build"} { t.Run(attack, func(t *testing.T) { b, err := rollout.InitializeRootFailures(testRootFailureStore(t), "box/en/1", nil) if err != nil { t.Fatal(err) } if _, err = b.Record(rollout.FailureRecord{Account: "bab", App: "km", Build: "v1", Host: "h.example.com", HeldDown: []string{"h.example.com"}}); err != nil { t.Fatal(err) } if err = b.MarkHoldRan("h.example.com", rollout.HoldRan{Account: "bab", App: "km", Build: "v1", Exec: "unsettled"}); err != nil { t.Fatal(err) } testRootSettingsPass(t, b, attack, true) }) } }