| 1 | //go:build linux && rootrunner |
| 2 | |
| 3 | package host |
| 4 | |
| 5 | import ( |
| 6 | "context" |
| 7 | "errors" |
| 8 | "fmt" |
| 9 | "os" |
| 10 | "os/exec" |
| 11 | "path/filepath" |
| 12 | "strings" |
| 13 | "syscall" |
| 14 | "testing" |
| 15 | "time" |
| 16 | |
| 17 | "github.com/carlosframework/platform/internal/activator" |
| 18 | "github.com/carlosframework/platform/internal/registry" |
| 19 | "github.com/carlosframework/platform/internal/release" |
| 20 | ) |
| 21 | |
| 22 | func TestPlan14GenerationBackendInstalledRootRunner(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 _, name := range []string{"data", "run", "opt/live", "opt/versions/v1"} { |
| 29 | if err := os.MkdirAll(filepath.Join(dir, name), 0755); err != nil { |
| 30 | t.Fatal(err) |
| 31 | } |
| 32 | } |
| 33 | for _, name := range []string{"data", "run"} { |
| 34 | if err := os.Chown(filepath.Join(dir, name), 65534, 65534); err != nil { |
| 35 | t.Fatal(err) |
| 36 | } |
| 37 | } |
| 38 | probe := filepath.Join(dir, "opt/write-probe") |
| 39 | if err := os.WriteFile(probe, nil, 0600); err != nil { |
| 40 | t.Fatal(err) |
| 41 | } |
| 42 | if err := os.Chown(probe, 65534, 65534); err != nil { |
| 43 | t.Fatal(err) |
| 44 | } |
| 45 | binary, err := os.Executable() |
| 46 | if err != nil { |
| 47 | t.Fatal(err) |
| 48 | } |
| 49 | raw, err := os.ReadFile(binary) |
| 50 | if err != nil { |
| 51 | t.Fatal(err) |
| 52 | } |
| 53 | installed := filepath.Join(dir, "runner.test") |
| 54 | if err := os.WriteFile(installed, raw, 0755); err != nil { |
| 55 | t.Fatal(err) |
| 56 | } |
| 57 | artifact := filepath.Join(dir, "opt/versions/v1/app") |
| 58 | script := fmt.Sprintf("#!/bin/sh\nprintf 'executed' > '%s'\n", filepath.Join(dir, "data/executed")) |
| 59 | if err := os.WriteFile(artifact, []byte(script), 0755); err != nil { |
| 60 | t.Fatal(err) |
| 61 | } |
| 62 | if err := os.Symlink(artifact, filepath.Join(dir, "opt/live/worker.example")); err != nil { |
| 63 | t.Fatal(err) |
| 64 | } |
| 65 | raw, err = unitsFS.ReadFile("units/carlos-edge.service") |
| 66 | if err != nil { |
| 67 | t.Fatal(err) |
| 68 | } |
| 69 | ctl := func(ctx context.Context, args ...string) (string, error) { |
| 70 | ctx, cancel := context.WithTimeout(ctx, 45*time.Second) |
| 71 | defer cancel() |
| 72 | out, err := exec.CommandContext(ctx, "systemctl", args...).CombinedOutput() |
| 73 | return strings.TrimSpace(string(out)), err |
| 74 | } |
| 75 | for _, mode := range []string{"off", "on"} { |
| 76 | t.Run(mode, func(t *testing.T) { |
| 77 | unit := filepath.Base(dir) + "-backend-" + mode + ".service" |
| 78 | var lines []string |
| 79 | for _, line := range strings.Split(string(raw), "\n") { |
| 80 | key, _, _ := strings.Cut(line, "=") |
| 81 | switch key { |
| 82 | case "Requires", "After", "Wants", "ExecStart", "ExecReload", "EnvironmentFile", "User", "Group", "Restart", "Slice": |
| 83 | continue |
| 84 | case "ReadWritePaths": |
| 85 | line = "ReadWritePaths=" + filepath.Join(dir, "data") + " " + filepath.Join(dir, "run") |
| 86 | } |
| 87 | lines = append(lines, line) |
| 88 | } |
| 89 | // Test plumbing replaces identity, dependencies and entrypoint only. |
| 90 | // The generated agent's hardening and delegation remain in force. |
| 91 | content := strings.Join(lines, "\n") + fmt.Sprintf("\n[Service]\nUser=65534\nGroup=65534\nType=oneshot\nRestart=no\nTimeoutStartSec=45s\nExecStart=%s -test.run=^TestPlan14GenerationBackendUnitPayload$ -test.v -test.timeout=30s\nEnvironment=PLAN14_E40_UNIT=%s PLAN14_E40_MODE=%s\nIPAddressDeny=any\n", installed, dir, mode) |
| 92 | path := filepath.Join("/run/systemd/system", unit) |
| 93 | file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644) |
| 94 | if err != nil { |
| 95 | t.Fatal(err) |
| 96 | } |
| 97 | t.Cleanup(func() { |
| 98 | if out, err := ctl(context.Background(), "stop", unit); err != nil { |
| 99 | t.Errorf("stop: %v %s", err, out) |
| 100 | } |
| 101 | _, _ = ctl(context.Background(), "reset-failed", unit) |
| 102 | if err := os.Remove(path); err != nil { |
| 103 | t.Error(err) |
| 104 | } |
| 105 | if out, err := ctl(context.Background(), "daemon-reload"); err != nil { |
| 106 | t.Errorf("reload: %v %s", err, out) |
| 107 | } |
| 108 | }) |
| 109 | _, writeErr := file.WriteString(content) |
| 110 | if err := errors.Join(writeErr, file.Close()); err != nil { |
| 111 | t.Fatal(err) |
| 112 | } |
| 113 | for _, args := range [][]string{{"daemon-reload"}, {"start", unit}} { |
| 114 | if out, err := ctl(t.Context(), args...); err != nil { |
| 115 | logs, _ := exec.CommandContext(t.Context(), "journalctl", "-u", unit, "--no-pager", "-n", "60").CombinedOutput() |
| 116 | t.Fatalf("systemctl %v: %v %s\n%s", args, err, out, logs) |
| 117 | } |
| 118 | } |
| 119 | for prop, want := range map[string]string{"Result": "success", "ExecMainStatus": "0"} { |
| 120 | if got, err := ctl(t.Context(), "show", unit, "--property="+prop, "--value"); err != nil || got != want { |
| 121 | t.Fatalf("%s: %q %v", prop, got, err) |
| 122 | } |
| 123 | } |
| 124 | }) |
| 125 | } |
| 126 | } |
| 127 | |
| 128 | func TestPlan14GenerationBackendUnitPayload(t *testing.T) { |
| 129 | dir, mode := os.Getenv("PLAN14_E40_UNIT"), os.Getenv("PLAN14_E40_MODE") |
| 130 | if dir == "" || (mode != "on" && mode != "off") || os.Geteuid() != 65534 { |
| 131 | t.Fatal("invoke through TestPlan14GenerationBackendInstalledRootRunner") |
| 132 | } |
| 133 | opt := filepath.Join(dir, "opt") |
| 134 | if err := os.WriteFile(filepath.Join(opt, "write-probe"), nil, 0600); !errors.Is(err, syscall.EROFS) { |
| 135 | t.Fatalf("installed agent version namespace is writable: %v", err) |
| 136 | } |
| 137 | b := &activator.ExecBackend{OptDir: opt, DataDir: filepath.Join(dir, "data")} |
| 138 | b.Cfg.HostWrites.Enabled = mode == "on" |
| 139 | rt := registry.Route{Host: "worker.example", Kind: "worker", Args: `["run"]`} |
| 140 | id, err := release.ResolveLive(opt, rt.Host) |
| 141 | if err != nil { |
| 142 | t.Fatal(err) |
| 143 | } |
| 144 | marker := filepath.Join(dir, "data/executed") |
| 145 | for _, pinned := range []bool{false, true} { |
| 146 | if err := os.Remove(marker); err != nil && !errors.Is(err, os.ErrNotExist) { |
| 147 | t.Fatal(err) |
| 148 | } |
| 149 | var onto *release.StagedIdentity |
| 150 | if pinned { |
| 151 | onto = &id |
| 152 | } |
| 153 | p, _, err := b.StartWorkerStaged(t.Context(), rt, nil, onto) |
| 154 | if p != nil { |
| 155 | t.Cleanup(func() { |
| 156 | if err := p.Stop(context.Background()); err != nil { |
| 157 | t.Error(err) |
| 158 | } |
| 159 | }) |
| 160 | deadline := time.Now().Add(5 * time.Second) |
| 161 | for !p.Exited() && time.Now().Before(deadline) { |
| 162 | time.Sleep(10 * time.Millisecond) |
| 163 | } |
| 164 | if !p.Exited() { |
| 165 | t.Fatal("fixture worker did not exit") |
| 166 | } |
| 167 | } |
| 168 | if mode == "on" { |
| 169 | if !errors.Is(err, activator.ErrClaimIsolationUnavailable) || p != nil { |
| 170 | t.Fatalf("claimed raw worker: %v %v", p, err) |
| 171 | } |
| 172 | if _, err := os.Stat(marker); !errors.Is(err, os.ErrNotExist) { |
| 173 | t.Fatalf("claimed worker executed: %v", err) |
| 174 | } |
| 175 | } else { |
| 176 | if err != nil || p == nil { |
| 177 | t.Fatalf("legacy raw worker: %v %v", p, err) |
| 178 | } |
| 179 | if raw, err := os.ReadFile(marker); err != nil || string(raw) != "executed" { |
| 180 | t.Fatalf("legacy execution marker: %q %v", raw, err) |
| 181 | } |
| 182 | } |
| 183 | } |
| 184 | } |
| 185 | |