Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions pkg/agent/delivery/local_docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,10 @@ func (d *LocalDockerDelivery) populateVolumeDirectCopy(
volumeName string,
data []byte,
) error {
if d.isRootlessPodman(ctx) {
return d.populateVolumeUnshare(ctx, volumeName, data)
}

mountpoint, err := d.volumeMountpoint(ctx, volumeName)
if err != nil {
return fmt.Errorf("inspect volume mountpoint: %w", err)
Expand All @@ -167,6 +171,28 @@ func (d *LocalDockerDelivery) populateVolumeDirectCopy(
return nil
}

func (d *LocalDockerDelivery) populateVolumeUnshare(
ctx context.Context,
volumeName string,
data []byte,
) error {
mountpoint, err := d.volumeMountpoint(ctx, volumeName)
if err != nil {
return fmt.Errorf("inspect volume mountpoint: %w", err)
}

destPath := filepath.Join(mountpoint, binaryName())

cmd := d.cmd(ctx, "unshare", "sh", "-c", `cat > "$1" && chmod 755 "$1"`, "--", destPath)
cmd.Stdin = bytes.NewReader(data)

out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("podman unshare write: %s: %w", string(out), err)
}
return nil
}

func (d *LocalDockerDelivery) volumeMountpoint(
ctx context.Context,
volumeName string,
Expand All @@ -191,6 +217,25 @@ func (d *LocalDockerDelivery) removeVolume(ctx context.Context, workspaceID stri
return nil
}

func (d *LocalDockerDelivery) isPodman(ctx context.Context) bool {
out, err := d.cmd(ctx, "--version").CombinedOutput()
if err != nil {
return false
}
return strings.Contains(string(out), "podman")
}

func (d *LocalDockerDelivery) isRootlessPodman(ctx context.Context) bool {
if !d.isPodman(ctx) {
return false
}
out, err := d.cmd(ctx, "info", "--format", "{{.Host.Security.Rootless}}").CombinedOutput()
if err != nil {
return false
}
return strings.TrimSpace(string(out)) == "true"
}

func (d *LocalDockerDelivery) cmd(ctx context.Context, args ...string) *exec.Cmd {
// #nosec G204 -- args are constructed internally, not from user input
cmd := exec.CommandContext(ctx, d.dockerCommand(), args...)
Expand Down
139 changes: 139 additions & 0 deletions pkg/agent/delivery/local_docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,142 @@ func TestPopulateVolume_FallbackToDirectCopy(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, os.FileMode(0o755), info.Mode().Perm())
}

func TestIsPodman_Docker(t *testing.T) {
tmpDir := t.TempDir()
scriptPath := filepath.Join(tmpDir, "fake-docker.sh")
script := "#!/bin/sh\necho 'Docker version 24.0.7, build afdd53b'\n"
require.NoError(t, os.WriteFile(scriptPath, []byte(script), 0o600))
// #nosec G302 -- test script must be executable
require.NoError(t, os.Chmod(scriptPath, 0o755))

d := &LocalDockerDelivery{DockerCommand: scriptPath}
assert.False(t, d.isPodman(context.Background()))
}

func TestIsPodman_Podman(t *testing.T) {
tmpDir := t.TempDir()
scriptPath := filepath.Join(tmpDir, "fake-podman.sh")
script := "#!/bin/sh\necho 'podman version 4.9.3'\n"
require.NoError(t, os.WriteFile(scriptPath, []byte(script), 0o600))
// #nosec G302 -- test script must be executable
require.NoError(t, os.Chmod(scriptPath, 0o755))

d := &LocalDockerDelivery{DockerCommand: scriptPath}
assert.True(t, d.isPodman(context.Background()))
}

func TestIsRootlessPodman_DockerRuntime(t *testing.T) {
tmpDir := t.TempDir()
scriptPath := filepath.Join(tmpDir, "fake-docker.sh")
script := "#!/bin/sh\necho 'Docker version 24.0.7, build afdd53b'\n"
require.NoError(t, os.WriteFile(scriptPath, []byte(script), 0o600))
// #nosec G302 -- test script must be executable
require.NoError(t, os.Chmod(scriptPath, 0o755))

d := &LocalDockerDelivery{DockerCommand: scriptPath}
assert.False(t, d.isRootlessPodman(context.Background()))
}

func TestIsRootlessPodman_PodmanRootful(t *testing.T) {
tmpDir := t.TempDir()
scriptPath := filepath.Join(tmpDir, "fake-podman.sh")
script := "#!/bin/sh\n" +
"case \"$1\" in\n" +
" --version) echo 'podman version 4.9.3' ;;\n" +
" info) echo 'false' ;;\n" +
" *) exit 1 ;;\n" +
"esac\n"
require.NoError(t, os.WriteFile(scriptPath, []byte(script), 0o600))
// #nosec G302 -- test script must be executable
require.NoError(t, os.Chmod(scriptPath, 0o755))

d := &LocalDockerDelivery{DockerCommand: scriptPath}
assert.False(t, d.isRootlessPodman(context.Background()))
}

func TestIsRootlessPodman_PodmanRootless(t *testing.T) {
tmpDir := t.TempDir()
scriptPath := filepath.Join(tmpDir, "fake-podman.sh")
script := "#!/bin/sh\n" +
"case \"$1\" in\n" +
" --version) echo 'podman version 4.9.3' ;;\n" +
" info) echo 'true' ;;\n" +
" *) exit 1 ;;\n" +
"esac\n"
require.NoError(t, os.WriteFile(scriptPath, []byte(script), 0o600))
// #nosec G302 -- test script must be executable
require.NoError(t, os.Chmod(scriptPath, 0o755))

d := &LocalDockerDelivery{DockerCommand: scriptPath}
assert.True(t, d.isRootlessPodman(context.Background()))
}

func TestPopulateVolumeDirectCopy_RootlessPodman_UsesUnshare(t *testing.T) {
tmpDir := t.TempDir()
mountDir := filepath.Join(tmpDir, "mount")
require.NoError(t, os.MkdirAll(mountDir, 0o750))

scriptPath := filepath.Join(tmpDir, "fake-podman.sh")
script := "#!/bin/sh\n" +
"case \"$1\" in\n" +
" --version) echo 'podman version 4.9.3' ;;\n" +
" info) echo 'true' ;;\n" +
" volume) echo '" + mountDir + "' ;;\n" +
" unshare) shift; exec \"$@\" ;;\n" +
" *) exit 1 ;;\n" +
"esac\n"
require.NoError(t, os.WriteFile(scriptPath, []byte(script), 0o600))
// #nosec G302 -- test script must be executable
require.NoError(t, os.Chmod(scriptPath, 0o755))

binaryContent := []byte("fake-agent-binary-rootless")

d := &LocalDockerDelivery{DockerCommand: scriptPath}

err := d.populateVolumeDirectCopy(context.Background(), "test-vol", binaryContent)
require.NoError(t, err)

destPath := filepath.Join(mountDir, binaryName())
data, err := os.ReadFile(destPath) //nolint:gosec // test reads from a temp directory we control
require.NoError(t, err)
assert.Equal(t, binaryContent, data)

info, err := os.Stat(destPath)
require.NoError(t, err)
assert.Equal(t, os.FileMode(0o755), info.Mode().Perm())
}

func TestPopulateVolumeDirectCopy_Docker_SkipsUnshare(t *testing.T) {
tmpDir := t.TempDir()
mountDir := filepath.Join(tmpDir, "mount")
require.NoError(t, os.MkdirAll(mountDir, 0o750))

scriptPath := filepath.Join(tmpDir, "fake-docker.sh")
script := "#!/bin/sh\n" +
"case \"$1\" in\n" +
" --version) echo 'Docker version 24.0.7, build afdd53b' ;;\n" +
" volume) echo '" + mountDir + "' ;;\n" +
" unshare) echo 'unshare should not be called' >&2; exit 1 ;;\n" +
" *) exit 1 ;;\n" +
"esac\n"
require.NoError(t, os.WriteFile(scriptPath, []byte(script), 0o600))
// #nosec G302 -- test script must be executable
require.NoError(t, os.Chmod(scriptPath, 0o755))

binaryContent := []byte("fake-agent-binary-docker")

d := &LocalDockerDelivery{DockerCommand: scriptPath}

err := d.populateVolumeDirectCopy(context.Background(), "test-vol", binaryContent)
require.NoError(t, err)

destPath := filepath.Join(mountDir, binaryName())
data, err := os.ReadFile(destPath) //nolint:gosec // test reads from a temp directory we control
require.NoError(t, err)
assert.Equal(t, binaryContent, data)

info, err := os.Stat(destPath)
require.NoError(t, err)
assert.Equal(t, os.FileMode(0o755), info.Mode().Perm())
}
Loading