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
2
3package host
4
5import (
6 "crypto/sha256"
7 "encoding/hex"
8 "errors"
9 "os"
10 "os/exec"
11 "path/filepath"
12 "strings"
13 "testing"
14
15 "github.com/carlosframework/platform/internal/hostbox"
16 "github.com/carlosframework/platform/internal/instancectl"
17 "golang.org/x/sys/unix"
18)
19
20func plan11LaunchFileID(t *testing.T, path string) hostbox.LaunchFile {
21 t.Helper()
22 var st unix.Stat_t
23 if err := unix.Stat(path, &st); err != nil {
24 t.Fatal(err)
25 }
26 return hostbox.LaunchFile{Device: uint64(st.Dev), Inode: st.Ino}
27}
28
29func TestClaimLaunchKernelVerifiesAnImmutableBuild(t *testing.T) {
30 opt := filepath.Join(t.TempDir(), "opt")
31 build := filepath.Join(opt, "versions", "v1", "app")
32 if err := os.MkdirAll(filepath.Dir(build), 0o755); err != nil {
33 t.Fatal(err)
34 }
35 if err := os.WriteFile(build, []byte("app"), 0o555); err != nil {
36 t.Fatal(err)
37 }
38 k := linuxClaimLaunchKernel{OptRoot: opt, CgroupRoot: "/sys/fs/cgroup", ProcRoot: "/proc", Owner: uint32(os.Getuid())}
39 want := plan11LaunchFileID(t, build)
40 if err := k.Verify(build, want); err != nil {
41 t.Fatal(err)
42 }
43 if err := k.Verify(build, hostbox.LaunchFile{Device: want.Device, Inode: want.Inode + 1}); err == nil {
44 t.Fatal("other file identity accepted")
45 }
46 if err := (linuxClaimLaunchKernel{OptRoot: opt, Owner: uint32(os.Getuid()) + 1}).Verify(build, want); err == nil {
47 t.Fatal("build not owned by root accepted")
48 }
49 link := filepath.Join(opt, "versions", "current")
50 if err := os.Symlink("v1", link); err != nil {
51 t.Fatal(err)
52 }
53 if err := k.Verify(filepath.Join(link, "app"), want); err == nil {
54 t.Fatal("symlinked component accepted")
55 }
56 live := filepath.Join(opt, "live", "shop.example")
57 if err := os.MkdirAll(filepath.Dir(live), 0o755); err != nil {
58 t.Fatal(err)
59 }
60 if err := os.WriteFile(live, []byte("app"), 0o555); err != nil {
61 t.Fatal(err)
62 }
63 if err := k.Verify(live, plan11LaunchFileID(t, live)); err == nil {
64 t.Fatal("retargetable live tree accepted")
65 }
66 if err := k.Verify(filepath.Join(t.TempDir(), "app"), want); err == nil {
67 t.Fatal("build outside the release tree accepted")
68 }
69 if err := os.Chmod(build, 0o575); err != nil {
70 t.Fatal(err)
71 }
72 if err := k.Verify(build, want); err == nil {
73 t.Fatal("group-writable build accepted")
74 }
75 if err := os.Chmod(build, 0o555); err != nil {
76 t.Fatal(err)
77 }
78 if err := os.Chmod(filepath.Dir(build), 0o777); err != nil {
79 t.Fatal(err)
80 }
81 if err := k.Verify(build, want); err == nil {
82 t.Fatal("build in a writable directory accepted")
83 }
84 if err := os.Chmod(filepath.Dir(build), 0o755); err != nil {
85 t.Fatal(err)
86 }
87 if err := k.Verify(strings.Replace(build, "/v1/", "/v1/../v1/", 1), want); err == nil {
88 t.Fatal("unclean path accepted")
89 }
90}
91
92func TestClaimLaunchKernelObservesTheStartedProcess(t *testing.T) {
93 raw, err := os.ReadFile("/proc/self/cgroup")
94 if err != nil || !strings.HasPrefix(string(raw), "0::/") {
95 t.Skip("no unified cgroup membership")
96 }
97 rel := strings.TrimSpace(strings.TrimPrefix(strings.SplitN(string(raw), "\n", 2)[0], "0::/"))
98 if _, err := os.Stat(filepath.Join("/sys/fs/cgroup", rel)); err != nil {
99 t.Skip("cgroup tree not visible:", err)
100 }
101 bound := "/bin/sleep"
102 child := exec.Command(bound, "30")
103 if err := child.Start(); err != nil {
104 t.Fatal(err)
105 }
106 t.Cleanup(func() { _ = child.Process.Kill(); _ = child.Wait() })
107 k := linuxClaimLaunchKernel{CgroupRoot: "/sys/fs/cgroup", ProcRoot: "/proc", BoundApp: strings.TrimPrefix(bound, "/")}
108 started := instancectl.ClaimUnitStarted{Unit: "u", InvocationID: "i", ControlGroup: rel, MainPID: os.Getpid()}
109 got, err := k.Observe(started)
110 if err != nil {
111 t.Fatal(err)
112 }
113 if got.ProcessStart != procSelfStart(t) || got.Group != plan11LaunchFileID(t, filepath.Join("/sys/fs/cgroup", rel)) || got.Executable != plan11LaunchFileID(t, bound) || got.Running != got.Executable || got.TenantPID != child.Process.Pid || got.TenantStart == "" {
114 t.Fatalf("observed=%+v", got)
115 }
116 started.ControlGroup = "carlos.slice/elsewhere.service"
117 if _, err := k.Observe(started); err == nil {
118 t.Fatal("process outside the unit group accepted")
119 }
120}
121
122func TestPlan11Rev2RootHashesThePinnedExecutable(t *testing.T) {
123 root := t.TempDir()
124 path := filepath.Join(root, "versions", "v1", "app")
125 if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
126 t.Fatal(err)
127 }
128 if err := os.Chmod(root, 0755); err != nil {
129 t.Fatal(err)
130 }
131 if err := os.WriteFile(path, []byte("app"), 0555); err != nil {
132 t.Fatal(err)
133 }
134 k := linuxClaimLaunchKernel{OptRoot: root, Owner: uint32(os.Getuid())}
135 sum := sha256.Sum256([]byte("app"))
136 digest := hex.EncodeToString(sum[:])
137 id := plan11LaunchFileID(t, path)
138 if os.Getuid() != 0 {
139 // The hash is taken only from a root-owned executable (#267), so an
140 // unprivileged run can prove the refusal but not the acceptance.
141 if err := k.VerifyDigest(t.Context(), path, id, digest); !errors.Is(err, hostbox.ErrChanged) {
142 t.Fatalf("non-root-owned executable: err=%v, want ErrChanged", err)
143 }
144 t.Skip("accepting a pinned executable needs a root-owned fixture")
145 }
146 if err := k.VerifyDigest(t.Context(), path, id, digest); err != nil {
147 t.Fatal(err)
148 }
149 if err := k.VerifyDigest(t.Context(), path, id, strings.Repeat("0", 64)); err == nil {
150 t.Fatal("root accepted forged executable digest")
151 }
152 id.Inode++
153 if err := k.VerifyDigest(t.Context(), path, id, digest); err == nil {
154 t.Fatal("root accepted another inode")
155 }
156}
157