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 unix
2
3package host
4
5import (
6 "errors"
7 "io"
8 "os"
9 "os/exec"
10 "os/user"
11 "path/filepath"
12 "strconv"
13 "testing"
14
15 "github.com/carlosframework/platform/internal/registry"
16)
17
18func TestRegistryChildHelper(t *testing.T) {
19 for i, arg := range os.Args {
20 if arg == "--registry-test-child" {
21 if err := RunRegistryChild(os.Args[i+1:]); err != nil {
22 os.Exit(1)
23 }
24 os.Exit(0)
25 }
26 }
27}
28
29func TestRegistryChildDropsCredentials(t *testing.T) {
30 if os.Geteuid() != 0 {
31 t.Skip("requires root to set the child uid/gid and clear supplementary groups")
32 }
33 t.Setenv("CARLOS_HOST_CLAIM", "true")
34 service, err := user.Lookup(serviceUser)
35 if err != nil {
36 t.Skip("requires the carlos service account")
37 }
38 uid, err := strconv.Atoi(service.Uid)
39 if err != nil || uid <= 0 {
40 t.Skip("requires a nonzero service uid")
41 }
42 gid, err := strconv.Atoi(service.Gid)
43 if err != nil {
44 t.Fatal(err)
45 }
46 dir, err := os.MkdirTemp(os.TempDir(), "registry-child-")
47 if err != nil {
48 t.Fatal(err)
49 }
50 t.Cleanup(func() { os.RemoveAll(dir) })
51 if err := os.Chmod(dir, 0755); err != nil {
52 t.Fatal(err)
53 }
54 executable, err := os.Executable()
55 if err != nil {
56 t.Fatal(err)
57 }
58 source, err := os.Open(executable)
59 if err != nil {
60 t.Fatal(err)
61 }
62 defer source.Close()
63 bin := filepath.Join(dir, "test-worker")
64 dest, err := os.OpenFile(bin, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0755)
65 if err != nil {
66 t.Fatal(err)
67 }
68 _, err = io.Copy(dest, source)
69 closeErr := dest.Close()
70 if err != nil {
71 t.Fatal(err)
72 }
73 if closeErr != nil {
74 t.Fatal(closeErr)
75 }
76 data := filepath.Join(dir, "data")
77 if err := os.Mkdir(data, 0700); err != nil {
78 t.Fatal(err)
79 }
80 if err := os.Chown(data, uid, gid); err != nil {
81 t.Fatal(err)
82 }
83 path := filepath.Join(data, "registry.db")
84 legacy, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644)
85 if err != nil {
86 t.Fatal(err)
87 }
88 if err := legacy.Close(); err != nil {
89 t.Fatal(err)
90 }
91 changed, err := ownRegistryFile(path, uid, gid)
92 if err != nil || !changed {
93 t.Fatalf("repair root-owned registry: changed=%v, err=%v", changed, err)
94 }
95 cmd := exec.Command(bin, "-test.run=^TestRegistryChildHelper$", "--", "--registry-test-child", path, service.Uid, service.Gid)
96 r, err := startRegistryChild(cmd, path, uid, gid)
97 if err != nil {
98 t.Fatal(err)
99 }
100 t.Cleanup(func() { r.Close() })
101 if err := r.Upsert(registry.Route{Host: "dropped", Kind: "instance"}); err != nil {
102 r.Close()
103 t.Fatal(err)
104 }
105 owner := registry.ClaimOwnership{ClaimIncarnation: "inc", ClaimStamp: 1, ClaimOwner: "owner", ClaimRowID: "row"}
106 row, err := r.Get("dropped")
107 if err != nil {
108 t.Fatal(err)
109 }
110 if err := r.AdoptClaimRoute(t.Context(), *row, owner); err != nil {
111 t.Fatal(err)
112 }
113 timer := registry.Timer{Host: "dropped", Name: "tick", Kind: "cron", Spec: "* * * * *"}
114 if err := r.UpsertTimer(timer); err != nil {
115 t.Fatal(err)
116 }
117 saved, err := r.GetTimer("dropped", "tick")
118 if err != nil || saved == nil || saved.Registration == "" || saved.ClaimOwner != owner.ClaimOwner {
119 t.Fatalf("worker lost claim mode: %+v, %v", saved, err)
120 }
121 timer.ClaimOwnership = owner
122 timer.ClaimOwner = "other"
123 if err := r.UpsertTimer(timer); !errors.Is(err, registry.ErrClaimOwnership) {
124 t.Fatalf("worker accepted conflicting claim: %v", err)
125 }
126 for _, name := range []string{path, path + "-wal", path + "-shm"} {
127 info, err := os.Stat(name)
128 if err != nil {
129 t.Fatal(err)
130 }
131 gotUID, gotGID, ok := fileOwner(info)
132 if !ok || gotUID != uid || gotGID != gid {
133 t.Fatalf("%s owner = %d:%d, want %d:%d", name, gotUID, gotGID, uid, gid)
134 }
135 }
136 if err := r.Close(); err != nil {
137 t.Fatal(err)
138 }
139
140}
141
142func TestRegistryChildRefusesRootCredentials(t *testing.T) {
143 if _, err := openRegistryChild(filepath.Join(t.TempDir(), "registry.db"), 0, 0); err == nil {
144 t.Fatal("root worker accepted")
145 }
146}
147
148func TestRegistryChildCommandEnvironment(t *testing.T) {
149 t.Setenv("CARLOS_HOST_CLAIM", "true")
150 t.Setenv("AWS_SECRET_ACCESS_KEY", "must-not-reach-worker")
151 cmd := exec.Command(filepath.Join(t.TempDir(), "missing-executable"))
152 if _, err := startRegistryChild(cmd, "unused", 65534, 65534); err == nil {
153 t.Fatal("missing worker started")
154 }
155 if len(cmd.Env) != 1 || cmd.Env[0] != "CARLOS_HOST_CLAIM=true" {
156 t.Fatalf("worker environment = %q", cmd.Env)
157 }
158 if cmd.Dir != "/" || cmd.SysProcAttr != nil {
159 t.Fatalf("unsafe worker command: %+v", cmd)
160 }
161}
162