From a446d73c3398699e3d5d0d1ca7151d616a575613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Wed, 15 Jul 2026 18:19:05 +0200 Subject: [PATCH] gha/test: Add userns integration mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User namespace remapping currently has no full integration-suite CI coverage. Add a graphdriver mode that starts dockerd with DOCKER_REMAP_ROOT=default. Skip tests that require privileged or host namespaces, and avoid the host network optimization in TestUpdatePidsLimit, because dockerd rejects those combinations when remapping is enabled. Signed-off-by: Paweł Gronowski --- .github/workflows/.test.yml | 3 +++ .github/workflows/test.yml | 2 +- integration/container/links_linux_test.go | 1 + integration/container/mounts_linux_test.go | 1 + integration/container/run_cgroupns_linux_test.go | 2 ++ integration/container/run_linux_test.go | 3 +++ integration/container/update_linux_test.go | 6 +++++- integration/network/bridge/bridge_linux_test.go | 2 ++ integration/network/service_test.go | 1 + 9 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/.test.yml b/.github/workflows/.test.yml index 32667d37b4..1d5fc86995 100644 --- a/.github/workflows/.test.yml +++ b/.github/workflows/.test.yml @@ -216,6 +216,9 @@ jobs: if [[ "$MODE" == *"rootless"* ]]; then echo "DOCKER_ROOTLESS=1" >> $GITHUB_ENV fi + if [[ "$MODE" == *"userns"* ]]; then + echo "DOCKER_REMAP_ROOT=default" >> $GITHUB_ENV + fi if [[ "$MODE" == *"systemd"* ]]; then echo "SYSTEMD=true" >> $GITHUB_ENV CACHE_DEV_SCOPE="dev-systemd-${ARCH}" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a6a8d852db..65f32fb02a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -96,7 +96,7 @@ jobs: - arch: amd64 storage: graphdriver runnerSuffix: "" - extraModes: '["rootless", "systemd"]' + extraModes: '["rootless", "systemd", "userns"]' - arch: arm64 storage: snapshotter runnerSuffix: "-arm" diff --git a/integration/container/links_linux_test.go b/integration/container/links_linux_test.go index 3bb4f49e80..a22eac6400 100644 --- a/integration/container/links_linux_test.go +++ b/integration/container/links_linux_test.go @@ -13,6 +13,7 @@ import ( func TestLinksEtcHostsContentMatch(t *testing.T) { skip.If(t, testEnv.IsRemoteDaemon) + skip.If(t, testEnv.IsUserNamespace, "host network mode is incompatible with user namespaces") hosts, err := os.ReadFile("/etc/hosts") skip.If(t, os.IsNotExist(err)) diff --git a/integration/container/mounts_linux_test.go b/integration/container/mounts_linux_test.go index ffdd664ab2..d725c7a0f3 100644 --- a/integration/container/mounts_linux_test.go +++ b/integration/container/mounts_linux_test.go @@ -350,6 +350,7 @@ func TestMountDaemonRoot(t *testing.T) { func TestContainerBindMountNonRecursive(t *testing.T) { skip.If(t, testEnv.IsRemoteDaemon) skip.If(t, testEnv.IsRootless, "cannot be tested because RootlessKit executes the daemon in private mount namespace (https://github.com/rootless-containers/rootlesskit/issues/97)") + skip.If(t, testEnv.IsUserNamespace, "non-recursive bind mounts of directories containing submounts fail with EINVAL in a user namespace") ctx := setupTest(t) diff --git a/integration/container/run_cgroupns_linux_test.go b/integration/container/run_cgroupns_linux_test.go index 004068545f..c7cb9e1856 100644 --- a/integration/container/run_cgroupns_linux_test.go +++ b/integration/container/run_cgroupns_linux_test.go @@ -60,6 +60,7 @@ func TestCgroupNamespacesRunPrivileged(t *testing.T) { skip.If(t, testEnv.IsRemoteDaemon()) skip.If(t, !requirement.CgroupNamespacesEnabled()) skip.If(t, testEnv.DaemonInfo.CgroupVersion == "2", "on cgroup v2, privileged containers use private cgroupns") + skip.If(t, testEnv.IsUserNamespace, "privileged mode is incompatible with user namespaces") t.Parallel() @@ -120,6 +121,7 @@ func TestCgroupNamespacesRunPrivilegedAndPrivate(t *testing.T) { skip.If(t, testEnv.DaemonInfo.OSType != "linux") skip.If(t, testEnv.IsRemoteDaemon()) skip.If(t, !requirement.CgroupNamespacesEnabled()) + skip.If(t, testEnv.IsUserNamespace, "privileged mode is incompatible with user namespaces") t.Parallel() diff --git a/integration/container/run_linux_test.go b/integration/container/run_linux_test.go index 9c349e120a..cfdd936bb3 100644 --- a/integration/container/run_linux_test.go +++ b/integration/container/run_linux_test.go @@ -33,6 +33,7 @@ import ( func TestNISDomainname(t *testing.T) { skip.If(t, testEnv.DaemonInfo.OSType != "linux") + skip.If(t, testEnv.IsUserNamespace, "user namespaces cannot write the kernel domainname sysctl") // Rootless supports custom Hostname but doesn't support custom Domainname // OCI runtime create failed: container_linux.go:349: starting container process caused "process_linux.go:449: container init caused \ @@ -136,6 +137,7 @@ func TestPrivilegedHostDevices(t *testing.T) { // so needs to be same host. skip.If(t, testEnv.IsRemoteDaemon) skip.If(t, testEnv.DaemonInfo.OSType != "linux") + skip.If(t, testEnv.IsUserNamespace, "privileged mode is incompatible with user namespaces") ctx := setupTest(t) apiClient := testEnv.APIClient() @@ -375,6 +377,7 @@ func TestWorkingDirNormalization(t *testing.T) { func TestSeccomp(t *testing.T) { skip.If(t, testEnv.DaemonInfo.OSType != "linux") + skip.If(t, testEnv.IsUserNamespace, "privileged test cases are incompatible with user namespaces") ctx := setupTest(t) apiClient := testEnv.APIClient() diff --git a/integration/container/update_linux_test.go b/integration/container/update_linux_test.go index 5e91865ca3..91e15c16e3 100644 --- a/integration/container/update_linux_test.go +++ b/integration/container/update_linux_test.go @@ -186,8 +186,12 @@ func TestUpdatePidsLimit(t *testing.T) { t.Run(test.desc, func(t *testing.T) { ctx := testutil.StartSpan(ctx, t) + opts := []func(*container.TestContainerConfig){container.WithPidsLimit(test.initial)} // Using "network=host" to speed up creation (13.96s vs 6.54s) - cID := container.Run(ctx, t, apiClient, container.WithPidsLimit(test.initial), container.WithNetworkMode("host")) + if !testEnv.IsUserNamespace() { + opts = append(opts, container.WithNetworkMode("host")) + } + cID := container.Run(ctx, t, apiClient, opts...) _, err := c.ContainerUpdate(ctx, cID, client.ContainerUpdateOptions{ Resources: &containertypes.Resources{ diff --git a/integration/network/bridge/bridge_linux_test.go b/integration/network/bridge/bridge_linux_test.go index c344457f02..dfee01d873 100644 --- a/integration/network/bridge/bridge_linux_test.go +++ b/integration/network/bridge/bridge_linux_test.go @@ -1231,6 +1231,8 @@ func TestBridgeIPAMStatus(t *testing.T) { // rolled back properly - the failed connection should not show up in container // or network inspect, and the container should not gain a network interface. func TestJoinError(t *testing.T) { + skip.If(t, testEnv.IsUserNamespace, "privileged mode is incompatible with user namespaces") + ctx := setupTest(t) d := daemon.New(t) d.StartWithBusybox(ctx, t) diff --git a/integration/network/service_test.go b/integration/network/service_test.go index b1b1c76957..23d725db7d 100644 --- a/integration/network/service_test.go +++ b/integration/network/service_test.go @@ -226,6 +226,7 @@ func TestDaemonWithBipAndDefaultNetworkPool(t *testing.T) { func TestServiceWithPredefinedNetwork(t *testing.T) { skip.If(t, testEnv.DaemonInfo.OSType == "windows") skip.If(t, testEnv.IsRootless, "rootless mode doesn't support Swarm-mode") + skip.If(t, testEnv.IsUserNamespace, "host network mode is incompatible with user namespaces") ctx := setupTest(t) d := swarm.NewSwarm(ctx, t, testEnv)