EKS MNG userns rootful DinD experiment - #175
Draft
geokat wants to merge 1 commit into
Draft
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Native Kubernetes user namespaces with rootful DinD
Experiment run: 2026-08-06
Record last updated: 2026-08-07
Question tested
Can a Coder workspace run a normal rootful Docker daemon and BuildKit in a
native Kubernetes user-namespace Pod, without Envbox or Sysbox, while keeping
Pod UID 0 mapped to an unprivileged host UID range?
This was not a rootless-Docker test. The ultimately successful shape was:
The Pod used
capabilities.add: ["ALL"],procMount: Unmasked, an unconfinedseccomp profile, and
allowPrivilegeEscalation: true. Those powers were insidethe Pod's user namespace: container UID 0 mapped to a nonzero host UID range.
The resulting workspace was effectively privileged over resources owned by
that user namespace, but it was neither a Kubernetes
privileged: truecontainer nor privileged in the host's initial user namespace. Consequently,
privileged: falsehere must not be read as the security posture of aconventionally restricted application Pod.
Environment
v1.36.2-eks-254016einus-east-2.m6i.largemanaged node-group nodes.6.18.38-76.139.amzn2023.x86_64.2.2.5+unknown.gp3-csiStorageClass usingebs.csi.aws.comandWaitForFirstConsumer.docker:27-dind, which resolved to Docker Engine27.5.1; the replay manifest now pinsdocker:27.5.1-dind.The MNG was selected as the debugging-friendly baseline before considering
EKS Auto Mode/Bottlerocket. It permits explicit node bootstrap and containerd
configuration.
Baseline results on the stock runtime
Native user namespace with an EBS PVC: pass
userns-volume-probe.yamlran withhostUsers: false, bound the EBS PVC, andwrote and read
/workspace/probe.txtsuccessfully:This proved both non-host UID mapping and compatibility with the CSI-mounted
EBS/ext4 workspace volume.
Namespaced privileged probe: pass
The initial capability probe used
privileged: trueandprocMount: Unmasked. It retained a non-host UID mapping and successfullycreated a private tmpfs mount. This established that the requested kernel
operations were available inside the user namespace, but the final DinD Pod
did not need Kubernetes
privileged: true.Rootful Docker on the stock runtime: fail
Dockerd started and initialized
overlay2on the PVC, and image pullingworked. Every attempt to start a child container failed with:
A focused probe confirmed that the stock runtime exposed no cgroup directory
writable by the Pod. Therefore
hostUsers: falsealone was insufficient forrootful DinD.
Cgroup-writable RuntimeClass follow-up
The follow-up added a second AL2023 MNG. Its containerd configuration
registered a named handler using the stock
io.containerd.runc.v2runtime:runc-cgroup-writableis a local RuntimeClass/handler name, not a custom runcbinary.
Writable-cgroup probe: pass
A non-privileged, user-namespaced Pod scheduled through this RuntimeClass and
created a child cgroup successfully:
This fixed the original permission-denied failure.
First rootful-Docker attempt: partial pass
Without preparing the cgroup topology, the following worked:
overlay2on the EBS/ext4 PVC;docker run;RUNsteps;Only a container using
--memory=64m --pids-limit=64failed. The hierarchyshowed:
PID 1, dockerd, and containerd occupied the delegated root while threaded
controllers were enabled. This forced a threaded topology in which Docker
could not apply the domain
memorycontroller.Domain-cgroup topology: full workload pass
Before starting dockerd, the final entrypoint:
/sys/fs/cgroup/workspace-processes;cpuset cpu io memory pidsin the root'scgroup.subtree_control;workspace-processes, with Docker children under thesibling
/dockerhierarchy.The recorded result was:
Image pull, ordinary nested execution, BuildKit, bridge networking, and a
nested container configured with memory and PID limits all completed.
This topology is not unique to the native MNG experiment. Current Envbox and
Sysbox solve the same cgroup-v2 no-internal-process constraint at two levels:
cli/wrap_dockerd.sh) creates an/initleaf, moves processes out of the visible cgroup root, and enables its
controllers before starting the outer dockerd. This keeps inner-container
cgroups beneath the Envbox Pod's host cgroup tree.
init.scopeleaf for the system container, placesits init and exec processes there, and delegates ownership of the cgroup-v2
control files so inner systemd or Docker can create domain sub-cgroups.
The native wrapper's
workspace-processesleaf and sibling/dockerhierarchy explicitly reproduce the latter delegation pattern using stock runc
and containerd's
cgroup_writable = truehandler. The wrapper is therefore anexplicit replacement for behavior that Sysbox normally supplies invisibly,
not an unrelated workaround.
Docker Compose networking follow-up: pass
A targeted Docker Compose test created a user-defined bridge network with an
nginx:1.27-alpineserver and analpine:3.21client. It demonstrated:http://server;0.0.0.0:18080->80/tcp;127.0.0.1:18080;The external peer check first passed on the workspace node and then passed
from the original MNG node. The cross-node probe reached both the workspace
Pod IP (
192.168.83.149:18080) and the ClusterIP Service, demonstrating thatDocker's nested bridge/NAT and port-publishing rules interoperated with EKS
Pod routing and Service forwarding across nodes.
One diagnostic nuance was observed. BusyBox
nslookup servertried theKubernetes search domains inherited by the nested container with
ndots:5and returned failure, while
nslookup server.,getent hosts server, andHTTP to the bare name
serverall resolved the Compose service correctly.This did not prevent ordinary libc-based application resolution, but clients
with unusual raw-DNS/search-list behavior may require separate validation.
The successful
rootful-dind.yamlreplaymanifest now automates the Compose service-name, HTTP, outbound-network, and
workspace-loopback checks and records explicit completion artifacts. The
companion
compose-network-peer.yamldeclaratively creates the ClusterIP and headless Services and pins a restricted
peer Pod to the original MNG. The peer resolves the headless Service to the
workspace Pod IP, accesses that IP directly, and separately accesses the
ClusterIP Service. The Docker patch release and observed Alpine and Nginx
digests are pinned for repeatability.
These manifests encode checks that passed interactively during the recorded
experiment. Their newly combined automated orchestration has not yet itself
been rerun; a future replay must still verify the completion files, peer log,
and distinct workspace/peer node placement before treating the manifests as a
fresh pass.
Interpretation
Native Kubernetes user namespaces can support rootful DinD on this EKS 1.36
AL2023 MNG without a host-privileged workspace Pod, provided that all of the
following are supplied:
hostUsers: falseand the broad in-user-namespace security context neededby dockerd;
cgroup_writable = true;topology before starting dockerd;
overlay2worked here.The stock EKS runtime remains insufficient. The positive result depends on
purpose-built node/runtime configuration and is currently demonstrated only
on a configurable managed node group.
AMI compatibility boundary
The positive result was demonstrated on the AWS EKS-optimized Amazon Linux
2023 AMI. That AMI runs
nodeadmduring boot, andnodeadmsupports mergingadditional inline containerd TOML from a
NodeConfig. The experiment usedthat supported bootstrap path to register the handler; it did not modify or
rebuild the AMI itself.
This result does not establish compatibility with every custom, certified, or
hardened AMI. A candidate AMI must preserve the
nodeadm/NodeConfigbootstrap path, permit the containerd override, provide a containerd version
that supports
cgroup_writable, and use the matching containerd configurationschema. In particular, containerd 1.x and 2.x use different CRI plugin paths.
AMI hardening or compliance policy may also prohibit writable delegated
cgroups even when the image can technically accept the configuration.
Therefore the current compatibility boundary is:
and runtime behavior are preserved, and must be tested;
compliance validation;
Storage compatibility boundary
The MNG stack supports idmapped mounts; the
hostUsers: falsevolume probesuccessfully mounted and wrote to an EBS/ext4 PVC. Ext4 therefore provides a
proven storage path for the normal Coder shape of one workspace Pod using one
RWO persistent volume.
NFS volumes are not supported for Kubernetes user-namespace Pods. Kubernetes
1.36 explicitly documents that the Linux NFS client does not support idmapped
mounts, which these Pods require for every filesystem used by a Pod volume.
This also excludes standard EFS CSI volumes because EFS is mounted through
NFS. See the upstream
user-namespace filesystem requirements.
This is a meaningful compatibility restriction, but not a general blocker for
an EBS-backed Envbox replacement. It becomes blocking for templates that
require NFS/EFS semantics such as RWX storage, concurrently shared home
directories or datasets, or storage without EBS availability-zone affinity.
Namespace and nested-networking boundaries
Kubernetes disallows combining
hostUsers: falsewithhostNetwork: true,hostPID: true, orhostIPC: true. This is a native user-namespacerestriction and therefore applies to the MNG design. It is unlikely to block
an ordinary Coder workspace, which normally uses Pod networking and isolated
PID and IPC namespaces, but it excludes specialized workspaces that require
direct host networking or host process/IPC inspection. See the upstream
user-namespace limitations.
The claim that nested networking necessarily uses userspace NAT is not true
for the rootful-Docker design tested here. Dockerd can use Linux bridges, veth
interfaces, and kernel iptables/nftables NAT within the Pod's network
namespace using its namespaced
CAP_NET_ADMIN; traffic then passes throughthe normal Pod CNI and node/VPC networking. Userspace networking such as
slirp4netnsis principally associated with rootless Docker. See Docker'spacket-filtering and firewall documentation.
The experiment and Compose follow-up proved nested-container outbound
connectivity, Compose service-name resolution through libc, published-port
reachability from the workspace, and same-node and cross-node reachability
through both the workspace Pod IP and a ClusterIP Service. Still untested are
CNI NetworkPolicy behavior, large-packet/MTU correctness, IPv6, and external
NodePort, LoadBalancer, or Ingress exposure.
Security comparison with Envbox/Sysbox
This approach demonstrated the same fundamental user-namespace property as
the Envbox inner container: workspace UID 0 maps to an unprivileged host UID,
and the user-controlled workspace does not run as a host-privileged container.
It is therefore reasonable to describe the two approaches as pursuing the
same core isolation objective.
The complete security postures are not yet proven equivalent. The native
approach removes Envbox's privileged outer container and the Sysbox manager,
filesystem service, and custom runtime from each workspace's trusted stack.
Kubernetes also assigned a distinct high host-UID range to each tested Pod,
rather than using Envbox's fixed
100000user-namespace offset. These may besecurity advantages.
Conversely, the successful native Pod required
ALLcapabilities inside itsuser namespace, an unmasked
/proc, an unconfined seccomp profile, and awritable delegated cgroup hierarchy. It also lacks Sysbox-specific
virtualization and mediation of system-container behavior. Those differences
must be evaluated rather than assumed equivalent.
More precisely, the successful workspace was effectively privileged inside
its own sandbox. It could administer the Pod's mounts, network namespace,
processes, delegated cgroups, nested containers, PVC contents, credentials,
and reachable network resources. This broad authority is expected for a
Docker-capable developer workspace, where the developer is intentionally
allowed complete control inside the workspace. The relevant security
requirement is therefore containment: that authority must not extend to the
node, other workspaces or their storage, cluster-wide credentials, or network
resources the workspace is not authorized to reach.
It was not effectively host-privileged.
hostUsers: falsemapped UID 0 to anunprivileged high host UID and scoped namespaced capabilities such as
CAP_SYS_ADMINandCAP_NET_ADMINto resources owned by the Pod's usernamespace; capabilities such as
CAP_SYS_MODULEcannot affect the host fromthat namespace. The manifest also did not automatically grant host UID 0,
host namespaces, arbitrary host mounts, or unrestricted host-device access.
Those are meaningful differences from a Kubernetes
privileged: truecontainer. See the upstream documentation on user-namespace capability boundaries
and privileged-container behavior.
The remaining risk is still material because all containers share the node's
kernel. An unconfined seccomp profile permits the full syscall surface,
unmasked
/procexposes interfaces normally hidden by the runtime, andALLcapabilities plus
allowPrivilegeEscalation: trueremove most defense in depthinside the namespace. A kernel or user-namespace vulnerability could cross the
intended boundary. This design therefore relies heavily on the Linux user
namespace as its primary host-security boundary: it is meaningfully safer than
host-privileged DinD, but it is not equivalent to a conventional restricted
Pod and still requires a focused security review.
The supported conclusion is therefore that the native MNG design reproduces
Envbox's fundamental non-host-root workspace boundary and may have a smaller
trusted stack, but full security equivalence requires focused escape,
cross-workspace,
/proc, cgroup, device, mount, networking, and kernel attack-surface testing.
In a separate EKS Auto Mode/Bottlerocket experiment,
the first
hostUsers: falseprobe failed because the AWS-managed Bottlerocketnode had
user.max_user_namespaces = 0. A privileged node-preparationDaemonSet, ordered with a NodePool startup taint, successfully raised that
sysctl and allowed a
hostUsers: falsePod to use an EBS PVC. The subsequent cgroupprobes nevertheless found no writable delegated hierarchy, including in the
user-namespaced privileged control. Auto Mode's supported NodeClass interface
still exposes no equivalent of the custom
cgroup_writable = truecontainerdhandler used by this successful MNG experiment.
Decision and remaining validation
This approach is now a technically credible Envbox/Sysbox alternative for
Coder workspaces on configurable EKS MNGs. It is not yet a production-readiness
or security-equivalence result.
Before recommending it, test at least:
than only successful creation with limits;
and cross-Pod isolation attempts;
configurations, Testcontainers, and devcontainer workflows;
cleanup behavior;
RuntimeClass is operationally acceptable;
ALLcapabilities, unmasked/proc,unconfined seccomp, writable cgroups, nested networking, and exposed
devices, even though these are bounded by the Pod user namespace;
privileged preparation DaemonSet overcame the tested node's initial
user.max_user_namespaces = 0, but writable cgroup delegation remainedunavailable and Auto Mode exposed no supported equivalent of the MNG's
custom containerd handler.
The runtime-wide
cgroup_writablehandler also lacks the finer per-Pod policyand cgroup-depth/descendant controls expected from a future first-class
Kubernetes writable-cgroups API. Until such an API is available and validated,
the custom handler should be limited to dedicated nodes and explicitly
authorized workloads.