daemon: split owner daemon from root helper
Move the supported systemd path to two services: an owner-user bangerd for orchestration and a narrow root helper for bridge/tap, NAT/resolver, dm/loop, and Firecracker ownership. This removes repeated sudo from daily vm and image flows without leaving the general daemon running as root. Add install metadata, system install/status/restart/uninstall commands, and a system-owned runtime layout. Keep user SSH/config material in the owner home, lock file_sync to the owner home, and move daemon known_hosts handling out of the old root-owned control path. Route privileged lifecycle steps through typed privilegedOps calls, harden the two systemd units, and rewrite smoke plus docs around the supported service model. Verified with make build, make test, make lint, and make smoke on the supported systemd host path.
This commit is contained in:
parent
3edd7c6de7
commit
59e48e830b
53 changed files with 3239 additions and 726 deletions
|
|
@ -427,8 +427,8 @@ func TestHealthVMReturnsHealthyForRunningGuest(t *testing.T) {
|
|||
runner := &scriptedRunner{
|
||||
t: t,
|
||||
steps: []runnerStep{
|
||||
sudoStep("", nil, "chown", fmt.Sprintf("%d:%d", os.Getuid(), os.Getgid()), vsockSock),
|
||||
sudoStep("", nil, "chmod", "600", vsockSock),
|
||||
sudoStep("", nil, "chown", fmt.Sprintf("%d:%d", os.Getuid(), os.Getgid()), vsockSock),
|
||||
},
|
||||
}
|
||||
d := &Daemon{store: db, runner: runner}
|
||||
|
|
@ -491,8 +491,8 @@ func TestPingVMAliasReturnsAliveForHealthyVM(t *testing.T) {
|
|||
runner := &scriptedRunner{
|
||||
t: t,
|
||||
steps: []runnerStep{
|
||||
sudoStep("", nil, "chown", fmt.Sprintf("%d:%d", os.Getuid(), os.Getgid()), vsockSock),
|
||||
sudoStep("", nil, "chmod", "600", vsockSock),
|
||||
sudoStep("", nil, "chown", fmt.Sprintf("%d:%d", os.Getuid(), os.Getgid()), vsockSock),
|
||||
},
|
||||
}
|
||||
d := &Daemon{store: db, runner: runner}
|
||||
|
|
@ -691,8 +691,8 @@ func TestPortsVMReturnsEnrichedPortsAndWebSchemes(t *testing.T) {
|
|||
runner := &scriptedRunner{
|
||||
t: t,
|
||||
steps: []runnerStep{
|
||||
sudoStep("", nil, "chown", fmt.Sprintf("%d:%d", os.Getuid(), os.Getgid()), vsockSock),
|
||||
sudoStep("", nil, "chmod", "600", vsockSock),
|
||||
sudoStep("", nil, "chown", fmt.Sprintf("%d:%d", os.Getuid(), os.Getgid()), vsockSock),
|
||||
},
|
||||
}
|
||||
d := &Daemon{store: db, runner: runner}
|
||||
|
|
@ -1148,13 +1148,92 @@ func TestRunFileSyncCopiesDirectoryRecursively(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRunFileSyncAllowsTopLevelSymlinkWithinHome(t *testing.T) {
|
||||
homeDir := t.TempDir()
|
||||
t.Setenv("HOME", homeDir)
|
||||
|
||||
targetDir := filepath.Join(homeDir, ".config", "gh")
|
||||
if err := os.MkdirAll(targetDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
targetPath := filepath.Join(targetDir, "hosts.yml")
|
||||
if err := os.WriteFile(targetPath, []byte("github.com"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
linkPath := filepath.Join(homeDir, "gh-hosts.yml")
|
||||
if err := os.Symlink(targetPath, linkPath); err != nil {
|
||||
t.Skipf("symlink unsupported on this filesystem: %v", err)
|
||||
}
|
||||
|
||||
workDisk := t.TempDir()
|
||||
d := &Daemon{
|
||||
runner: &filesystemRunner{t: t},
|
||||
config: model.DaemonConfig{
|
||||
HostHomeDir: homeDir,
|
||||
FileSync: []model.FileSyncEntry{
|
||||
{Host: "~/gh-hosts.yml", Guest: "~/.config/gh/hosts.yml"},
|
||||
},
|
||||
},
|
||||
}
|
||||
wireServices(d)
|
||||
vm := testVM("sync-top-level-symlink-ok", "image", "172.16.0.77")
|
||||
vm.Runtime.WorkDiskPath = workDisk
|
||||
if err := d.ws.runFileSync(context.Background(), &vm); err != nil {
|
||||
t.Fatalf("runFileSync: %v", err)
|
||||
}
|
||||
|
||||
got, err := os.ReadFile(filepath.Join(workDisk, ".config", "gh", "hosts.yml"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(got) != "github.com" {
|
||||
t.Fatalf("guest file = %q, want github.com", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunFileSyncRejectsTopLevelSymlinkOutsideHome(t *testing.T) {
|
||||
homeDir := t.TempDir()
|
||||
t.Setenv("HOME", homeDir)
|
||||
|
||||
outsideDir := t.TempDir()
|
||||
targetPath := filepath.Join(outsideDir, "secret.txt")
|
||||
if err := os.WriteFile(targetPath, []byte("must-stay-outside"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
linkPath := filepath.Join(homeDir, "secret-link")
|
||||
if err := os.Symlink(targetPath, linkPath); err != nil {
|
||||
t.Skipf("symlink unsupported on this filesystem: %v", err)
|
||||
}
|
||||
|
||||
workDisk := t.TempDir()
|
||||
d := &Daemon{
|
||||
runner: &filesystemRunner{t: t},
|
||||
config: model.DaemonConfig{
|
||||
HostHomeDir: homeDir,
|
||||
FileSync: []model.FileSyncEntry{
|
||||
{Host: "~/secret-link", Guest: "~/secret.txt"},
|
||||
},
|
||||
},
|
||||
}
|
||||
wireServices(d)
|
||||
vm := testVM("sync-top-level-symlink-reject", "image", "172.16.0.78")
|
||||
vm.Runtime.WorkDiskPath = workDisk
|
||||
err := d.ws.runFileSync(context.Background(), &vm)
|
||||
if err == nil || !strings.Contains(err.Error(), "owner home") {
|
||||
t.Fatalf("runFileSync error = %v, want owner-home rejection", err)
|
||||
}
|
||||
if _, statErr := os.Stat(filepath.Join(workDisk, "secret.txt")); !os.IsNotExist(statErr) {
|
||||
t.Fatalf("guest file exists after rejected sync (stat err = %v)", statErr)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunFileSyncSkipsNestedSymlinks pins the anti-sprawl contract:
|
||||
// a symlink INSIDE a synced directory is not followed, even if the
|
||||
// target holds real files. Without this, a user syncing ~/.aws with
|
||||
// a ~/.aws/session -> ~/other-creds symlink would copy the unrelated
|
||||
// creds into the guest. Top-level entries (the path the user
|
||||
// literally named) still follow, because they explicitly asked for
|
||||
// that path.
|
||||
// creds into the guest. Top-level entries are resolved separately:
|
||||
// they may still follow, but only when the real target stays under
|
||||
// the configured owner home.
|
||||
func TestRunFileSyncSkipsNestedSymlinks(t *testing.T) {
|
||||
homeDir := t.TempDir()
|
||||
t.Setenv("HOME", homeDir)
|
||||
|
|
@ -1543,8 +1622,8 @@ func TestStopVMFallsBackToForcedCleanupAfterGracefulTimeout(t *testing.T) {
|
|||
scriptedRunner: &scriptedRunner{
|
||||
t: t,
|
||||
steps: []runnerStep{
|
||||
sudoStep("", nil, "chown", fmt.Sprintf("%d:%d", os.Getuid(), os.Getgid()), apiSock),
|
||||
sudoStep("", nil, "chmod", "600", apiSock),
|
||||
sudoStep("", nil, "chown", fmt.Sprintf("%d:%d", os.Getuid(), os.Getgid()), apiSock),
|
||||
{call: runnerCall{name: "pgrep", args: []string{"-n", "-f", apiSock}}, out: []byte(strconv.Itoa(fake.Process.Pid) + "\n")},
|
||||
sudoStep("", nil, "kill", "-KILL", strconv.Itoa(fake.Process.Pid)),
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue