oficina / home Public

Clone
git clone https://amadan.net/oficina/home

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package operatorlaunch
4
5import (
6 "bytes"
7 "context"
8 "crypto/sha256"
9 "encoding/base64"
10 "encoding/hex"
11 "encoding/json"
12 "io"
13 "os"
14 "os/exec"
15 "path/filepath"
16 "strings"
17 "testing"
18 "time"
19
20 "amadan.net/carlos/platform.git/launchctx"
21 "golang.org/x/sys/unix"
22)
23
24func TestStartupChild(t *testing.T) {
25 if os.Getenv("HOME_STARTUP_FIXTURE") != "1" {
26 return
27 }
28 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
29 defer cancel()
30 c, raw, err := ReadOperator(ctx, os.Getenv("HOME_STARTUP_DATABASE"))
31 closed := true
32 for _, fd := range []uintptr{0, 3, 4} {
33 var st unix.Stat_t
34 flags, e := unix.FcntlInt(fd, unix.F_GETFL, 0)
35 if e != nil || flags&unix.O_ACCMODE != unix.O_RDONLY || unix.Fstat(int(fd), &st) != nil || st.Mode&unix.S_IFMT != unix.S_IFCHR || unix.Major(st.Rdev) != 1 || unix.Minor(st.Rdev) != 3 {
36 closed = false
37 }
38 }
39 if err != nil && (err != ErrRefused || len(raw) != 0 || c.Version != 0) {
40 os.Exit(3)
41 }
42 if err == nil {
43 file, e := os.Open(os.Getenv("HOME_STARTUP_DATABASE"))
44 if e != nil || file.Fd() <= 4 {
45 os.Exit(4)
46 }
47 if os.Stdin.Close() != nil {
48 os.Exit(5)
49 }
50 if _, e := file.Stat(); e != nil || file.Close() != nil {
51 os.Exit(6)
52 }
53 }
54 json.NewEncoder(os.Stdout).Encode(map[string]any{"accepted": err == nil, "closed": closed, "input_bytes": len(raw)})
55 clear(raw)
56 os.Exit(0)
57}
58
59func TestStartupTransportInSubprocess(t *testing.T) {
60 image, e := os.ReadFile("/proc/self/exe")
61 if e != nil {
62 t.Fatal(e)
63 }
64 imageSum := sha256.Sum256(image)
65 for _, mode := range []string{"valid", "runtime-default", "context", "context-file", "context-overflow", "input", "input-file", "input-overflow", "selector", "artifact-hash", "artifact-identity", "artifact-flags", "artifact-path", "expired", "checker", "stalled-context", "stalled-input"} {
66 t.Run(mode, func(t *testing.T) {
67 dir := t.TempDir()
68 path := filepath.Join(dir, "image")
69 if e := os.WriteFile(path, image, 0700); e != nil {
70 t.Fatal(e)
71 }
72 database := filepath.Join(dir, "home.db")
73 marker := []byte("untouched database sentinel")
74 if e := os.WriteFile(database, marker, 0600); e != nil {
75 t.Fatal(e)
76 }
77 input := []byte(`{"private":"` + strings.Repeat("x", 60000) + `"}`)
78 inputSum := sha256.Sum256(input)
79 id := base64.RawURLEncoding.EncodeToString(make([]byte, 32))
80 digest := strings.Repeat("a", 64)
81 c := launchctx.LaunchContext{Version: 1, Account: "bds", App: "home", Host: "home.example", RequestID: id, AttemptID: id, RequestDigest: digest, RecordIdentity: digest, ArtifactDigest: hex.EncodeToString(imageSum[:]), EnvironmentEnvelopeDigest: digest, GuardGeneration: 1, InputSHA256: hex.EncodeToString(inputSum[:]), DatabasePath: database, DatabaseDevice: 1, DatabaseInode: 1, ExecutionDeadline: time.Now().Add(10 * time.Second).UTC().Format(time.RFC3339Nano)}
82 selector := database
83 artifactPath, artifactFlags := path, os.O_RDONLY
84 switch mode {
85 case "input":
86 input[20] = 'y'
87 case "input-overflow":
88 input = bytes.Repeat([]byte("x"), inputLimit+1)
89 case "selector":
90 selector += ".other"
91 case "artifact-hash":
92 c.ArtifactDigest = digest
93 case "artifact-path":
94 artifactFlags = unix.O_PATH
95 case "artifact-identity", "artifact-flags":
96 artifactPath = filepath.Join(dir, "other")
97 if e := os.WriteFile(artifactPath, image, 0600); e != nil {
98 t.Fatal(e)
99 }
100 if mode == "artifact-flags" {
101 artifactFlags = os.O_RDWR
102 }
103 case "expired":
104 c.ExecutionDeadline = time.Now().Add(-time.Second).UTC().Format(time.RFC3339Nano)
105 case "checker":
106 c.Version = 2
107 c.ExecutionKind = "compatibility-check"
108 c.OperationID = base64.RawURLEncoding.EncodeToString(bytes.Repeat([]byte{1}, 32))
109 c.ExpectedStateSHA256 = digest
110 c.ClosedCheckpoint = &launchctx.ClosedCheckpoint{Version: 1, PreparationID: id, CompletedAt: time.Now().UTC().Format(time.RFC3339Nano), MainLength: 1, MainSHA256: digest}
111 c.ClosedCheckpoint.EvidenceSHA256, e = c.CheckpointDigest()
112 if e != nil {
113 t.Fatal(e)
114 }
115 }
116 raw, e := launchctx.EncodeLaunch(c)
117 if e != nil {
118 t.Fatal(e)
119 }
120 switch mode {
121 case "context":
122 raw = []byte(`{"version":1}`)
123 case "context-overflow":
124 raw = bytes.Repeat([]byte(" "), launchctx.MaxLaunchBytes+1)
125 }
126 pin, e := os.OpenFile(artifactPath, artifactFlags, 0)
127 if e != nil {
128 t.Fatal(e)
129 }
130 defer pin.Close()
131 inR, inW, e := os.Pipe()
132 if e != nil {
133 t.Fatal(e)
134 }
135 defer inR.Close()
136 defer inW.Close()
137 contextR, contextW, e := os.Pipe()
138 if e != nil {
139 t.Fatal(e)
140 }
141 defer contextR.Close()
142 defer contextW.Close()
143 ctx, cancel := context.WithTimeout(t.Context(), 8*time.Second)
144 defer cancel()
145 cmd := exec.CommandContext(ctx, path, "-test.run=^TestStartupChild$")
146 cmd.Dir = "/"
147 // Keep the fixture's descriptor inventory independent of runtime CPU detection.
148 cmd.Env = []string{"HOME_STARTUP_FIXTURE=1", "HOME_STARTUP_DATABASE=" + selector, "GODEBUG=containermaxprocs=0,updatemaxprocs=0"}
149 if mode == "runtime-default" {
150 cmd.Env = cmd.Env[:2]
151 }
152 cmd.Stdin = inR
153 cmd.ExtraFiles = []*os.File{contextR, pin}
154 if mode == "input-file" || mode == "context-file" {
155 content := input
156 if mode == "context-file" {
157 content = raw
158 }
159 filePath := filepath.Join(dir, "invalid-transport")
160 if e := os.WriteFile(filePath, content, 0600); e != nil {
161 t.Fatal(e)
162 }
163 file, e := os.Open(filePath)
164 if e != nil {
165 t.Fatal(e)
166 }
167 defer file.Close()
168 if mode == "input-file" {
169 cmd.Stdin = file
170 } else {
171 cmd.ExtraFiles[0] = file
172 }
173 }
174 var output bytes.Buffer
175 cmd.Stdout = &output
176 if e := cmd.Start(); e != nil {
177 t.Fatal(e)
178 }
179 inR.Close()
180 contextR.Close()
181 writes := make(chan struct{}, 2)
182 for _, item := range []struct {
183 fd *os.File
184 raw []byte
185 stall bool
186 }{{inW, input, mode == "stalled-input"}, {contextW, raw, mode == "stalled-context"}} {
187 go func() {
188 if !item.stall {
189 io.Copy(item.fd, bytes.NewReader(item.raw))
190 item.fd.Close()
191 }
192 writes <- struct{}{}
193 }()
194 }
195 if e := cmd.Wait(); e != nil {
196 t.Fatal("startup child failed", e)
197 }
198 <-writes
199 <-writes
200 var result struct {
201 Accepted bool `json:"accepted"`
202 Closed bool `json:"closed"`
203 InputBytes int `json:"input_bytes"`
204 }
205 wantAccepted := mode == "valid" || mode == "runtime-default"
206 if json.Unmarshal(output.Bytes(), &result) != nil || !result.Closed || result.Accepted != wantAccepted {
207 t.Fatal("wrong startup result", output.String())
208 }
209 if wantAccepted && result.InputBytes != len(input) {
210 t.Fatal("private input changed")
211 }
212 unchanged, e := os.ReadFile(database)
213 if e != nil || !bytes.Equal(unchanged, marker) {
214 t.Fatal("startup touched database")
215 }
216 })
217 }
218}
219