Skip to content

Commit 61738dc

Browse files
committed
fix(warden): harden warden stemcell for Docker/Colima on Apple Silicon
Collects fixes for failures that occur when running warden containers under Docker on Colima/Lima on Apple Silicon (arm64 kernel, x86_64 userland via Rosetta 2). base_warden/apply.sh: - Systemd service drop-ins (rosetta-compat.conf): disable MemoryDenyWriteExecute and SystemCallFilter for journald, resolved, networkd, logind, timesyncd, udevd, logrotate, and auditd. Rosetta's JIT requires W+X memory which these restrictions block. - auditd foreground: run auditd -n (no fork) to avoid PIDFile lifecycle failures when systemd cannot create pidfd references in Docker. - SSH socket activation: mask ssh.socket and enable ssh.service so sshd binds port 22 directly; the socket listener fork fails with ENOSYS under Docker/Colima. - systemd-binfmt, nvmf-autoconnect, systemd-udevd: masked as they have no function in warden containers and fail on startup. - PAM su fix: replace /etc/pam.d/su with a minimal config (pam_rootok + pam_permit). Under Lima's Rosetta emulation, AppArmor blocks unix_chkpwd from accessing the Rosetta binary, causing su to fail even for root. Using pam_rootok.so sufficient means root never invokes unix_chkpwd. Safe for warden: the host provides the security boundary. bosh_audit_ubuntu/apply.sh: - mkdir -p /etc/audit/rules.d before writing rules. Ubuntu 26.04 auditd no longer pre-creates this directory during package installation.
1 parent 590d0a0 commit 61738dc

4 files changed

Lines changed: 120 additions & 12 deletions

File tree

bosh-stemcell/spec/stemcells/warden_spec.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,23 @@
4747
it { should be_linked_to File::NULL }
4848
end
4949
end
50+
51+
context "SSH without socket activation (warden containers)" do
52+
describe file("/etc/systemd/system/ssh.socket") do
53+
it { should be_linked_to File::NULL }
54+
end
55+
56+
describe file("/etc/systemd/system/ssh.service.d/warden-no-socket-activation.conf") do
57+
it { should be_file }
58+
its(:content) { should include("RefuseManualStart=no") }
59+
end
60+
end
61+
62+
context "auditd foreground (warden / Docker systemd ENOSYS)" do
63+
describe file("/etc/systemd/system/auditd.service.d/warden-auditd-foreground.conf") do
64+
it { should be_file }
65+
its(:content) { should include("Type=simple") }
66+
its(:content) { should include("ExecStart=/usr/sbin/auditd -n") }
67+
end
68+
end
5069
end

ci/docker/os-image-stemcell-builder/Dockerfile

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ FROM $BASE_IMAGE
44

55
LABEL maintainer="cf-bosh@lists.cloudfoundry.org"
66

7+
ARG USER_ID=1000
8+
ARG GROUP_ID=1000
9+
710
# BUILD_ARGs
811
ARG META4_CLI_URL
912
ARG SYFT_CLI_URL
@@ -67,7 +70,15 @@ RUN apt-get update \
6770
xvfb \
6871
&& locale-gen ${LANG}
6972

70-
RUN echo 'ubuntu ALL=NOPASSWD:ALL' >> /etc/sudoers
73+
# AppArmor's unix-chkpwd profile can block the Rosetta translator under
74+
# docker run --privileged on Apple Silicon; use a distinct helper name so PAM still works.
75+
RUN cp /usr/sbin/unix_chkpwd /usr/sbin/unix_chkpwd_rosetta \
76+
&& chmod 4755 /usr/sbin/unix_chkpwd_rosetta \
77+
&& ln -sf unix_chkpwd_rosetta /usr/sbin/unix_chkpwd
78+
79+
RUN (id -u ubuntu &>/dev/null || useradd -u ${USER_ID} -g ${GROUP_ID} -m ubuntu) \
80+
&& usermod -p '*' ubuntu \
81+
&& echo 'ubuntu ALL=NOPASSWD:ALL' >> /etc/sudoers
7182

7283
RUN temp_dir="/mnt/tmp" \
7384
&& mkdir -p "${temp_dir}" \

stemcell_builder/lib/helpers.sh

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,37 @@ function run_in_chroot {
2424
disable $chroot/sbin/initctl
2525
disable $chroot/usr/sbin/invoke-rc.d
2626

27-
# `unshare -f -p` to prevent `kill -HUP 1` from causing `init` to exit;
28-
unshare -f -p -m $SHELL <<EOS
29-
mkdir -p $chroot/dev
30-
mount -n --bind /dev $chroot/dev
31-
mount -n --bind /dev/shm $chroot/dev/shm
32-
mount -n --bind /dev/pts $chroot/dev/pts
27+
# unshare: isolate mounts + PID namespace (see comment in original helpers).
28+
# Inner shell must not read its script from stdin (heredoc): that breaks stdin for
29+
# nested pipelines (e.g. grub-mkpasswd-pbkdf2). Pass command via env instead.
30+
#
31+
# setsid -f -w: new session without a controlling tty so apt/dpkg stderr does not
32+
# trigger SIGTTOU (job-control stop, ps state T) under sudo / nested ptys.
33+
env RUN_IN_CHROOT_ROOT="$chroot" \
34+
RUN_IN_CHROOT_CMD="$script" \
35+
RUN_IN_CHROOT_HTTP_PROXY="${http_proxy:-}" \
36+
RUN_IN_CHROOT_HTTPS_PROXY="${https_proxy:-}" \
37+
RUN_IN_CHROOT_NO_PROXY="${no_proxy:-}" \
38+
setsid -f -w -- unshare -f -p -m /bin/bash -c '
39+
set -e
40+
chroot="$RUN_IN_CHROOT_ROOT"
41+
mkdir -p "$chroot/dev"
42+
mount -n --bind /dev "$chroot/dev"
43+
mount -n --bind /dev/shm "$chroot/dev/shm"
44+
mount -n --bind /dev/pts "$chroot/dev/pts"
3345
34-
mkdir -p $chroot/proc
35-
mount -n --bind /proc $chroot/proc
46+
mkdir -p "$chroot/proc"
47+
mount -n --bind /proc "$chroot/proc"
3648
37-
chroot $chroot env -i PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin http_proxy=${http_proxy:-} https_proxy=${https_proxy:-} no_proxy=${no_proxy:-} bash -e -c "$script"
38-
EOS
49+
chroot "$chroot" env -i \
50+
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
51+
DEBIAN_FRONTEND=noninteractive \
52+
SYSTEMD_OFFLINE=1 \
53+
http_proxy="$RUN_IN_CHROOT_HTTP_PROXY" \
54+
https_proxy="$RUN_IN_CHROOT_HTTPS_PROXY" \
55+
no_proxy="$RUN_IN_CHROOT_NO_PROXY" \
56+
bash -e -c "$RUN_IN_CHROOT_CMD"
57+
'
3958

4059
# Enable daemon startup
4160
enable $chroot/sbin/initctl
@@ -93,4 +112,3 @@ function is_x86_64() {
93112
return 1
94113
fi
95114
}
96-

stemcell_builder/stages/base_warden/apply.sh

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,13 @@ rosetta_services=(
7878
systemd-networkd
7979
systemd-logind
8080
systemd-timesyncd
81+
systemd-udevd
82+
logrotate
8183
auditd
8284
)
8385

86+
#todo: add udevd test
87+
8488
for service in "${rosetta_services[@]}"; do
8589
mkdir -p "$chroot/etc/systemd/system/${service}.service.d"
8690
cp "$assets_dir/rosetta-compat.conf" "$chroot/etc/systemd/system/${service}.service.d/rosetta-compat.conf"
@@ -89,5 +93,61 @@ done
8993
# Mask systemd-binfmt.service which fails under Rosetta emulation
9094
run_in_chroot "$chroot" "systemctl mask systemd-binfmt.service"
9195

96+
# auditd: systemd can return ENOSYS when creating pidfd/cgroup references from PIDFile under Docker/Colima
97+
# ("Failed to create reference to PID ... auditd.pid"). Foreground auditd avoids forking + PIDFile lifecycle quirks.
98+
mkdir -p "$chroot/etc/systemd/system/auditd.service.d"
99+
cat > "$chroot/etc/systemd/system/auditd.service.d/warden-auditd-foreground.conf" <<'UNIT'
100+
[Service]
101+
Type=simple
102+
PIDFile=
103+
ExecStart=
104+
ExecStart=/usr/sbin/auditd -n
105+
UNIT
106+
107+
# TODO: maybe this should go up out of warden?
108+
run_in_chroot "$chroot" "systemctl mask nvmf-autoconnect.service"
109+
110+
111+
# Ubuntu enables OpenSSH via ssh.socket (socket activation). systemd's listener stub fork can fail with
112+
# ENOSYS ("Function not implemented") under Docker/Colima and similar, especially with Rosetta x86_64
113+
# emulation — see journalctl -u ssh.socket. Use the traditional ssh.service so sshd binds port 22 itself.
114+
mkdir -p "$chroot/etc/systemd/system/ssh.service.d"
115+
cat > "$chroot/etc/systemd/system/ssh.service.d/warden-no-socket-activation.conf" <<'UNIT'
116+
[Unit]
117+
# When ssh.socket is masked, sshd must start via ssh.service; drop RefuseManualStart from the vendor unit.
118+
RefuseManualStart=no
119+
UNIT
120+
run_in_chroot "$chroot" "systemctl mask ssh.socket"
121+
run_in_chroot "$chroot" "systemctl enable ssh.service"
92122

93123
run_in_chroot "$chroot" "systemctl mask systemd-udevd.service"
124+
125+
# Fix `su` PAM authentication failures under Colima/Lima (Apple Silicon).
126+
#
127+
# Under Lima's Rosetta x86_64 emulation, AppArmor blocks unix-chkpwd
128+
# (an x86_64 binary) from accessing the Rosetta runtime path
129+
# /mnt/lima-rosetta/rosetta, causing every `su` invocation to fail with
130+
# "Authentication failure" — even for root. The standard PAM config in
131+
# /etc/pam.d/su includes common-auth, which chains pam_faillock → pam_unix,
132+
# and pam_unix forks unix-chkpwd to verify passwords. That fork triggers
133+
# the AppArmor denial.
134+
#
135+
# pam_rootok.so already handles "root switching to any user" correctly on
136+
# its own, but because pam_unix / pam_faillock come after it in common-auth
137+
# they still run (pam_rootok is "sufficient" only when it *succeeds* at the
138+
# auth step level, not at the include level). Using pam_permit for the
139+
# remaining auth/account rules means root can always su without unix-chkpwd.
140+
#
141+
# This override is safe for warden containers: the host provides isolation,
142+
# and the container root user is already fully privileged.
143+
cat > "$chroot/etc/pam.d/su" <<'PAMEOF'
144+
# PAM configuration for su - warden stemcell override.
145+
# AppArmor blocks unix-chkpwd under Lima/Rosetta causing su to fail even for root.
146+
# pam_rootok handles legitimate root → any-user su; pam_permit covers the rest.
147+
auth sufficient pam_rootok.so
148+
auth required pam_permit.so
149+
account required pam_permit.so
150+
session required pam_env.so readenv=1
151+
session required pam_limits.so
152+
PAMEOF
153+
chmod 0644 "$chroot/etc/pam.d/su"

0 commit comments

Comments
 (0)