Merge benvin/go-launcher
This commit is contained in:
@@ -3,7 +3,7 @@ when:
|
||||
|
||||
steps:
|
||||
- name: lint
|
||||
image: "golangci/golangci-lint:latest"
|
||||
image: "golangci/golangci-lint:v2.15.0"
|
||||
commands:
|
||||
- golangci-lint run ./...
|
||||
backend_options:
|
||||
|
||||
@@ -80,6 +80,11 @@ func allowlistPath() string {
|
||||
return envOr("AGENT_SANDBOX_ALLOWLIST", filepath.Join(configDir(), "allowed-hosts"))
|
||||
}
|
||||
|
||||
// runtimeDir holds the session dirs, including the private API socket's.
|
||||
func runtimeDir() string {
|
||||
return envOr("XDG_RUNTIME_DIR", "/tmp")
|
||||
}
|
||||
|
||||
func warn(format string, a ...any) {
|
||||
fmt.Fprintf(os.Stderr, "agent-sandbox: "+format+"\n", a...)
|
||||
}
|
||||
@@ -115,7 +120,7 @@ func launch(ctx context.Context, args []string) int {
|
||||
}
|
||||
|
||||
session := fmt.Sprintf("agent-sandbox-%d-%d", time.Now().Unix(), os.Getpid())
|
||||
runtimeDir := envOr("XDG_RUNTIME_DIR", "/tmp")
|
||||
runtimeDir := runtimeDir()
|
||||
tmp, err := os.MkdirTemp(runtimeDir, session+".")
|
||||
if err != nil {
|
||||
warn("%v", err)
|
||||
@@ -341,10 +346,7 @@ func allowedPathsFile() string {
|
||||
// sensitiveDirs are host dirs an allowed-paths root may neither contain nor sit under.
|
||||
func sensitiveDirs(home string) []string {
|
||||
dirs := []string{home + "/.ssh", home + "/.gnupg", home + "/.config", filepath.Dir(configDir()),
|
||||
home + "/.local/share/containers"}
|
||||
if v := os.Getenv("XDG_RUNTIME_DIR"); v != "" {
|
||||
dirs = append(dirs, v)
|
||||
}
|
||||
home + "/.local/share/containers", runtimeDir()}
|
||||
if v := os.Getenv("XDG_DATA_HOME"); v != "" {
|
||||
dirs = append(dirs, v+"/containers")
|
||||
}
|
||||
@@ -396,8 +398,8 @@ func allowedRoots(home string) (roots []string, def bool, err error) {
|
||||
}
|
||||
|
||||
// checkLaunchDir allows only launch dirs strictly under an allowed root, never
|
||||
// $HOME itself or anything holding the sandbox's config. Under the default
|
||||
// $HOME root, dot-dirs of $HOME are refused too.
|
||||
// $HOME itself or anything holding the sandbox's config or the API socket.
|
||||
// Under the default $HOME root, dot-dirs of $HOME are refused too.
|
||||
func checkLaunchDir(pwd, allowlist string) error {
|
||||
pwd = realpath(pwd)
|
||||
home := realpath(os.Getenv("HOME"))
|
||||
@@ -413,7 +415,7 @@ func checkLaunchDir(pwd, allowlist string) error {
|
||||
}
|
||||
paths := allowedPathsFile()
|
||||
for _, dir := range []string{configDir(), filepath.Dir(allowlist), filepath.Dir(realpath(allowlist)),
|
||||
filepath.Dir(paths), filepath.Dir(realpath(paths))} {
|
||||
filepath.Dir(paths), filepath.Dir(realpath(paths)), runtimeDir()} {
|
||||
if dir = realpath(dir); inside(dir, pwd) {
|
||||
return fmt.Errorf("refusing to mount %s: it contains %s", pwd, dir)
|
||||
}
|
||||
|
||||
@@ -268,6 +268,20 @@ func TestLaunchRefusals(t *testing.T) {
|
||||
{"allowlist-dir", "home/proj", func(t *testing.T, e *env) {
|
||||
t.Setenv("AGENT_SANDBOX_ALLOWLIST", e.work+"/home/proj/hosts")
|
||||
}},
|
||||
{"runtime-dir", "other/run", func(t *testing.T, e *env) {
|
||||
_ = os.MkdirAll(e.work+"/other/run", 0o755)
|
||||
t.Setenv("XDG_RUNTIME_DIR", e.work+"/other/run")
|
||||
}},
|
||||
{"runtime-parent", "other", func(t *testing.T, e *env) {
|
||||
_ = os.MkdirAll(e.work+"/other/run", 0o755)
|
||||
t.Setenv("XDG_RUNTIME_DIR", e.work+"/other/run")
|
||||
}},
|
||||
{"runtime-symlink", "other", func(t *testing.T, e *env) {
|
||||
_ = os.MkdirAll(e.work+"/other/run", 0o755)
|
||||
_ = os.Symlink(e.work+"/other/run", e.work+"/home/runlink")
|
||||
t.Setenv("XDG_RUNTIME_DIR", e.work+"/home/runlink")
|
||||
}},
|
||||
{"runtime-fallback", "/tmp", func(t *testing.T, e *env) { t.Setenv("XDG_RUNTIME_DIR", "") }},
|
||||
{"colon", "home/a:b", func(t *testing.T, e *env) { _ = os.Mkdir(e.work+"/home/a:b", 0o755) }},
|
||||
{"symlinked-allowlist-target", "home/dotfiles", func(t *testing.T, e *env) {
|
||||
_ = os.MkdirAll(e.work+"/home/dotfiles", 0o755)
|
||||
@@ -321,6 +335,18 @@ func TestLaunchRefusals(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckLaunchDirRuntimeFallback(t *testing.T) {
|
||||
t.Setenv("HOME", "/nonexistent/home")
|
||||
t.Setenv("XDG_CONFIG_HOME", "/nonexistent/cfg")
|
||||
t.Setenv("XDG_RUNTIME_DIR", "")
|
||||
err := checkLaunchDir("/tmp", "/nonexistent/hosts")
|
||||
if err == nil || !strings.Contains(err.Error(), "contains "+realpath("/tmp")) {
|
||||
t.Fatalf("got %v", err)
|
||||
}
|
||||
t.Setenv("HOME", "/tmp/home")
|
||||
check(t, "subdir", nil, checkLaunchDir("/tmp/home/proj", "/nonexistent/hosts"))
|
||||
}
|
||||
|
||||
func leftovers(t *testing.T, dir string) {
|
||||
t.Helper()
|
||||
entries, _ := os.ReadDir(dir)
|
||||
|
||||
@@ -53,21 +53,31 @@ var startService = func(dir string) (context.Context, engine, func(), error) {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
defer func() { _ = logf.Close() }()
|
||||
cmd := exec.Command("podman", "system", "service", "--time=0", "unix://"+sock)
|
||||
// The service stops when we close lifeline, or the kernel does on SIGKILL.
|
||||
// Pdeathsig can't do this: podman's userns join resets it.
|
||||
lifeline, lifelineW, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
cmd := exec.Command("sh", "-c", `podman system service --time=60 "$1" 3<&- & p=$!; { read -r _ <&3; kill $p; } & wait $p`, "sh", "unix://"+sock)
|
||||
cmd.Stdout, cmd.Stderr = logf, logf
|
||||
cmd.ExtraFiles = []*os.File{lifeline}
|
||||
// Own process group: terminal signals must not kill it before cleanup.
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
if err := cmd.Start(); err != nil {
|
||||
err = cmd.Start()
|
||||
_ = lifeline.Close()
|
||||
if err != nil {
|
||||
_ = lifelineW.Close()
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
exited := make(chan struct{})
|
||||
go func() { _ = cmd.Wait(); close(exited) }()
|
||||
stop := func() {
|
||||
_ = cmd.Process.Signal(syscall.SIGTERM)
|
||||
_ = lifelineW.Close()
|
||||
select {
|
||||
case <-exited:
|
||||
case <-time.After(5 * time.Second):
|
||||
_ = cmd.Process.Kill()
|
||||
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
||||
<-exited
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user