carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux && rootrunner
2
3package host
4
5import (
6 "context"
7 "crypto/sha256"
8 "encoding/hex"
9 "encoding/json"
10 "os"
11 "path/filepath"
12 "testing"
13
14 "github.com/carlosframework/platform/internal/release"
15 "github.com/carlosframework/platform/internal/rootstate"
16 "golang.org/x/sys/unix"
17)
18
19const generationPinRebootRoot = "/var/lib/carlos-e53-pins"
20
21func TestRootRebootGenerationPinIdentityPhase1(t *testing.T) {
22 if os.Geteuid() != 0 {
23 t.Fatal("requires root")
24 }
25 if err := os.Mkdir(generationPinRebootRoot, 0700); err != nil {
26 t.Fatal(err)
27 }
28 path := release.VersionPath(filepath.Join(generationPinRebootRoot, "opt"), "bab", "v1", "app")
29 tree := filepath.Dir(path)
30 if err := os.MkdirAll(tree, 0755); err != nil {
31 t.Fatal(err)
32 }
33 if err := os.WriteFile(path, []byte("app"), 0755); err != nil {
34 t.Fatal(err)
35 }
36 capture := func(path string, dir bool) rootstate.FileIdentity {
37 var f *os.File
38 var err error
39 if dir {
40 f, err = rootstate.OpenAuthorityDirectory(path)
41 } else {
42 f, err = rootstate.OpenAuthorityFile(path)
43 }
44 if err != nil {
45 t.Fatal(err)
46 }
47 defer f.Close()
48 id, err := rootstate.PersistentIdentity(f)
49 if err != nil {
50 t.Fatal(err)
51 }
52 return id
53 }
54 exe, parent := capture(path, false), capture(tree, true)
55 // Force boot-local renumbering even when this runner reuses device numbers.
56 exe.Device, parent.Device = exe.Device^0x100000, parent.Device^0x100000
57 exe.Mount, parent.Mount = exe.Mount+1000, parent.Mount+1000
58 digest := sha256.Sum256([]byte("app"))
59 b := release.GenerationBinding{Binding: release.HistoryBinding{Host: "inventory.example", Account: "bab", App: "app"}, Installation: "installation", Record: "record", RecordETag: "etag", Attempt: "main", Inputs: "inputs", Invocation: "invocation", ControllerPID: os.Getpid(), ControllerStart: "fixture", Version: "v1", Digest: hex.EncodeToString(digest[:]), Path: path, Tree: tree, Executable: rootstate.FileIdentity{Device: exe.Device, Inode: exe.Inode}, TreeIdentity: rootstate.FileIdentity{Device: parent.Device, Inode: parent.Inode}, ExecutableProof: exe, TreeProof: parent, Target: release.GenerationOwner{Host: "inventory.example", Incarnation: "inc", Stamp: 1, Session: 1, Box: "box", Enrollment: "enrollment", BootID: exe.Boot, ControllerID: "fixture"}}
60 state, err := rootstate.Open(filepath.Join(generationPinRebootRoot, "generations"), true, false)
61 if err != nil {
62 t.Fatal(err)
63 }
64 defer state.Close()
65 if _, err := (&release.RootGenerations{Store: state}).Reserve(b); err != nil {
66 t.Fatal(err)
67 }
68 raw, err := json.Marshal(b)
69 if err != nil {
70 t.Fatal(err)
71 }
72 if err := writeFileDurable(filepath.Join(generationPinRebootRoot, "binding.json"), raw, 0600); err != nil {
73 t.Fatal(err)
74 }
75 t.Logf("generation saved boot=%s device=%d mount=%d", exe.Boot, exe.Device, exe.Mount)
76}
77
78func TestRootRebootGenerationPinIdentityPhase2(t *testing.T) {
79 if os.Geteuid() != 0 {
80 t.Fatal("requires root")
81 }
82 defer os.RemoveAll(generationPinRebootRoot)
83 raw, err := os.ReadFile(filepath.Join(generationPinRebootRoot, "binding.json"))
84 var b release.GenerationBinding
85 if err != nil || json.Unmarshal(raw, &b) != nil {
86 t.Fatal("binding", err)
87 }
88 current, err := claimUnitBoot()
89 if err != nil || current == b.Target.BootID {
90 t.Fatal("requires actual reboot", err)
91 }
92 state, err := rootstate.Open(filepath.Join(generationPinRebootRoot, "generations"), false, false)
93 if err != nil {
94 t.Fatal(err)
95 }
96 defer state.Close()
97 ledger := &release.RootGenerations{Store: state}
98 // Completeness is supplied; persisted file and directory pin checks are real.
99 source := release.RootGenerationPins{Ledger: ledger, Verify: func(context.Context, []release.GenerationReservation) error { return nil }}
100 check := func() error {
101 pins, err := source.StagedPins(t.Context(), filepath.Join(generationPinRebootRoot, "opt"))
102 if err != nil {
103 return err
104 }
105 held, err := pins.Refers(b.Path)
106 if err != nil {
107 return err
108 }
109 if !held {
110 return os.ErrPermission
111 }
112 return nil
113 }
114 if err := check(); err != nil {
115 t.Fatal("cross-boot staged pin", err)
116 }
117 if err := unix.Mount(b.Tree, b.Tree, "", unix.MS_BIND, ""); err != nil {
118 t.Fatal(err)
119 }
120 if err := check(); err == nil {
121 t.Error("staged pin accepted substituted mount")
122 }
123 if err := unix.Unmount(b.Tree, 0); err != nil {
124 t.Fatal(err)
125 }
126 if err := os.Rename(b.Path, b.Path+".saved"); err != nil {
127 t.Fatal(err)
128 }
129 if err := os.WriteFile(b.Path, []byte("replacement"), 0755); err != nil {
130 t.Fatal(err)
131 }
132 if err := check(); err == nil {
133 t.Error("staged pin accepted replacement")
134 }
135 if err := os.Remove(b.Path); err != nil {
136 t.Fatal(err)
137 }
138 if err := os.Rename(b.Path+".saved", b.Path); err != nil {
139 t.Fatal(err)
140 }
141 if err := os.Chown(filepath.Join(generationPinRebootRoot, "opt"), 65534, 65534); err != nil {
142 t.Fatal(err)
143 }
144 if err := check(); err == nil {
145 t.Error("staged pin accepted untrusted ancestry")
146 }
147 if err := os.Chown(filepath.Join(generationPinRebootRoot, "opt"), 0, 0); err != nil {
148 t.Fatal(err)
149 }
150 if err := check(); err != nil {
151 t.Fatal("restored staged pin", err)
152 }
153 saved, err := ledger.Read(b)
154 if err != nil || saved.Binding != b {
155 t.Fatal("rewrote old generation identity", err)
156 }
157 t.Logf("staged pin retained across boot %s -> %s", b.Target.BootID, current)
158}
159