//go:build linux && rootrunner package host import ( "bytes" "context" "crypto/ed25519" "encoding/base64" "encoding/json" "fmt" "net" "os" "os/exec" "os/user" "path/filepath" "strconv" "syscall" "testing" "time" "github.com/carlosframework/platform/internal/hostactors" "github.com/carlosframework/platform/internal/hostbox" execlease "github.com/carlosframework/platform/internal/hostexec" "github.com/carlosframework/platform/internal/hostguard" "github.com/carlosframework/platform/internal/hostops" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/rootparticipation" "github.com/carlosframework/platform/internal/rootstate" ) func init() { if len(os.Args) > 1 && os.Args[1] == registryChildCommand { if err := RunRegistryChild(os.Args[2:]); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } os.Exit(0) } } func TestRootFilesSeedInstanceControlConstruction(t *testing.T) { runServingParkPeerNamespace(t, false, false, "seed-construction") } func filesSeedInstanceControlChild(t *testing.T, binary, root string, listener *net.UnixListener) { t.Helper() for _, path := range []string{"/data", "/var/lib/carlos-claims"} { if err := os.Mkdir(path, 0755); err != nil { t.Fatal("refuse existing fixture path", path, err) } t.Cleanup(func() { os.RemoveAll(path) }) } account, err := user.Lookup("carlos") if err != nil { t.Fatal(err) } uid, _ := strconv.Atoi(account.Uid) gid, _ := strconv.Atoi(account.Gid) if err := os.Chown("/data", uid, gid); err != nil { t.Fatal(err) } file, err := listener.File() if err != nil { t.Fatal(err) } defer file.Close() listener.SetUnlinkOnClose(false) if err := listener.Close(); err != nil { t.Fatal(err) } output, err := os.Create(filepath.Join(root, "control.log")) if err != nil { t.Fatal(err) } cmd := exec.Command(binary, "-test.run=^TestRootFilesSeedInstanceControlPayload$", "-test.v", "-test.timeout=90s") cmd.Env = append(os.Environ(), "FILES_SEED_CONTROL=1", "TMPDIR="+root) if os.Getenv("PLAN14_R9_PENDING") != "" || os.Getenv("PLAN14_R10_ACQUISITION") != "" || os.Getenv("PLAN14_R10_RETIRED") != "" { cmd.SysProcAttr = &syscall.SysProcAttr{Cloneflags: syscall.CLONE_NEWNS} for _, path := range []string{"/run/carlos", "/run/carlos/hostguard"} { if err := os.Mkdir(path, 0755); err == nil { t.Cleanup(func() { os.RemoveAll(path) }) } else if !os.IsExist(err) { t.Fatal(err) } } } cmd.ExtraFiles = []*os.File{file} cmd.Stdout, cmd.Stderr = output, output if err := cmd.Start(); err != nil { output.Close() t.Fatal(err) } t.Cleanup(func() { cmd.Process.Kill() cmd.Wait() output.Close() body, _ := os.ReadFile(filepath.Join(root, "control.log")) t.Log(string(body)) }) // The inherited listener stays open while RunInstanceControl constructs its owners. } func TestRootFilesSeedInstanceControlPayload(t *testing.T) { if os.Getenv("FILES_SEED_CONTROL") != "1" { t.Fatal("invoke through construction witness") } t.Setenv("CARLOS_HOST_CLAIM", "1") t.Setenv("CARLOS_HOST_CLAIM_ORIGINS", "1") t.Setenv("CARLOS_MOVES_PHASE1", "") t.Setenv("CARLOS_REGION_HA", "") t.Setenv("CARLOS_DEPLOYMENT_BUCKET", "") t.Setenv("CARLOS_DATA_DIR", "/data") t.Setenv("LISTEN_FDS", "1") t.Setenv("LISTEN_PID", strconv.Itoa(os.Getpid())) store, resources, owners, _ := fix4JournalFixture(t) if os.Getenv("PLAN14_R10_ACQUISITION") != "" || os.Getenv("PLAN14_R10_RETIRED") != "" { openClaimRootAuthority = func() (rootstate.Store, error) { return rootstate.Open("/var/lib/carlos-claims/authority/fences", true, false) } openClaimRootJournal = func() (*hostbox.LaunchJournal, error) { return hostbox.OpenLaunchJournal("/var/lib/carlos-claims/journals.db") } } resources.OptDir = generationRootRunnerDir(t) dir := t.TempDir() t.Setenv("CARLOS_DEPLOYMENT_DIR", dir) t.Setenv("CARLOS_OPT_DIR", resources.OptDir) body, _, err := store.Get(t.Context(), hostactors.Key) if err != nil { t.Fatal(err) } if _, err := release.NewDirStore(dir).Put(t.Context(), hostactors.Key, body, ""); err != nil { t.Fatal(err) } owners.Serving = plan9D5Fixture(t).owners.Sources owners.Controllers = productionStopControllers{} claimOwnerRegistration.Provider = seedConstructionOwners{owners} state, err := rootstate.Open(rootparticipation.Namespace, true, false) if err != nil { t.Fatal(err) } actor, err := owners.Identity.Verify(t.Context()) if err != nil { t.Fatal(err) } filesSeedConstructionMigration(t, resources.OptDir, actor.ControlBucket, actor.Box, actor.Enrollment) raw, _ := json.Marshal(rootparticipation.Census{Schema: 1, Box: actor.Box, Enrollment: actor.Enrollment, Migration: 1, Revision: 1, LaunchRevision: 1, Complete: true}) if err := state.Write("census.json", raw); err != nil { t.Fatal(err) } state.Close() exclusion := &release.ReleaseExclusion{Open: func() (rootstate.Store, error) { return rootstate.Open(filepath.Join(dir, "release-exclusion"), true, false) }} rootLaunchStartup.Do(func() { verify := func(ctx context.Context, _ uint64) error { return exclusion.Check(ctx) } if os.Getenv("PLAN14_R10_ACQUISITION") != "" || os.Getenv("PLAN14_R10_RETIRED") != "" { generation, err := rootstate.Open(release.RootGenerationsDir, true, false) if err != nil { t.Fatal(err) } generation.Close() read := func(ctx context.Context) (generationInventory, error) { snapshot, err := productionLocalGenerationInventory(ctx) if err != nil { t.Logf("production generation read: %v", err) return snapshot, err } if os.Getenv("PLAN14_R10_RETIRED") != "" { if len(snapshot.Installations) != 2 || len(snapshot.Payloads) != 2 { t.Fatalf("lost retained installation or payload: %d %d", len(snapshot.Installations), len(snapshot.Payloads)) } installs := map[string]hostguard.FenceView{} for _, v := range snapshot.Installations { installs[v.Host] = v } if err := generationPayloadBindings(snapshot.Payloads[1:], installs); err == nil { t.Fatal("retirement dropped payload obligation") } } return snapshot, nil } v := rootLaunchVerifier{exclude: exclusion, open: func() (rootstate.Store, error) { return rootstate.Open(release.RootGenerationsDir, false, false) }, reconcile: generationReconciler{read: read, kernel: func(context.Context) (string, string, error) { boot, err := execlease.BootID() return boot, floorTestDigest, err }}} verify = v.verify } rootLaunchStartup.err = rootparticipation.RegisterLaunchVerifier(verify, exclusion.With) }) if rootLaunchStartup.err != nil { t.Fatal(rootLaunchStartup.err) } if mode := os.Getenv("PLAN14_R9_PENDING"); mode != "" { filesSeedPendingConstruction(t, release.NewDirStore(dir), resources, owners.ClaimOwnerBindings, actor) if mode == "system" { if err := recoverRootExecution(t.Context(), "/data", systemGuardDir(resources.OptDir)); err != nil { t.Fatal("production system recovery", err) } } } if os.Getenv("PLAN14_R10_RETIRED") != "" { for _, host := range []string{"sibling.example", "retired.example"} { t.Setenv("PLAN14_R10_INSTALL_HOST", host) filesSeedPendingConstruction(t, release.NewDirStore(dir), resources, owners.ClaimOwnerBindings, actor) } } if os.Getenv("PLAN14_R10_ACQUISITION") != "" { plan14PendingAcquisition(t, resources.OptDir, actor.ControlBucket) } RunInstanceControl(nil) t.Fatal("claims-on instance-control returned before serving") } type seedConstructionOwners struct{ fix4Owners } func (o seedConstructionOwners) OpenClaimOwners(_ context.Context, store release.Store, _ ClaimRuntimeResources) (ClaimOwnerBindings, error) { owners := o.ClaimOwnerBindings authority := *owners.RetirementAuthority authority.Store = store authority.Directory.Store = store owners.RetirementAuthority = &authority return owners, nil } func filesSeedConstructionMigration(t *testing.T, opt, deployment, box, enrollment string) { t.Helper() boot, err := execlease.BootID() if err != nil { t.Fatal(err) } record := rootMigrationRecord{Schema: 1, Identity: rootMigrationIdentity{Deployment: deployment, Box: box, Enrollment: enrollment, Generation: 1}, Phase: "complete", Snapshot: rootMigrationSnapshot{ Complete: true, DirectoryRevision: 1, Exclusion: "construction-fixture", Items: []rootMigrationItem{}, Report: hostops.CensusReport{Complete: true, Box: box, Enrollment: enrollment, DirectoryRevision: 1, Reservation: "construction-fixture", BootID: boot, CapturedAt: time.Now(), Installations: []hostops.CensusInstallation{}, Seeds: &hostops.CensusSeeds{}}, }} if err := record.Snapshot.preflight(record.Identity); err != nil { t.Fatal("construction migration preflight", err) } migration, err := rootstate.Open(rootMigrationDir, true, false) if err != nil { t.Fatal("construction migration state", err) } defer migration.Close() raw, err := json.Marshal(record) if err != nil { t.Fatal(err) } if err := migration.Write("migration.json", raw); err != nil { t.Fatal(err) } key := ed25519.NewKeyFromSeed(bytes.Repeat([]byte{1}, ed25519.SeedSize)) t.Setenv("CARLOS_RELEASE_PUBKEY", base64.StdEncoding.EncodeToString(key.Public().(ed25519.PublicKey))) if err := os.MkdirAll(systemGuardDir(opt), 0755); err != nil { t.Fatal(err) } rootFloorRunnerBinary(t, opt, systemBinaryPath(opt), "v2.0.0", key) rootFloorRunnerBinary(t, opt, filepath.Join(systemGuardDir(opt), "carlos"), "v2.0.0", key) }