Skip to content

Commit 7854e97

Browse files
author
Roy Lin
committed
ci: use a userns-reachable native fixture root
1 parent aa551d5 commit 7854e97

2 files changed

Lines changed: 21 additions & 98 deletions

File tree

.github/scripts/native-linux-smoke.sh

Lines changed: 12 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,8 @@
22

33
set -euo pipefail
44

5-
runner_temp="${RUNNER_TEMP:?RUNNER_TEMP must identify the CI job temporary directory}"
6-
run_id="${GITHUB_RUN_ID:?GITHUB_RUN_ID must identify the CI run}"
7-
run_attempt="${GITHUB_RUN_ATTEMPT:?GITHUB_RUN_ATTEMPT must identify the CI attempt}"
8-
apparmor_userns_policy="/proc/sys/kernel/apparmor_restrict_unprivileged_userns"
9-
apparmor_profile_name="a3s-oci-agent-ci"
10-
apparmor_profile_file="$runner_temp/a3s-oci-agent-ci.apparmor"
11-
apparmor_profile_loaded=false
12-
saved_kvm="/dev/a3s-oci-kvm-${run_id}-${run_attempt}"
5+
qualification_root=""
6+
saved_kvm="/dev/a3s-oci-kvm-$$"
137
kvm_original_moved=false
148
kvm_test_directory_created=false
159

@@ -20,20 +14,15 @@ restore_host() {
2014
trap - EXIT
2115
set +e
2216

23-
if [[ "$apparmor_profile_loaded" == true ]]; then
24-
sudo apparmor_parser -R "$apparmor_profile_file"
17+
if [[ "$kvm_test_directory_created" == true && -d /dev/kvm ]]; then
18+
sudo rmdir /dev/kvm
2519
status=$?
2620
if ((status != 0)); then
2721
cleanup_status=$status
2822
fi
2923
fi
30-
rm -f "$apparmor_profile_file"
31-
status=$?
32-
if ((status != 0)); then
33-
cleanup_status=$status
34-
fi
35-
if [[ "$kvm_test_directory_created" == true && -d /dev/kvm ]]; then
36-
sudo rmdir /dev/kvm
24+
if [[ -n "$qualification_root" && -d "$qualification_root" ]]; then
25+
sudo rm -rf --one-file-system "$qualification_root"
3726
status=$?
3827
if ((status != 0)); then
3928
cleanup_status=$status
@@ -53,45 +42,9 @@ restore_host() {
5342
}
5443
trap restore_host EXIT
5544

56-
prepare_github_user_namespace_profile() {
57-
local agent_path
58-
59-
if [[ "${GITHUB_ACTIONS:-}" != "true" || ! -r "$apparmor_userns_policy" ]]; then
60-
return
61-
fi
62-
printf 'AppArmor unprivileged user namespace restriction: %s\n' \
63-
"$(<"$apparmor_userns_policy")"
64-
if [[ "$(<"$apparmor_userns_policy")" != "1" ]]; then
65-
return
66-
fi
67-
68-
agent_path="$(realpath "$PWD/target/debug/a3s-oci-agent")"
69-
if [[ "$agent_path" == *\"* ||
70-
"$agent_path" == *$'\n'* ||
71-
"$agent_path" == *$'\r'* ]]; then
72-
printf 'The AppArmor attachment path cannot be represented safely: %s\n' \
73-
"$agent_path" >&2
74-
return 1
75-
fi
76-
77-
{
78-
printf '%s\n' 'abi <abi/4.0>,'
79-
printf '%s\n\n' 'include <tunables/global>'
80-
printf 'profile %s "%s" flags=(unconfined) {\n' \
81-
"$apparmor_profile_name" "$agent_path"
82-
printf '%s\n' ' userns,'
83-
printf '%s\n' '}'
84-
} >"$apparmor_profile_file"
85-
sudo apparmor_parser -r -W "$apparmor_profile_file"
86-
apparmor_profile_loaded=true
87-
printf 'Loaded temporary AppArmor profile %s for %s\n' \
88-
"$apparmor_profile_name" "$agent_path"
89-
}
90-
9145
sudo apt-get update
9246
sudo apt-get install --yes busybox-static jq
9347
cargo build -p a3s-oci-agent -p a3s-oci-cli
94-
prepare_github_user_namespace_profile
9548

9649
features="$("$PWD/target/debug/a3s-oci" features)"
9750
printf '%s\n' "$features"
@@ -106,9 +59,10 @@ jq --exit-status \
10659
)' \
10760
<<<"$features" >/dev/null
10861

109-
bundle="$runner_temp/a3s-native-bundle"
110-
bundle_b="$runner_temp/a3s-native-bundle-b"
111-
work_parent="$runner_temp/a3s-native-work"
62+
qualification_root="$(mktemp -d /var/tmp/a3s-oci-native.XXXXXXXX)"
63+
bundle="$qualification_root/bundle"
64+
bundle_b="$qualification_root/bundle-b"
65+
work_parent="$qualification_root/work"
11266
mkdir -p \
11367
"$bundle/rootfs/bin" "$bundle/rootfs/proc" \
11468
"$bundle_b/rootfs/bin" "$bundle_b/rootfs/proc" \
@@ -118,7 +72,8 @@ for candidate in "$bundle" "$bundle_b"; do
11872
cp "$(command -v busybox)" "$candidate/rootfs/bin/busybox"
11973
ln -s busybox "$candidate/rootfs/bin/sh"
12074
done
121-
sudo chown -R 0:0 "$bundle/rootfs" "$bundle_b/rootfs"
75+
sudo chown -R 0:0 "$qualification_root"
76+
sudo chmod 0755 "$qualification_root"
12277

12378
report_native_failure() {
12479
local rootfs="$1"
@@ -137,10 +92,6 @@ report_native_failure() {
13792
sudo sh -c \
13893
'grep -E "^(NoNewPrivs|Seccomp|Cap(Inh|Prm|Eff|Bnd|Amb)):" /proc/self/status' ||
13994
true
140-
if command -v aa-status >/dev/null; then
141-
sudo aa-status || true
142-
fi
143-
14495
if sudo timeout 10s unshare \
14596
--user --map-root-user --mount --fork -- \
14697
sh -c \
@@ -165,33 +116,6 @@ report_native_failure() {
165116
"$status"
166117
fi
167118

168-
if [[ "$apparmor_profile_loaded" == true ]] &&
169-
command -v aa-exec >/dev/null; then
170-
if sudo timeout 10s aa-exec -p "$apparmor_profile_name" -- \
171-
unshare --user --map-root-user --mount --fork -- \
172-
sh -c \
173-
'printf "Profile-qualified bind context: "; cat /proc/self/attr/current; mount --make-rprivate / && mount --bind "$1" "$1"' \
174-
sh "$rootfs"; then
175-
printf '%s\n' 'Profile-qualified user/mount namespace bind probe: succeeded'
176-
else
177-
status=$?
178-
printf 'Profile-qualified user/mount namespace bind probe: failed (%s)\n' \
179-
"$status"
180-
fi
181-
182-
if sudo timeout 10s aa-exec -p "$apparmor_profile_name" -- \
183-
unshare --user --map-root-user --mount --fork -- \
184-
sh -c \
185-
'printf "Profile-qualified probe context: "; cat /proc/self/attr/current; mount --make-rprivate / && mount --rbind "$1" "$1"' \
186-
sh "$rootfs"; then
187-
printf '%s\n' 'Profile-qualified user/mount namespace rbind probe: succeeded'
188-
else
189-
status=$?
190-
printf 'Profile-qualified user/mount namespace rbind probe: failed (%s)\n' \
191-
"$status"
192-
fi
193-
fi
194-
195119
sudo dmesg --ctime 2>/dev/null | tail -n 120 || true
196120
}
197121

docs/linux-native.md

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -123,14 +123,13 @@ that path, which is present but unusable as a KVM device. The script validates
123123
the corresponding `kvm_device_present` report field and restores any original
124124
device after the test.
125125

126-
Ubuntu 24.04 GitHub-hosted runners enable an AppArmor policy that rejects the
127-
fixture's mount operations inside its new user namespace. Only when
128-
`GITHUB_ACTIONS=true`, the smoke script loads a temporary profile attached to
129-
the exact built `a3s-oci-agent` path. The profile grants the AppArmor `userns`
130-
permission without changing the system-wide restriction and is removed on
131-
exit. The runtime itself never changes host security policy. A production host
132-
must provide an appropriate narrow LSM policy for the requested OCI profile; a
133-
denied mount fails the create operation.
126+
The fixture is created beneath a private `/var/tmp` directory whose complete
127+
ancestor chain is searchable by the mapped host root identity. This is required
128+
after entering the child user namespace: its capabilities no longer bypass
129+
mode bits owned by the initial user namespace. The script does not weaken
130+
AppArmor or another host security policy. A production rootfs must likewise be
131+
reachable by its configured host mappings; an inaccessible ancestor or an LSM
132+
denial fails the create operation.
134133

135134
Run the same gate on a supported Ubuntu host:
136135

@@ -140,8 +139,8 @@ bash .github/scripts/native-linux-smoke.sh
140139

141140
The script installs `busybox-static` and `jq`, builds the matching
142141
`a3s-oci-agent` and CLI binaries, constructs the checked-in fixture with a
143-
root-owned rootfs and `/proc` mount target, and executes both KVM-independent
144-
cases.
142+
root-owned, searchable rootfs and `/proc` mount target, executes both
143+
KVM-independent cases, and removes its qualification directory on exit.
145144

146145
## Multi-container generation gate
147146

0 commit comments

Comments
 (0)