Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2b68004ae | ||
|
|
8454c8e407 | ||
|
|
f931705971 |
@@ -103,39 +103,6 @@ EOF
|
|||||||
rm -rf "$work"
|
rm -rf "$work"
|
||||||
}
|
}
|
||||||
|
|
||||||
test_user() {
|
|
||||||
echo "=== Testing USER directive ==="
|
|
||||||
|
|
||||||
work="$(mktemp -d)"
|
|
||||||
img="slim-test-user"
|
|
||||||
|
|
||||||
cat > "$work/Containerfile" <<'EOF'
|
|
||||||
FROM alpine:latest
|
|
||||||
RUN adduser -D -u 1500 testuser
|
|
||||||
USER testuser
|
|
||||||
CMD ["/bin/sh", "-c", "echo USER_BUILD_OK:$(id -u):$(whoami); poweroff -f"]
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo "-- Building container image with USER directive..."
|
|
||||||
if ! podman build --network=none -t "$img" -f "$work/Containerfile" >/dev/null 2>&1; then
|
|
||||||
report fail "USER directive (podman build failed)"
|
|
||||||
rm -rf "$work"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "-- Test 1: build-time CMD runs as USER"
|
|
||||||
"$SLIM_BIN" build qcow2 "$img" >/dev/null 2>&1
|
|
||||||
output=$(timeout "$TIMEOUT" "$SLIM_BIN" run "$img" 2>&1 || true)
|
|
||||||
check_output "$output" "USER_BUILD_OK:1500:testuser" "USER build-time CMD runs as testuser"
|
|
||||||
|
|
||||||
echo "-- Test 2: run --cmd override runs as USER"
|
|
||||||
output=$(timeout "$TIMEOUT" "$SLIM_BIN" run "$img" --cmd 'echo USER_RUN_OK:$(id -u):$(whoami); poweroff -f' 2>&1 || true)
|
|
||||||
check_output "$output" "USER_RUN_OK:1500:testuser" "USER run --cmd override runs as testuser"
|
|
||||||
|
|
||||||
cleanup "$img" "$img"
|
|
||||||
rm -rf "$work"
|
|
||||||
}
|
|
||||||
|
|
||||||
test_mount() {
|
test_mount() {
|
||||||
echo "=== Testing --mount (9p shares) ==="
|
echo "=== Testing --mount (9p shares) ==="
|
||||||
|
|
||||||
@@ -301,8 +268,6 @@ cargo build 2>&1
|
|||||||
test_distro "alpine" "alpine:latest" ""
|
test_distro "alpine" "alpine:latest" ""
|
||||||
test_distro "archlinux" "archlinux:latest" "RUN pacman -Sy --noconfirm iproute2 wget; pacman -Sc --noconfirm"
|
test_distro "archlinux" "archlinux:latest" "RUN pacman -Sy --noconfirm iproute2 wget; pacman -Sc --noconfirm"
|
||||||
|
|
||||||
test_user
|
|
||||||
|
|
||||||
test_mount
|
test_mount
|
||||||
|
|
||||||
test_service
|
test_service
|
||||||
|
|||||||
+1
-6
@@ -78,12 +78,7 @@ fn build_inner(
|
|||||||
let command = cmd.unwrap_or_else(|| inject::infer_command(&config));
|
let command = cmd.unwrap_or_else(|| inject::infer_command(&config));
|
||||||
let exec_script =
|
let exec_script =
|
||||||
inject::build_exec_script(&command, &config.env, config.working_dir.as_deref());
|
inject::build_exec_script(&command, &config.env, config.working_dir.as_deref());
|
||||||
inject::inject(
|
inject::inject(mount_path, &exec_script, config.working_dir.as_deref())?;
|
||||||
mount_path,
|
|
||||||
&exec_script,
|
|
||||||
config.user.as_deref(),
|
|
||||||
config.working_dir.as_deref(),
|
|
||||||
)?;
|
|
||||||
println!("Injected /slim/ (init + exec)");
|
println!("Injected /slim/ (init + exec)");
|
||||||
|
|
||||||
match kind {
|
match kind {
|
||||||
|
|||||||
+43
-29
@@ -22,8 +22,6 @@ pub struct Config {
|
|||||||
pub env: Vec<String>,
|
pub env: Vec<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub working_dir: Option<String>,
|
pub working_dir: Option<String>,
|
||||||
#[serde(default)]
|
|
||||||
pub user: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn inspect_config(image: &str) -> Result<Config> {
|
pub fn inspect_config(image: &str) -> Result<Config> {
|
||||||
@@ -87,38 +85,54 @@ pub fn build_exec_script(command: &str, env: &[String], working_dir: Option<&str
|
|||||||
lines
|
lines
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Install `content` into the mounted rootfs at `<mount>/slim/<name>` with
|
/// Inject /slim/init and /slim/exec (and optionally /slim/workdir) into a
|
||||||
/// the given mode.
|
/// mounted container image rootfs.
|
||||||
fn install_into_rootfs(mount: &str, name: &str, mode: &str, content: &str) -> Result<()> {
|
pub fn inject(mount_path: &Path, exec_script: &str, working_dir: Option<&str>) -> Result<()> {
|
||||||
let temp = tempfile::NamedTempFile::new()?;
|
|
||||||
fs::write(temp.path(), content)?;
|
|
||||||
let src = temp.path().to_str().context("temp path is not UTF-8")?;
|
|
||||||
let dest = format!("{mount}/slim/{name}");
|
|
||||||
cmd(&[
|
|
||||||
"podman", "unshare", "--", "install", "-D", "-m", mode, src, &dest,
|
|
||||||
])?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Inject /slim/init, /slim/exec, and optionally /slim/user and /slim/workdir
|
|
||||||
/// into a mounted container image rootfs.
|
|
||||||
pub fn inject(
|
|
||||||
mount_path: &Path,
|
|
||||||
exec_script: &str,
|
|
||||||
user: Option<&str>,
|
|
||||||
working_dir: Option<&str>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let mount_str = mount_path.to_str().context("mount path is not UTF-8")?;
|
let mount_str = mount_path.to_str().context("mount path is not UTF-8")?;
|
||||||
|
|
||||||
install_into_rootfs(mount_str, "init", "755", SLIM_INIT)?;
|
let init_temp = tempfile::NamedTempFile::new()?;
|
||||||
install_into_rootfs(mount_str, "exec", "755", exec_script)?;
|
let exec_temp = tempfile::NamedTempFile::new()?;
|
||||||
|
fs::write(init_temp.path(), SLIM_INIT)?;
|
||||||
|
fs::write(exec_temp.path(), exec_script)?;
|
||||||
|
|
||||||
if let Some(user) = user.filter(|u| !u.is_empty()) {
|
let init_src = init_temp
|
||||||
install_into_rootfs(mount_str, "user", "644", user)?;
|
.path()
|
||||||
}
|
.to_str()
|
||||||
|
.context("temp path is not UTF-8")?;
|
||||||
|
let exec_src = exec_temp
|
||||||
|
.path()
|
||||||
|
.to_str()
|
||||||
|
.context("temp path is not UTF-8")?;
|
||||||
|
|
||||||
|
let init_dest = format!("{mount_str}/slim/init");
|
||||||
|
let exec_dest = format!("{mount_str}/slim/exec");
|
||||||
|
|
||||||
|
cmd(&[
|
||||||
|
"podman", "unshare", "--", "install", "-D", "-m", "755", init_src, &init_dest,
|
||||||
|
])?;
|
||||||
|
cmd(&[
|
||||||
|
"podman", "unshare", "--", "install", "-D", "-m", "755", exec_src, &exec_dest,
|
||||||
|
])?;
|
||||||
|
|
||||||
if let Some(dir) = working_dir.filter(|d| !d.is_empty()) {
|
if let Some(dir) = working_dir.filter(|d| !d.is_empty()) {
|
||||||
install_into_rootfs(mount_str, "workdir", "644", dir)?;
|
let workdir_temp = tempfile::NamedTempFile::new()?;
|
||||||
|
fs::write(workdir_temp.path(), dir)?;
|
||||||
|
let workdir_src = workdir_temp
|
||||||
|
.path()
|
||||||
|
.to_str()
|
||||||
|
.context("temp path is not UTF-8")?;
|
||||||
|
let workdir_dest = format!("{mount_str}/slim/workdir");
|
||||||
|
cmd(&[
|
||||||
|
"podman",
|
||||||
|
"unshare",
|
||||||
|
"--",
|
||||||
|
"install",
|
||||||
|
"-D",
|
||||||
|
"-m",
|
||||||
|
"644",
|
||||||
|
workdir_src,
|
||||||
|
&workdir_dest,
|
||||||
|
])?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -70,39 +70,6 @@ for tok in $(cat /proc/cmdline 2>/dev/null); do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# === Drop privileges (respect USER directive from image config) ===
|
|
||||||
# /slim/user may contain a username, uid, or uid:gid (OCI image spec).
|
|
||||||
# Numeric uids are resolved to a username via /etc/passwd because BusyBox
|
|
||||||
# su does not accept numeric arguments.
|
|
||||||
SLIM_USER=""
|
|
||||||
[ -f /slim/user ] && SLIM_USER=$(cat /slim/user 2>/dev/null)
|
|
||||||
|
|
||||||
# Resolve a numeric uid to a username from /etc/passwd.
|
|
||||||
slim_resolve_user() {
|
|
||||||
_u="$1"
|
|
||||||
case "$_u" in
|
|
||||||
*[!0-9]*) printf '%s' "$_u" ;;
|
|
||||||
*)
|
|
||||||
_resolved=$(awk -F: -v uid="$_u" '$3==uid{print $1; exit}' /etc/passwd 2>/dev/null)
|
|
||||||
[ -n "$_resolved" ] && printf '%s' "$_resolved" || printf '%s' "$_u"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# Exec a command, dropping privileges if SLIM_USER is set.
|
|
||||||
# When dropping privileges, run as a child (not exec) so PID 1 stays root
|
|
||||||
# and can poweroff after the command exits — non-root cannot call poweroff.
|
|
||||||
slim_exec() {
|
|
||||||
_cmd="$1"
|
|
||||||
if [ -z "$SLIM_USER" ]; then
|
|
||||||
exec /bin/sh -c "$_cmd"
|
|
||||||
fi
|
|
||||||
_user="${SLIM_USER%%:*}"
|
|
||||||
_user=$(slim_resolve_user "$_user")
|
|
||||||
su "$_user" -c "$_cmd"
|
|
||||||
poweroff -f
|
|
||||||
}
|
|
||||||
|
|
||||||
# === Execute the configured command ===
|
# === Execute the configured command ===
|
||||||
# If slim.cmd=<base64> is on the kernel cmdline, decode and exec it.
|
# If slim.cmd=<base64> is on the kernel cmdline, decode and exec it.
|
||||||
# Otherwise, exec /slim/exec (generated from the image's CMD/ENTRYPOINT).
|
# Otherwise, exec /slim/exec (generated from the image's CMD/ENTRYPOINT).
|
||||||
@@ -113,9 +80,9 @@ for tok in $(cat /proc/cmdline 2>/dev/null); do
|
|||||||
v=${tok#slim.cmd=}
|
v=${tok#slim.cmd=}
|
||||||
decoded=$(b64dec "$v") || echo "base64 decode failed"
|
decoded=$(b64dec "$v") || echo "base64 decode failed"
|
||||||
if [ -n "$decoded" ]; then
|
if [ -n "$decoded" ]; then
|
||||||
slim_exec "$decoded"
|
exec /bin/sh -c "$decoded"
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
slim_exec "/slim/exec"
|
exec /slim/exec
|
||||||
|
|||||||
Reference in New Issue
Block a user