-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathdocker-entrypoint.sh
More file actions
executable file
·146 lines (134 loc) · 6.99 KB
/
Copy pathdocker-entrypoint.sh
File metadata and controls
executable file
·146 lines (134 loc) · 6.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#!/bin/sh
set -e
# --- Direct sandbox backend: provision the Python venv on first boot ----------
# The direct backend executes untrusted Python in-process. Its dependency set is
# deliberately NOT baked into the image (that keeps the default docker/gvisor
# image lean); it installs here, once, from the hash-locked lockfile. Idempotent:
# skipped when the venv already exists (a container restart, or a /opt/panda-venv
# volume persisted across pod restarts). Other backends never run this.
#
# uv installs from PyPI, but --require-hashes pins every artifact to the lock, so
# a compromised index cannot substitute a package. A failure here aborts startup
# (set -e) rather than launching a half-provisioned server.
PANDA_VENV="${PANDA_SANDBOX_PYTHON_VENV:-/opt/panda-venv}"
if [ "${PANDA_SANDBOX_BACKEND:-}" = "direct" ]; then
if [ ! -x "$PANDA_VENV/bin/python" ]; then
echo "docker-entrypoint: provisioning direct-backend venv at $PANDA_VENV (first boot, hash-locked)..." >&2
uv venv "$PANDA_VENV" --python python3
uv pip install --python "$PANDA_VENV/bin/python" --no-cache \
--require-hashes --only-binary=:all: -r /opt/panda-sandbox/requirements.txt
uv pip install --python "$PANDA_VENV/bin/python" --no-cache --no-deps \
/opt/panda-sandbox/ethpandaops-pkg
echo "docker-entrypoint: direct-backend venv ready" >&2
fi
# Exported so a config with python_path: ${PANDA_SANDBOX_PYTHON_PATH} resolves.
export PANDA_SANDBOX_PYTHON_PATH="${PANDA_SANDBOX_PYTHON_PATH:-$PANDA_VENV/bin/python}"
fi
# ------------------------------------------------------------------------------
# Run the server as whoever owns the mounted credentials directory, so it can
# read and refresh the 0600 OAuth credential files the host wrote there.
#
# The image bakes a `panda` user at UID/GID 1000. When the host user driving
# panda has a different UID — a dedicated service account, a non-1000 primary
# user — the mounted credentials are owned by that UID and `panda` (1000) cannot
# read them: `panda auth status` fails with "permission denied" and every server
# token refresh re-writes the creds owned by 1000, so a host-side chown never
# sticks. Re-number the baked `panda` user to match the credentials owner before
# dropping to it, so the rest of the startup (volume chown, Docker group
# membership, HOME resolution) keeps working unchanged.
#
# Rootless Docker is the exception: the host user maps to container UID 0, so the
# credentials appear root-owned. Run as root directly (under rootless that maps
# back to the unprivileged host user, so it is not a privilege escalation) with
# HOME set so the credential path beneath it still resolves.
CRED_DIR="/home/panda/.config/panda/credentials"
CRED_UID=1000
if [ -d "$CRED_DIR" ]; then
CRED_UID=$(stat -c '%u' "$CRED_DIR" 2>/dev/null || echo 1000)
fi
if [ "$CRED_UID" = "0" ]; then
export HOME=/home/panda
exec "$@"
fi
# Non-1000 host user: re-number the panda user (and its primary group, when that
# GID is free) to own the credentials it reads and rewrites. usermod re-owns the
# files already under /home/panda for us; data dirs are chowned below.
if [ "$CRED_UID" != "1000" ]; then
CRED_GID=$(stat -c '%g' "$CRED_DIR" 2>/dev/null || echo "$CRED_UID")
if [ "$CRED_GID" != "1000" ] && ! getent group "$CRED_GID" >/dev/null 2>&1; then
groupmod -g "$CRED_GID" panda 2>/dev/null || true
fi
usermod -u "$CRED_UID" panda 2>/dev/null || true
# Fail loudly rather than silently drop to UID 1000 and recreate the
# unreadable-credentials bug — e.g. usermod/groupmod missing, or $CRED_UID
# already taken by another account.
if [ "$(id -u panda)" != "$CRED_UID" ]; then
echo "docker-entrypoint: failed to run as credentials owner UID $CRED_UID" \
"(panda is still UID $(id -u panda)); credentials would be unreadable." >&2
exit 1
fi
fi
PANDA_UID=$(id -u panda)
PANDA_GID=$(id -g panda)
# Fix ownership of mounted volumes that Docker may create as root, plus the
# build-time directories the renumbered panda user no longer owns, so the server
# can write them after dropping privileges.
for dir in /data/storage /data/cache /output /shared; do
if [ -d "$dir" ] && [ "$(stat -c '%u' "$dir")" != "$PANDA_UID" ]; then
chown "$PANDA_UID:$PANDA_GID" "$dir"
fi
done
# If the Docker socket is mounted, add panda to its group so the server
# can manage sandbox containers after dropping root.
# --group-add at the container level is lost by su-exec/gosu, so we
# persist the group in /etc/group instead.
if [ -S /var/run/docker.sock ]; then
DOCKER_GID=$(stat -c '%g' /var/run/docker.sock)
if ! getent group "$DOCKER_GID" >/dev/null 2>&1; then
addgroup -g "$DOCKER_GID" docker-host 2>/dev/null || groupadd -g "$DOCKER_GID" docker-host 2>/dev/null || true
fi
DOCKER_GROUP=$(getent group "$DOCKER_GID" | cut -d: -f1)
addgroup panda "$DOCKER_GROUP" 2>/dev/null || usermod -aG "$DOCKER_GROUP" panda 2>/dev/null || true
fi
# Drop to the panda user. su-exec/gosu set HOME from the passwd entry, but set
# it explicitly too so the credential path beneath it resolves regardless.
export HOME=/home/panda
# The direct sandbox backend re-execs panda-server into fresh mount + PID +
# network namespaces and drops it to a dedicated unprivileged uid, so the server
# process must carry ambient CAP_SETUID/CAP_SETGID/CAP_SYS_ADMIN (namespaces +
# uid drop), CAP_NET_ADMIN (raise loopback inside the sandbox netns), and
# CAP_CHOWN (lock each workspace to the exec gid) across this privilege drop.
# gosu/su-exec cannot set ambient caps; setpriv can. The container must start as
# root (this entrypoint provisions the venv, renumbers panda, chowns volumes,
# then drops) with those caps in its permitted set — grant them via the pod
# securityContext (do NOT set runAsNonRoot / runAsUser). On AppArmor-enforcing
# hosts the default container profiles (docker-default, cri-containerd.apparmor.d)
# deny mount(2) inside the new namespaces even with CAP_SYS_ADMIN, so the profile
# must be Unconfined (docker: --security-opt apparmor=unconfined):
#
# securityContext:
# appArmorProfile:
# type: Unconfined
# capabilities:
# add: ["SETUID", "SETGID", "SYS_ADMIN", "NET_ADMIN", "CHOWN"]
#
# If the caps are absent, setpriv fails here and the container fails closed
# rather than launching an unconfined direct backend.
if [ "${PANDA_SANDBOX_BACKEND:-}" = "direct" ]; then
if ! command -v setpriv >/dev/null 2>&1; then
echo "docker-entrypoint: setpriv is required for the direct sandbox backend" >&2
exit 1
fi
exec setpriv --reuid=panda --regid="$(id -g panda)" --init-groups \
--inh-caps=+setuid,+setgid,+sys_admin,+net_admin,+chown \
--ambient-caps=+setuid,+setgid,+sys_admin,+net_admin,+chown \
"$@"
fi
# Other backends: drop with su-exec (Alpine) / gosu (Debian), no elevated caps.
if command -v su-exec >/dev/null 2>&1; then
exec su-exec panda "$@"
elif command -v gosu >/dev/null 2>&1; then
exec gosu panda "$@"
else
exec "$@"
fi