Skip to content

Commit 36b8345

Browse files
authored
Merge pull request #1 from loft-sh/experiments/in_rootless_container
Experiments/in rootless container
2 parents c98a217 + eb61003 commit 36b8345

File tree

10 files changed

+86
-31
lines changed

10 files changed

+86
-31
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ env*
22
devpod-provider-dockerless
33
release
44
rootlesskit*
5+
work

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,15 @@ After the initial setup, just use:
4040
```sh
4141
devpod up .
4242
```
43+
44+
## Run in a container
45+
46+
To run in a container, we need CAP_SYS_ADMIN (needed for the unshare, mount and pivot_root syscalls)
47+
To have custom networking we also need access to /dev/net/tun
48+
49+
We DO NOT require root
50+
A nice way to run it is:
51+
52+
`docker run --rm -ti --cap-add CAP_SYS_ADMIN --device /dev/net/tun --user 1000:1000 build-alpine:latest`
53+
54+
Using the image in the `image` folder.

cmd/enter.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import (
1010
)
1111

1212
// EnterCmd holds the cmd flags
13-
type EnterCmd struct{}
13+
type EnterCmd struct{
14+
}
1415

1516
// NewEnterCmd defines a command
1617
func NewEnterCmd() *cobra.Command {

hack/build.sh

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ DATE=$(date "+%Y-%m-%d")
1010
BUILD_PLATFORM=$(uname -a | awk '{print tolower($1);}')
1111

1212
ROOTLESSKIT_VERSION="1.1.1"
13+
SLIRP4NETNS_VERSION="1.2.2"
1314

1415
echo "Current working directory is $(pwd)"
1516
echo "PATH is $PATH"
@@ -56,13 +57,19 @@ for OS in ${PROVIDER_BUILD_PLATFORMS[@]}; do
5657

5758
echo "Building for ${OS}/${ARCH}"
5859
if [[ ${ARCH} == "amd64" ]]; then
60+
rm -f rootlesskit slirp4netns
5961
wget -c "https://github.com/rootless-containers/rootlesskit/releases/download/v${ROOTLESSKIT_VERSION}/rootlesskit-x86_64.tar.gz"
6062
tar -zxvf rootlesskit-x86_64.tar.gz rootlesskit
63+
wget -c "https://github.com/rootless-containers/slirp4netns/releases/download/v${SLIRP4NETNS_VERSION}/slirp4netns-x86_64" -O slirp4netns
64+
chmod +x slirp4netns
6165
elif [[ ${ARCH} == "arm64" ]]; then
66+
rm -f rootlesskit slirp4netns
6267
wget -c "https://github.com/rootless-containers/rootlesskit/releases/download/v${ROOTLESSKIT_VERSION}/rootlesskit-aarch64.tar.gz"
6368
tar -zxvf rootlesskit-aarch64.tar.gz rootlesskit
69+
wget -c "https://github.com/rootless-containers/slirp4netns/releases/download/v${SLIRP4NETNS_VERSION}/slirp4netns-aarch64" -O slirp4netns
70+
chmod +x slirp4netns
6471
fi
65-
GOARCH=${ARCH} GOOS=${OS} ${GO_BUILD_CMD} -ldflags "${GO_BUILD_LDFLAGS}" \
72+
CGO_ENABLED=0 GOARCH=${ARCH} GOOS=${OS} ${GO_BUILD_CMD} -ldflags "${GO_BUILD_LDFLAGS}" \
6673
-o "${PROVIDER_ROOT}/release/${NAME}" main.go
6774
shasum -a 256 "${PROVIDER_ROOT}/release/${NAME}" | cut -d ' ' -f 1 > "${PROVIDER_ROOT}/release/${NAME}".sha256
6875
done

hack/provider/provider.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ options:
1111
agent:
1212
containerInactivityTimeout: ${INACTIVITY_TIMEOUT}
1313
local: true
14+
docker:
15+
install: false
1416
binaries:
1517
DOCKERLESS_PROVIDER:
1618
- os: linux

image/Dockerfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
FROM alpine:latest
2+
3+
4+
RUN apk add --update-cache shadow shadow-subids iproute2 && rm -rf /var/cache/apk/*
5+
6+
RUN useradd --shell /bin/sh --home /home/rootless --add-subids-for-system --uid 1000 --password "" rootless && \
7+
mkdir -p /home/rootless && chown -R rootless:rootless /home/rootless

main.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ import (
1414
var rootlesskit []byte
1515
var rootlesskitPath = filepath.Join("/tmp/dockerless", "rootlesskit")
1616

17+
//nolint: typecheck
18+
//go:embed slirp4netns
19+
var slirp4netns []byte
20+
var slirp4netnsPath = filepath.Join("/tmp/dockerless", "slirp4netns")
21+
1722
func main() {
1823
_, err := os.Stat(rootlesskitPath)
1924
if err != nil {
@@ -28,6 +33,19 @@ func main() {
2833
}
2934
}
3035

36+
_, err = os.Stat(slirp4netnsPath)
37+
if err != nil {
38+
err = os.MkdirAll("/tmp/dockerless", 0o755)
39+
if err != nil {
40+
log.Fatal(err)
41+
}
42+
43+
err = os.WriteFile(slirp4netnsPath, slirp4netns, 0o755)
44+
if err != nil {
45+
log.Fatal(err)
46+
}
47+
}
48+
3149
err = os.Setenv("PATH", os.Getenv("PATH")+":/tmp/dockerless")
3250
if err != nil {
3351
log.Fatal(err)

pkg/dockerless/enter.go

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,7 @@ func (p *DockerlessProvider) Enter(ctx context.Context, workspaceId string) erro
6161
return err
6262
}
6363

64-
err = PivotRoot(containerDIR)
65-
if err != nil {
66-
return err
67-
}
68-
69-
err = syscall.Chdir("/")
64+
err = syscall.Chdir(containerDIR)
7065
if err != nil {
7166
return err
7267
}
@@ -77,28 +72,23 @@ func (p *DockerlessProvider) Enter(ctx context.Context, workspaceId string) erro
7772
return fmt.Errorf("error setting hostname for namespace: %w", err)
7873
}
7974

80-
args := []string{
81-
"--",
82-
runOptions.Entrypoint,
83-
}
84-
args = append(args, runOptions.Cmd...)
85-
86-
cmd := exec.Command("/usr/bin/env", args...)
75+
cmd := exec.Command(runOptions.Entrypoint, runOptions.Cmd...)
8776
cmd.Stdin = os.Stdin
8877
cmd.Stdout = os.Stdout
8978
cmd.Stderr = os.Stderr
90-
cmd.Dir = "/"
79+
cmd.SysProcAttr = &syscall.SysProcAttr{
80+
Chroot: containerDIR,
81+
}
9182
cmd.Env = config.ObjectToList(runOptions.Env)
9283

9384
return cmd.Run()
9485
}
9586

9687
func prepareMounts(rootfs string) error {
97-
err := MountProc(filepath.Join(rootfs, "/proc"))
88+
err := MountBind("/proc", filepath.Join(rootfs, "/proc"))
9889
if err != nil {
9990
return err
10091
}
101-
10292
err = MountTmpfs(filepath.Join(rootfs, "/tmp"))
10393
if err != nil {
10494
return err

pkg/dockerless/exec.go

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import (
1414
)
1515

1616
func (p *DockerlessProvider) ExecuteCommand(ctx context.Context, workspaceId, user, command string, stdin io.Reader, stdout, stderr io.Writer) error {
17+
containerDIR := filepath.Join(p.Config.TargetDir, "rootfs", workspaceId)
18+
1719
ppid, err := GetPid(workspaceId)
1820
if err != nil {
1921
return fmt.Errorf("container %s is not running", workspaceId)
@@ -33,8 +35,14 @@ func (p *DockerlessProvider) ExecuteCommand(ctx context.Context, workspaceId, us
3335
"-u",
3436
"-i",
3537
"-p",
38+
"-r/proc/" + string(pid) + "/root",
39+
"-w/proc/" + string(pid) + "/root",
3640
}
3741

42+
_, err = os.Stat("/dev/net/tun")
43+
if err == nil {
44+
args = append(args, "-n")
45+
}
3846
// user namespace only if we're rootless
3947
if os.Getuid() > 0 {
4048
args = append(args, "-U")
@@ -44,23 +52,19 @@ func (p *DockerlessProvider) ExecuteCommand(ctx context.Context, workspaceId, us
4452
args = append(args, []string{
4553
"-t",
4654
string(pid),
55+
"sh",
56+
"-l",
57+
"-c",
4758
}...)
4859

49-
if user == "" {
50-
args = append(args, command)
51-
} else {
52-
containerDIR := filepath.Join(p.Config.TargetDir, "rootfs", workspaceId)
60+
if user != "" && user != "0" && user != "root" {
5361
uid := findUserPasswd(containerDIR, user)
54-
55-
args = append(args, []string{"su", "-l", uid, "-c", command}...)
62+
command = "su -l " + uid + " -c " + command
5663
}
5764

65+
args = append(args, command)
66+
5867
cmd := exec.Command(nsenter, args...)
59-
environB, err := os.ReadFile(filepath.Join("/proc", string(pid), "environ"))
60-
if err == nil {
61-
environ := strings.Split(string(environB), "\000")
62-
cmd.Env = environ
63-
}
6468
cmd.Stdin = os.Stdin
6569
cmd.Stdout = os.Stdout
6670
cmd.Stderr = os.Stderr

pkg/dockerless/start.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,24 @@ func (p *DockerlessProvider) Start(ctx context.Context, workspaceId string) erro
5656
"--cgroupns",
5757
"--utsns",
5858
"--ipcns",
59-
"--net",
60-
"host",
6159
"--state-dir",
6260
filepath.Join("/tmp", "dockerless", workspaceId),
6361
}
62+
63+
// Default to use slip4netns if we have /dev/net/tun access
64+
_, err = os.Stat("/dev/net/tun")
65+
if err == nil {
66+
args = append(args, []string{
67+
"--net",
68+
"slirp4netns",
69+
"--port-driver",
70+
"slirp4netns",
71+
"--disable-host-loopback",
72+
"--copy-up",
73+
"/etc",
74+
}...)
75+
}
76+
6477
} else {
6578
command = "unshare"
6679
args = []string{

0 commit comments

Comments
 (0)