43ab6bbb68
* feat(execd): execd as sandbox init (OSEP-0018 phase 1) Make execd the sandbox init (PID 1): it becomes the parent of the user entrypoint, reaps every child through a single reaper, forwards application signals, and owns the container lifecycle (entrypoint exit code is propagated to the runtime). - --init flag + EXECD_INIT bootstrap.sh exec branch (default off, classic background-and-wait topology unchanged) - single reaper (only wait4-family caller) with pre-reap WNOWAIT barrier; managedProcess abstraction replaces Cmd.Wait across all launch paths (command, bash session, PTY, isolated session) - signal forwarding (HUP/USR1/USR2/WINCH) and SIGTERM graceful shutdown; subreaper fallback when not PID 1; PR_SET_DUMPABLE(0) - hardening.init_mode/signal_shield reported on the capabilities endpoint (spec + Go/Python SDKs aligned) - unit tests for the reaper and lifecycle; bootstrap contract test * feat(execd): pre-exec hardening floor (OSEP-0018 phase 2) Route every user-code launch (entrypoint, /command, /code, PTY) through a native launcher that applies the privilege floor between fork and exec: execd credential env stripped, bounding set trimmed to keep_capabilities (default none), no_new_privs, identity drop to the image user, ambient caps raised, and the seccomp filter installed last. - native/launcher.c: static prelude helper, fail-closed on malformed policy, fail-open per step; built/installed by the Makefile and image - [hardening] enabled + keep_capabilities in the isolation TOML; the launcher's execve is reserved and rejected at config time (execveat stays valid); [seccomp] deny is reused as the floor filter - isolated sessions exempt: the bwrap workload is already reduced inside the namespace, and flooring the bwrap process would deny unshare and strip the caps it needs to build the namespace - hardening.cap_drop/seccomp/landlock/ebpf layer states on the capabilities endpoint (spec + Go/Python SDKs aligned) - all layers fail open: missing launcher or CAP_SETPCAP degrades with a reason instead of blocking startup * feat(server): runtime.execd_run_as_init config injects EXECD_INIT Single server-side switch (default false) that sets EXECD_INIT=1 in the sandbox environment across all paths (Docker, K8s Batch/Agent, Pool taskTemplate). bootstrap.sh then execs execd --init, so topology and the --init flag stay in lockstep by construction (OSEP-0018 open question 1). Execd init mode and hardening remain independently controllable; the switch is intended to be flipped on by default in a later release. * test(execd): real-container init-mode regression (OSEP-0018) Runs the execd image with EXECD_INIT=1 in Docker and verifies the init contract end to end: execd is PID 1 with the workload as its direct child, orphans are reaped (no zombie accumulation), in-namespace kill -9 1 is inert (signal shield), entrypoint exit codes are propagated, runtime SIGTERM is forwarded with the workload status preserved, and with [hardening] enabled the floor applies (CapEff=0, no_new_privs, seccomp filter, env strip) while the capabilities endpoint reports pid1 + active layers. Also covers the Pool-style backgrounded topology reporting subreaper mode. Wired into execd-test.yml (ubuntu smoke job); EXECD_TEST_IMAGE allows local iteration against a prebuilt image. * feat(execd): landlock confinement (OSEP-0018 phase 3) Add [landlock] enabled/extra_writable/extra_readable on top of the hardening floor. The launcher applies the filesystem allowlist between the identity drop and seccomp: system paths and /proc/self read+exec, writable device files and the controlling tty, /tmp, /run, and allowed_writable — everything else denied. A root EXECUTE-only rule covers traversal and execve of any binary without granting reads, and all of /proc is never granted (would re-expose /proc/1). The kernel ABI is probed (v1-v4) and the launcher trims access bits (REFER/TRUNCATE) accordingly; ABI < 1 reports unsupported and skips. The container regression now enables landlock and asserts that /proc/1/environ is unreadable by the workload when the layer is active. * feat(execd): eBPF observation variant (OSEP-0018 phase 4) Opt-in exec/connect/privilege audit, scoped to the sandbox cgroup and written as rotating JSONL (stable envelope: ts/event/sandbox_id/pid/comm plus per-kind fields). Ships as the execd-ebpf build variant (CGO + cilium/ebpf); the default image is unchanged and reports disabled. - BPF programs (CO-RE): sched_process_exec (filename+argv via __data_loc), inet_sock_set_state (dst ip:port), commit_creds kprobe (uid/gid deltas, cap_added) - kernel compatibility from 5.10: the exec trace event layout change in 5.16 is handled with a bpf_core_field_exists discriminator, the kernel_cap_t shape change in 6.3 with a layout-agnostic 8-byte read; Landlock (phase 3) still requires >= 5.13 and degrades to unsupported - [ebpf] enabled/observe/audit_file config; capabilities endpoint layer state (active/unsupported/degraded/disabled), all fail-open - Dockerfile ebpf target + make build-ebpf; generated bytecode committed; unit tests for event decoding; CI runs the ebpf-tagged tests * docs(spec): drop phase markers from hardening layer descriptions * feat(sdks): surface hardening status across all execd-capabilities SDKs Regenerate the JS execd client from the spec and extend the handwritten adapters so every SDK exposes the hardening object (init_mode, signal_shield, cap_drop/seccomp/landlock/ebpf layer states): - javascript: generated execd.ts gains hardening + HardeningLayerState - kotlin: IsolatedCapabilities.hardening domain model + adapter mapping (with a mock-server test) — the generated sandbox-api client picks the spec up at build time - csharp: IsolatedCapabilities gains Hardening/HardeningStatus/ HardeningLayerState records Go and Python already carried the field; MCP and code-interpreter SDKs do not consume the capabilities endpoint. * feat(server,k8s): pool tasks run execd as the task root (OSEP-0018 phase 5) With runtime.execd_run_as_init enabled, the pool taskTemplate no longer backgrounds bootstrap: the task-executor shim's shell execs bootstrap.sh, which execs `execd --init` (EXECD_INIT=1), so execd becomes the root of the task process tree — orphaned task children are reaped (subreaper) and the entrypoint exit code propagates to the task status. Classic background-and-wait topology is preserved when the switch is off. The K8s Restart recycle contract (kill 1 via pod exec) is confirmed compatible with init-mode execd under the current SIGTERM-forward semantics: execd forwards the signal and exits with the workload status, so the kubelet restarts the container. A note marks the reconciliation required once a trusted out-of-band stop channel replaces signal-driven stop (OSEP-0018 §3). Docs: Pool pod template guidance for running execd as the pod's PID 1. * docs(oseps): mark 0018 as implementing with granular phase status Record the phased implementation state (phases 1-5 + server switch done), the resolutions of open questions 1-5, and the remaining work (trusted stop channel, Pool pod-level PID 1, 5.10 validation, e2e). * fix: address Codex/human review feedback across five rounds + CI repairs Squashed review-fix series: - CI: Dockerfile stage order (ebpf variant was the default image), license headers on bpf2go output, verify-license skip for generated files, golangci (gci/predeclared/unused), kotlin ktlint - landlock: required-vs-best-effort rule semantics, mount-expanded rules (bind-mounted workspaces), dynamic rule counts, preflight degradation for operator-explicit grants, per-launch fail-closed ruleset - hardening: trusted launcher path first, MFD_CLOEXEC policy memfd, per-request uid/gid folded into the policy, entrypoint keeps bootstrap env but never EXECD_ACCESS_TOKEN, bash-session env snapshot scrub - init: reaper drops reaped pids (bounded map, no stale pgid signalling), SIGTERM/SIGKILL sent under the reaper lock, synchronous signal.Notify before the entrypoint starts - eBPF: sandbox_id in records, cgroup-id under the cgroup v2 mount, requested-hook attach failures degrade, IPv4-mapped event format, cap-only privilege events, argv dropped from exec events - distribution: launcher shipped on docker/k8s paths, pool taskTemplate exec + needs_task_template fix, ebpf image keeps the default layout and builds static - container regression: workdir world-writable (no CAP_DAC_OVERRIDE under hardening), /proc/self read via the entrypoint process (Landlock descendant limitation) * fix: address Codex round 6 (credential identity, audit sink, /opt, sandbox_id) - hardening: per-request credentials now force the launcher's UID_DROP even when execd is not root (a /command uid/gid request previously ran as the image user), and supplementary groups are serialized into the policy so the launcher applies setgroups(groups) instead of clearing them - audit: loaded eBPF objects are retained on the Observer (GC could close the fd and detach the programs) and closed on Close(); ringbuf reader failure now also closes already-attached links; audit write errors are logged instead of dropped - landlock: /opt joins the default read+exec set (bundled code-interpreter entrypoints and runtimes live under /opt) - server: OPENSANDBOX_ID is injected on the Docker, K8s Batch/Agent and pool taskTemplate env paths so eBPF audit records carry the sandbox id * test(e2e): dedicated execd-as-init real-e2e for python, plus nightly job Add tests/python/tests/test_execd_init_e2e.py (sync SDK): PID 1 is execd, the workload is its direct child, orphans are reaped, in-namespace kill -9 1 is inert, and /v1/isolated/capabilities reports hardening.init_mode=pid1. Runs against a server with runtime.execd_run_as_init=true. - scripts/python-execd-init-e2e.sh: docker-bridge runner for the new suite; wired as a dedicated real-e2e job (python-execd-init-e2e) - scripts/python-k8s-execd-init-e2e.sh + E2E_EXECD_RUN_AS_INIT in k8s_e2e_write_server_helm_values: Kind/Kubernetes variant; wired as a dedicated kubernetes-nightly-build job (execd-init-e2e) * ci(e2e): fix execd-init e2e failures in python and k8s nightly jobs * feat(execd): ship execd-ebpf in default image; docs and e2e sync (OSEP-0018) * fix(execd): address Codex round 7 (landlock jupyter log, init-mode report, launcher identity, ebpf build tags) * docs(oseps): fold remaining-work tracking into OSEP-0018 status section * fix(execd,server): resolve codex review on hardening report and pool sandbox id attribution - ReportHardening keys the non-init topology correction off hardening being enabled instead of cap_drop's state, and only degrades layers that are actually in effect (previously a degraded cap_drop left seccomp/landlock claiming active, and a disabled landlock could be marked degraded) - eBPF Init reports unsupported when OPENSANDBOX_ID is missing, so pool fast-path allocations without a task template cannot silently claim active attribution - batchsandbox_provider logs the pool fast-path limitation (no per-allocation env injection without a task template) - docs/components/execd.md documents the pool path attribution behavior - tests: cover non-init degradation, landlock enabled case, and pool fast path * fix(execd): report configured non-Linux layers as unsupported; fix golint goconst - non-Linux: InitHardening/SetEbpfState now record requested layers so the capabilities endpoint reports configured hardening/landlock/ebpf as unsupported instead of disabled (codex review) - hardening_linux.go: hoist repeated "disabled" state string into a const (golangci-lint goconst) - docs: describe the dual-binary default image (execd + execd-ebpf) and the deferred server-side selection; no Dockerfile 'ebpf' target exists --------- Co-authored-by: Sky <yutian.taoyt@alibaba-inc.com>
54 lines
1.6 KiB
Bash
Executable File
54 lines
1.6 KiB
Bash
Executable File
#!/bin/bash
|
|
# Copyright 2026 Alibaba Group Holding Ltd.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
# Runs the execd-as-init E2E (OSEP-0018) against a local Docker-bridge
|
|
# server. The server config (~/.sandbox.toml) is written by the workflow
|
|
# with runtime.execd_run_as_init = true.
|
|
|
|
set -euxo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
SERVER_PID=""
|
|
|
|
cleanup() {
|
|
if [ -n "${SERVER_PID}" ]; then
|
|
kill "${SERVER_PID}" 2>/dev/null || true
|
|
wait "${SERVER_PID}" 2>/dev/null || true
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
docker build -f components/execd/Dockerfile -t opensandbox/execd:local "${REPO_ROOT}"
|
|
docker pull opensandbox/code-interpreter:${TAG:-latest}
|
|
|
|
mkdir -p /tmp/opensandbox-e2e/logs
|
|
echo "-------- EXECD INIT E2E test logs for execd --------" > /tmp/opensandbox-e2e/logs/execd.log
|
|
|
|
cd server
|
|
export OPENSANDBOX_INSECURE_SERVER=YES
|
|
uv sync
|
|
uv run python -m opensandbox_server.main > server.log 2>&1 &
|
|
SERVER_PID=$!
|
|
cd ..
|
|
|
|
sleep 10
|
|
|
|
cd sdks/sandbox/python && make generate-api
|
|
cd ../../..
|
|
|
|
cd tests/python
|
|
uv sync --all-extras --refresh
|
|
uv run pytest tests/test_execd_init_e2e.py -v
|