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 "errors"
8 "fmt"
9 "os"
10 "os/exec"
11 "path/filepath"
12 "reflect"
13 "strings"
14 "syscall"
15 "testing"
16 "time"
17
18 "github.com/carlosframework/platform/internal/release"
19 "github.com/carlosframework/platform/internal/rootstate"
20)
21
22func TestPlan14GenerationInstalledUnitRootRunner(t *testing.T) {
23 dir := generationRootRunnerDir(t)
24 pid1, err := os.ReadFile("/proc/1/comm")
25 if err != nil || strings.TrimSpace(string(pid1)) != "systemd" {
26 t.Fatal("designated runner requires systemd as PID 1")
27 }
28 for _, path := range []string{"/run/carlos-control", "/run/carlos", "/var/lib/carlos-claims", "/var/lib/carlos-release", rootReleasePrivate} {
29 private := path == "/var/lib/carlos-claims" || path == rootReleasePrivate
30 mode := os.FileMode(0755)
31 if private {
32 mode = 0700
33 }
34 if err := os.Mkdir(path, mode); err == nil {
35 t.Cleanup(func() {
36 if err := os.Remove(path); err != nil {
37 t.Error(err)
38 }
39 })
40 } else if !errors.Is(err, os.ErrExist) {
41 t.Fatal(err)
42 }
43 state, err := rootstate.Open(path, false, !private)
44 if err != nil {
45 t.Fatal(err)
46 }
47 state.Close()
48 }
49 authority := filepath.Join(rootReleasePrivate, filepath.Base(dir))
50 state, err := rootstate.Open(authority, true, false)
51 if err != nil {
52 t.Fatal(err)
53 }
54 state.Close()
55 t.Cleanup(func() {
56 if err := os.RemoveAll(authority); err != nil {
57 t.Error(err)
58 }
59 })
60 for _, name := range []string{"data", "opt"} {
61 if err := os.Mkdir(filepath.Join(dir, name), 0755); err != nil {
62 t.Fatal(err)
63 }
64 }
65 binary, err := os.Executable()
66 if err != nil {
67 t.Fatal(err)
68 }
69 body, err := os.ReadFile(binary)
70 if err != nil {
71 t.Fatal(err)
72 }
73 installed := filepath.Join(dir, "runner.test")
74 if err := os.WriteFile(installed, body, 0755); err != nil {
75 t.Fatal(err)
76 }
77 raw, err := unitsFS.ReadFile("units/" + claimControlUnit)
78 if err != nil {
79 t.Fatal(err)
80 }
81 ctl := func(t *testing.T, args ...string) string {
82 t.Helper()
83 ctx, cancel := context.WithTimeout(t.Context(), 45*time.Second)
84 defer cancel()
85 out, err := exec.CommandContext(ctx, "systemctl", args...).CombinedOutput()
86 if err != nil {
87 t.Fatalf("systemctl %v: %v\n%s", args, err, out)
88 }
89 return strings.TrimSpace(string(out))
90 }
91 for _, mode := range []string{"off", "on"} {
92 t.Run(mode, func(t *testing.T) {
93 unit := filepath.Base(dir) + "-" + mode + ".service"
94 content := claimUnitContent(claimControlUnit, raw, claimUnitSettings{enabled: mode == "on", data: filepath.Join(dir, "data"), opt: filepath.Join(dir, "opt")})
95 var lines []string
96 for _, line := range strings.Split(string(content), "\n") {
97 if strings.HasPrefix(line, "Requires=") || strings.HasPrefix(line, "After=") || strings.HasPrefix(line, "ExecStart=") || strings.HasPrefix(line, "EnvironmentFile=") {
98 continue
99 }
100 lines = append(lines, line)
101 }
102 content = []byte(strings.Join(lines, "\n") + fmt.Sprintf("\nType=oneshot\nTimeoutStartSec=45s\nExecStart=%s -test.run=^TestPlan14GenerationUnitPayload$ -test.v -test.timeout=30s\nEnvironment=PLAN14_GENERATION_UNIT_MODE=%s PLAN14_GENERATION_UNIT_STORE=%s\nIPAddressDeny=any\n", installed, mode, authority))
103 path := filepath.Join("/run/systemd/system", unit)
104 file, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
105 if err != nil {
106 t.Fatal(err)
107 }
108 t.Cleanup(func() {
109 for _, args := range [][]string{{"stop", unit}, {"reset-failed", unit}} {
110 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
111 out, err := exec.CommandContext(ctx, "systemctl", args...).CombinedOutput()
112 cancel()
113 if err != nil && args[0] == "stop" {
114 t.Errorf("cleanup unit: %v %s", err, out)
115 }
116 }
117 if err := os.Remove(path); err != nil {
118 t.Error(err)
119 }
120 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
121 defer cancel()
122 if out, err := exec.CommandContext(ctx, "systemctl", "daemon-reload").CombinedOutput(); err != nil {
123 t.Errorf("cleanup reload: %v %s", err, out)
124 }
125 })
126 _, writeErr := file.Write(content)
127 if err := errors.Join(writeErr, file.Close()); err != nil {
128 t.Fatal(err)
129 }
130 ctl(t, "daemon-reload")
131 ctl(t, "start", unit)
132 if ctl(t, "show", unit, "--property=Result", "--value") != "success" || ctl(t, "show", unit, "--property=ExecMainStatus", "--value") != "0" {
133 t.Fatal("installed unit did not complete its payload")
134 }
135 })
136 }
137}
138
139func TestPlan14GenerationUnitPayload(t *testing.T) {
140 mode, path := os.Getenv("PLAN14_GENERATION_UNIT_MODE"), os.Getenv("PLAN14_GENERATION_UNIT_STORE")
141 if path == "" || (mode != "off" && mode != "on" && mode != "service") {
142 t.Fatal("invoke through TestPlan14GenerationInstalledUnitRootRunner")
143 }
144 if mode == "service" {
145 if os.Geteuid() != 65534 {
146 t.Fatal("service witness did not drop credentials")
147 }
148 if _, err := os.ReadDir(path); !errors.Is(err, os.ErrPermission) {
149 t.Fatalf("service listed private generations: %v", err)
150 }
151 if err := os.WriteFile(filepath.Join(path, "forged.json"), nil, 0600); !errors.Is(err, os.ErrPermission) {
152 t.Fatalf("service wrote private generations: %v", err)
153 }
154 return
155 }
156 if os.Geteuid() != 0 {
157 t.Fatal("installed control unit must run as root")
158 }
159 if mode == "off" {
160 if err := os.WriteFile(filepath.Join(path, "unexpected.json"), nil, 0600); !errors.Is(err, syscall.EROFS) {
161 t.Fatalf("claims-off private generation write: %v", err)
162 }
163 return
164 }
165 state, err := rootstate.Open(path, false, false)
166 if err != nil {
167 t.Fatal(err)
168 }
169 defer state.Close()
170 boot, err := (generationKernel{proc: "/proc"}).boot()
171 if err != nil {
172 t.Fatal(err)
173 }
174 start, err := claimConstructionProcessStart()
175 if err != nil {
176 t.Fatal(err)
177 }
178 binary, err := os.Executable()
179 if err != nil {
180 t.Fatal(err)
181 }
182 identity := func(path string) rootstate.FileIdentity {
183 t.Helper()
184 file, err := os.Open(path)
185 if err != nil {
186 t.Fatal(err)
187 }
188 defer file.Close()
189 id, err := rootstate.Identity(file)
190 if err != nil {
191 t.Fatal(err)
192 }
193 return id
194 }
195 // Authorization and launch facts are fixtures. This tests installed access.
196 binding := release.GenerationBinding{
197 Binding: release.HistoryBinding{Host: "unit.example", Account: "bab", App: "shop"},
198 Target: release.GenerationOwner{Host: "unit.example", Incarnation: "inc", Stamp: 1, Session: 1, Box: "runner", Enrollment: "en", BootID: boot, ControllerID: "controller"},
199 Installation: "fixture", Attempt: "main", Inputs: "fixture", Invocation: "fixture", Record: "fixture", RecordETag: "fixture",
200 ControllerPID: os.Getpid(), ControllerStart: start, Version: "v1", Digest: strings.Repeat("a", 64),
201 Path: binary, Tree: filepath.Dir(binary), Executable: identity(binary), TreeIdentity: identity(filepath.Dir(binary)),
202 }
203 g := &release.RootGenerations{Store: state}
204 reserved, err := g.Reserve(binding)
205 if err != nil {
206 t.Fatal(err)
207 }
208 cmd := exec.CommandContext(t.Context(), binary, "-test.run=^TestPlan14GenerationUnitPayload$", "-test.v", "-test.timeout=10s")
209 cmd.Dir = filepath.Dir(binary)
210 cmd.Env = []string{"PLAN14_GENERATION_UNIT_MODE=service", "PLAN14_GENERATION_UNIT_STORE=" + path}
211 cmd.SysProcAttr = &syscall.SysProcAttr{Credential: &syscall.Credential{Uid: 65534, Gid: 65534, Groups: []uint32{65534}}}
212 if out, err := cmd.CombinedOutput(); err != nil {
213 t.Fatalf("service-UID witness: %v\n%s", err, out)
214 }
215 state.Close()
216 state, err = rootstate.Open(path, false, false)
217 if err != nil {
218 t.Fatal(err)
219 }
220 defer state.Close()
221 got, err := (&release.RootGenerations{Store: state}).Read(binding)
222 if err != nil || !reflect.DeepEqual(got, reserved) {
223 t.Fatalf("reopened generation changed: %+v %v", got, err)
224 }
225}
226