3737 - suite : rust-docker
3838 cmd : " mise run --no-deps --skip-deps e2e:rust"
3939 apt_packages : " openssh-client"
40- - suite : rust-podman
41- cmd : " mise run --no-deps --skip-deps e2e:podman"
42- apt_packages : " openssh-client podman"
43- - suite : rust-podman-rootless
44- cmd : " mise run --no-deps --skip-deps e2e:podman:rootless"
45- apt_packages : " openssh-client podman uidmap"
46- rootless : true
4740 - suite : mcp
4841 cmd : " mise run --no-deps --skip-deps e2e:mcp"
4942 apt_packages : " "
8982 - name : Log in to GHCR with Docker
9083 run : echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
9184
92- - name : Log in to GHCR with Podman
93- if : startsWith(matrix.suite, 'rust-podman')
94- run : echo "${{ secrets.GITHUB_TOKEN }}" | podman login ghcr.io -u "${{ github.actor }}" --password-stdin
95-
96- - name : Set up rootless Podman user
97- if : matrix.rootless
98- run : |
99- useradd -m openshell-test
100- echo "openshell-test:100000:65536" >> /etc/subuid
101- echo "openshell-test:100000:65536" >> /etc/subgid
102- mkdir -p "/run/user/$(id -u openshell-test)"
103- chown openshell-test: "/run/user/$(id -u openshell-test)"
104- chmod 700 "/run/user/$(id -u openshell-test)"
105- chown -R openshell-test: .
106- mkdir -p /home/openshell-test/.cache/mise /home/openshell-test/.cargo /home/openshell-test/.local/state/mise
107- chown -R openshell-test: /home/openshell-test/.cache /home/openshell-test/.cargo /home/openshell-test/.local
108- install -m 0755 "$(command -v mise)" /usr/local/bin/mise
109- chmod a+x /root /root/.local /root/.local/bin
110- for dir in /root/.cargo /root/.rustup /root/.local/share/mise /opt/mise; do
111- [ -d "$dir" ] && chmod -R a+rX "$dir"
112- done
113-
11485 - name : Install Python dependencies and generate protobuf stubs
11586 if : matrix.suite == 'python'
11687 run : uv sync --frozen && mise run --no-deps python:proto
@@ -119,27 +90,118 @@ jobs:
11990 env :
12091 OPENSHELL_SUPERVISOR_IMAGE : ${{ format('ghcr.io/nvidia/openshell/supervisor:{0}', inputs.image-tag) }}
12192 OPENSHELL_MCP_CONFORMANCE_CLIENT_IMAGE : ${{ format('openshell-mcp-conformance-client:{0}', inputs.image-tag) }}
122- E2E_CMD : ${{ matrix.cmd }}
93+ run : ${{ matrix.cmd }}
94+
95+ e2e-podman-rootless :
96+ name : E2E (rust-podman-rootless, ${{ matrix.runner }})
97+ # Run directly on the Ubuntu host so the test observes the host's AppArmor
98+ # and unprivileged-user-namespace policy. A privileged job container masks
99+ # the restrictions that production rootless Podman installations enforce.
100+ runs-on : ${{ matrix.runner }}
101+ timeout-minutes : 30
102+ strategy :
103+ fail-fast : false
104+ matrix :
105+ include :
106+ # Ubuntu 24.04 matches the environment reported in #2069 and ships
107+ # Podman 4.x. The probe records whether AppArmor blocks the drop.
108+ - runner : ubuntu-24.04
109+ podman_major : " 4"
110+ # Ubuntu 26.04 provides the supported Podman 5.x coverage for
111+ # comparison with the Ubuntu 24.04 environment.
112+ - runner : ubuntu-26.04
113+ podman_major : " 5"
114+ env :
115+ IMAGE_TAG : ${{ inputs.image-tag }}
116+ MISE_GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
117+ OPENSHELL_REGISTRY : ghcr.io/nvidia/openshell
118+ OPENSHELL_REGISTRY_HOST : ghcr.io
119+ OPENSHELL_REGISTRY_NAMESPACE : nvidia/openshell
120+ OPENSHELL_REGISTRY_USERNAME : ${{ github.actor }}
121+ OPENSHELL_REGISTRY_PASSWORD : ${{ secrets.GITHUB_TOKEN }}
122+ OPENSHELL_SUPERVISOR_IMAGE : ${{ format('ghcr.io/nvidia/openshell/supervisor:{0}', inputs.image-tag) }}
123+ steps :
124+ - uses : actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
125+ with :
126+ ref : ${{ inputs['checkout-ref'] || github.sha }}
127+ persist-credentials : false
128+
129+ - name : Install mise
130+ run : |
131+ curl https://mise.run | MISE_VERSION=v2026.4.25 sh
132+ echo "$HOME/.local/bin" >> "$GITHUB_PATH"
133+ echo "$HOME/.local/share/mise/shims" >> "$GITHUB_PATH"
134+
135+ - name : Install tools
136+ run : mise install --locked
137+
138+ - name : Install Podman and build dependencies
139+ run : |
140+ sudo apt-get update
141+ sudo apt-get install -y --no-install-recommends \
142+ build-essential \
143+ clang \
144+ fuse-overlayfs \
145+ libssl-dev \
146+ libz3-dev \
147+ openssh-client \
148+ passt \
149+ pkg-config \
150+ podman \
151+ slirp4netns \
152+ uidmap
153+
154+ - name : Configure rootless Podman
123155 run : |
124- if [ "${{ matrix.rootless }}" = "true" ]; then
125- TESTUID="$(id -u openshell-test)"
126- runuser -u openshell-test -- env \
127- XDG_RUNTIME_DIR="/run/user/${TESTUID}" \
128- HOME="/home/openshell-test" \
129- PATH="/usr/local/bin:/root/.cargo/bin:/opt/mise/shims:/root/.local/bin:${PATH}" \
130- CARGO_HOME="/home/openshell-test/.cargo" \
131- RUSTUP_HOME="/root/.rustup" \
132- MISE_DATA_DIR="/opt/mise" \
133- MISE_CACHE_DIR="/home/openshell-test/.cache/mise" \
134- MISE_STATE_DIR="/home/openshell-test/.local/state/mise" \
135- OPENSHELL_SUPERVISOR_IMAGE="${OPENSHELL_SUPERVISOR_IMAGE}" \
136- OPENSHELL_REGISTRY="${OPENSHELL_REGISTRY}" \
137- OPENSHELL_REGISTRY_HOST="${OPENSHELL_REGISTRY_HOST}" \
138- OPENSHELL_REGISTRY_USERNAME="${OPENSHELL_REGISTRY_USERNAME}" \
139- OPENSHELL_REGISTRY_PASSWORD="${OPENSHELL_REGISTRY_PASSWORD}" \
140- IMAGE_TAG="${IMAGE_TAG}" \
141- MISE_GITHUB_TOKEN="${MISE_GITHUB_TOKEN}" \
142- bash -c "${E2E_CMD}"
143- else
144- ${E2E_CMD}
156+ set -euo pipefail
157+ if ! grep -q "^${USER}:" /etc/subuid; then
158+ sudo usermod --add-subuids 100000-165535 "$USER"
145159 fi
160+ if ! grep -q "^${USER}:" /etc/subgid; then
161+ sudo usermod --add-subgids 100000-165535 "$USER"
162+ fi
163+ runtime_dir="/run/user/$(id -u)"
164+ sudo install -d -m 0700 -o "$(id -u)" -g "$(id -g)" "$runtime_dir"
165+ echo "XDG_RUNTIME_DIR=$runtime_dir" >> "$GITHUB_ENV"
166+
167+ - name : Verify rootless Podman environment
168+ run : |
169+ set -euo pipefail
170+ podman_version="$(podman version --format '{{.Client.Version}}')"
171+ case "$podman_version" in
172+ "${{ matrix.podman_major }}".*) ;;
173+ *) echo "ERROR: expected Podman ${{ matrix.podman_major }}.x, found $podman_version" >&2; exit 1 ;;
174+ esac
175+ test "$(podman info --format '{{.Host.Security.Rootless}}')" = "true"
176+ test "$(sudo sysctl -n kernel.apparmor_restrict_unprivileged_userns)" = "1"
177+ echo "=== host ==="
178+ uname -a
179+ echo "=== AppArmor ==="
180+ cat /proc/self/attr/current
181+ sudo aa-status || true
182+ echo "=== Podman ==="
183+ podman version
184+ podman info --debug
185+
186+ - name : Probe rootless capability bounding set
187+ run : |
188+ set -euo pipefail
189+ probe="$RUNNER_TEMP/openshell-capbset-probe"
190+ cc -static -O2 -Wall -Wextra -Werror \
191+ e2e/support/capbset-probe.c \
192+ -o "$probe"
193+ podman run --rm \
194+ --cap-add=SETPCAP \
195+ --volume "$probe:/openshell-capbset-probe:ro" \
196+ docker.io/library/alpine:3.22 \
197+ /openshell-capbset-probe
198+
199+ - name : Log in to GHCR with Podman
200+ run : echo "${{ secrets.GITHUB_TOKEN }}" | podman login ghcr.io -u "${{ github.actor }}" --password-stdin
201+
202+ - name : Run rootless Podman E2E
203+ run : mise run --no-deps --skip-deps e2e:podman:rootless
204+
205+ - name : Print AppArmor denials
206+ if : always()
207+ run : sudo dmesg | grep -E 'apparmor=.*DENIED|profile="unprivileged_userns"' | tail -100 || true
0 commit comments