//go:build unix package host import ( "errors" "io" "os" "os/exec" "os/user" "path/filepath" "strconv" "testing" "github.com/carlosframework/platform/internal/registry" ) func TestRegistryChildHelper(t *testing.T) { for i, arg := range os.Args { if arg == "--registry-test-child" { if err := RunRegistryChild(os.Args[i+1:]); err != nil { os.Exit(1) } os.Exit(0) } } } func TestRegistryChildDropsCredentials(t *testing.T) { if os.Geteuid() != 0 { t.Skip("requires root to set the child uid/gid and clear supplementary groups") } t.Setenv("CARLOS_HOST_CLAIM", "true") service, err := user.Lookup(serviceUser) if err != nil { t.Skip("requires the carlos service account") } uid, err := strconv.Atoi(service.Uid) if err != nil || uid <= 0 { t.Skip("requires a nonzero service uid") } gid, err := strconv.Atoi(service.Gid) if err != nil { t.Fatal(err) } dir, err := os.MkdirTemp(os.TempDir(), "registry-child-") if err != nil { t.Fatal(err) } t.Cleanup(func() { os.RemoveAll(dir) }) if err := os.Chmod(dir, 0755); err != nil { t.Fatal(err) } executable, err := os.Executable() if err != nil { t.Fatal(err) } source, err := os.Open(executable) if err != nil { t.Fatal(err) } defer source.Close() bin := filepath.Join(dir, "test-worker") dest, err := os.OpenFile(bin, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0755) if err != nil { t.Fatal(err) } _, err = io.Copy(dest, source) closeErr := dest.Close() if err != nil { t.Fatal(err) } if closeErr != nil { t.Fatal(closeErr) } data := filepath.Join(dir, "data") if err := os.Mkdir(data, 0700); err != nil { t.Fatal(err) } if err := os.Chown(data, uid, gid); err != nil { t.Fatal(err) } path := filepath.Join(data, "registry.db") legacy, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644) if err != nil { t.Fatal(err) } if err := legacy.Close(); err != nil { t.Fatal(err) } changed, err := ownRegistryFile(path, uid, gid) if err != nil || !changed { t.Fatalf("repair root-owned registry: changed=%v, err=%v", changed, err) } cmd := exec.Command(bin, "-test.run=^TestRegistryChildHelper$", "--", "--registry-test-child", path, service.Uid, service.Gid) r, err := startRegistryChild(cmd, path, uid, gid) if err != nil { t.Fatal(err) } t.Cleanup(func() { r.Close() }) if err := r.Upsert(registry.Route{Host: "dropped", Kind: "instance"}); err != nil { r.Close() t.Fatal(err) } owner := registry.ClaimOwnership{ClaimIncarnation: "inc", ClaimStamp: 1, ClaimOwner: "owner", ClaimRowID: "row"} row, err := r.Get("dropped") if err != nil { t.Fatal(err) } if err := r.AdoptClaimRoute(t.Context(), *row, owner); err != nil { t.Fatal(err) } timer := registry.Timer{Host: "dropped", Name: "tick", Kind: "cron", Spec: "* * * * *"} if err := r.UpsertTimer(timer); err != nil { t.Fatal(err) } saved, err := r.GetTimer("dropped", "tick") if err != nil || saved == nil || saved.Registration == "" || saved.ClaimOwner != owner.ClaimOwner { t.Fatalf("worker lost claim mode: %+v, %v", saved, err) } timer.ClaimOwnership = owner timer.ClaimOwner = "other" if err := r.UpsertTimer(timer); !errors.Is(err, registry.ErrClaimOwnership) { t.Fatalf("worker accepted conflicting claim: %v", err) } for _, name := range []string{path, path + "-wal", path + "-shm"} { info, err := os.Stat(name) if err != nil { t.Fatal(err) } gotUID, gotGID, ok := fileOwner(info) if !ok || gotUID != uid || gotGID != gid { t.Fatalf("%s owner = %d:%d, want %d:%d", name, gotUID, gotGID, uid, gid) } } if err := r.Close(); err != nil { t.Fatal(err) } } func TestRegistryChildRefusesRootCredentials(t *testing.T) { if _, err := openRegistryChild(filepath.Join(t.TempDir(), "registry.db"), 0, 0); err == nil { t.Fatal("root worker accepted") } } func TestRegistryChildCommandEnvironment(t *testing.T) { t.Setenv("CARLOS_HOST_CLAIM", "true") t.Setenv("AWS_SECRET_ACCESS_KEY", "must-not-reach-worker") cmd := exec.Command(filepath.Join(t.TempDir(), "missing-executable")) if _, err := startRegistryChild(cmd, "unused", 65534, 65534); err == nil { t.Fatal("missing worker started") } if len(cmd.Env) != 1 || cmd.Env[0] != "CARLOS_HOST_CLAIM=true" { t.Fatalf("worker environment = %q", cmd.Env) } if cmd.Dir != "/" || cmd.SysProcAttr != nil { t.Fatalf("unsafe worker command: %+v", cmd) } }