fix(oci): set HOME for native OCI runtime

This commit is contained in:
VAIO73
2026-09-30 10:46:34 +02:00
parent 0fa112de56
commit 4fd987fdd5
3 changed files with 118 additions and 10 deletions
+33 -10
View File
@@ -1011,6 +1011,37 @@ apply_runtime_user() {
set_lxc_directive lxc.init.gid "$gid"
}
set_runtime_environment() {
local name=$1 value=$2 temporary
[[ $name =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || die "$(translate "Invalid variable name:") $name"
[[ $value != *$'\n'* && $value != *$'\r'* ]] || die "$(translate "The variable contains line breaks:") $name"
if LC_ALL=C grep -q '[[:cntrl:]]' <<<"$value"; then
die "$(translate "The variable contains control characters:") $name"
fi
temporary=$(mktemp)
awk -v prefix="lxc.environment.runtime: ${name}=" 'index($0, prefix) != 1' "$CONF" >"$temporary"
printf 'lxc.environment.runtime: %s=%s\n' "$name" "$value" >>"$temporary"
cat "$temporary" >"$CONF"
rm -f "$temporary"
}
ensure_runtime_home() {
local uid home rootfs passwd_file
grep -q '^lxc.environment.runtime: HOME=' "$CONF" && return 0
uid=$(awk -F': ' '$1 == "lxc.init.uid" {print $2; exit}' "$CONF" 2>/dev/null || true)
uid=${uid:-0}
[[ $uid =~ ^[0-9]+$ ]] || return 0
rootfs="/var/lib/lxc/${VMID}/rootfs"
passwd_file="${rootfs}/etc/passwd"
mount_ct_rootfs
if ! home=$(python3 "$OCI_RUNTIME_RESOLVER" --home "$passwd_file" "$uid" 2>>"${OCI_LOG:-/dev/stderr}"); then
oci_quiet pct unmount "$VMID" || true
return 0
fi
oci_quiet pct unmount "$VMID" || die "$(translate "Could not unmount the container filesystem")"
set_runtime_environment HOME "$home"
}
apply_runtime_groups() {
local groups_csv=$1 rootfs="/var/lib/lxc/${VMID}/rootfs"
local group_file="${rootfs}/etc/group" existing resolved group failed=0
@@ -1883,21 +1914,13 @@ apply_native_device_permissions
# PVE represents the public env property as repeated native LXC runtime lines.
# Merge only Compose overrides while the newly-created CT is stopped.
while IFS=$'\t' read -r NAME ENCODED; do
[[ $NAME =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || die "$(translate "Invalid variable name:") $NAME"
VALUE=$(printf '%s' "$ENCODED" | base64 -d)
[[ $VALUE != *$'\n'* && $VALUE != *$'\r'* ]] || die "$(translate "The variable contains line breaks:") $NAME"
if LC_ALL=C grep -q '[[:cntrl:]]' <<<"$VALUE"; then
die "$(translate "The variable contains control characters:") $NAME"
fi
TEMP_CONF=$(mktemp)
awk -v prefix="lxc.environment.runtime: ${NAME}=" 'index($0, prefix) != 1' "$CONF" >"$TEMP_CONF"
printf 'lxc.environment.runtime: %s=%s\n' "$NAME" "$VALUE" >>"$TEMP_CONF"
cat "$TEMP_CONF" >"$CONF"
rm -f "$TEMP_CONF"
set_runtime_environment "$NAME" "$VALUE"
done < <(jq -r '.environment[] | [.name, (.value | @base64)] | @tsv' "$DEPLOYMENT_FILE")
apply_extra_hosts
apply_installer_profile
ensure_runtime_home
apply_rlimits
apply_host_monitor
apply_kept_settings
+22
View File
@@ -89,7 +89,29 @@ def effective_entrypoint(
return " ".join(shlex.quote(item) for item in process)
def home_directory(passwd_path: Path, uid: int) -> str | None:
"""Return the safe home directory recorded for a container UID."""
for line in passwd_path.read_text(encoding="utf-8").splitlines():
fields = line.split(":")
if len(fields) < 7 or fields[2] != str(uid):
continue
home = fields[5]
return home if home.startswith("/") else None
return None
def main() -> int:
if len(sys.argv) == 4 and sys.argv[1] == "--home":
try:
uid = int(sys.argv[3])
home = home_directory(Path(sys.argv[2]), uid)
except (OSError, ValueError) as exc:
print(f"{translate('Cannot resolve the container home directory:')} {exc}", file=sys.stderr)
return 1
if home is None:
return 1
print(home)
return 0
if len(sys.argv) not in (3, 4):
print(
f"{translate('Usage:')} {sys.argv[0]} OCI_ARCHIVE COMMAND_JSON [ENTRYPOINT_JSON]",
+63
View File
@@ -0,0 +1,63 @@
"""Regression tests for the native OCI PID 1 HOME fallback."""
from pathlib import Path
import sys
import tempfile
import unittest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "remote"))
import oci_runtime # noqa: E402
class RuntimeHomeResolutionTests(unittest.TestCase):
def passwd(self, content: str) -> Path:
directory = tempfile.TemporaryDirectory()
self.addCleanup(directory.cleanup)
path = Path(directory.name) / "passwd"
path.write_text(content, encoding="utf-8")
return path
def test_root_home_is_resolved_from_passwd(self):
passwd = self.passwd("root:x:0:0:root:/root:/bin/sh\n")
self.assertEqual(oci_runtime.home_directory(passwd, 0), "/root")
def test_non_root_home_is_resolved_from_passwd(self):
passwd = self.passwd(
"root:x:0:0:root:/root:/bin/sh\n"
"app:x:1001:1001:App user:/srv/app:/sbin/nologin\n"
)
self.assertEqual(oci_runtime.home_directory(passwd, 1001), "/srv/app")
def test_unknown_or_unsafe_home_is_not_invented(self):
passwd = self.passwd(
"root:x:0:0:root:relative-home:/bin/sh\n"
"app:x:1001:1001:App user::/sbin/nologin\n"
)
self.assertIsNone(oci_runtime.home_directory(passwd, 0))
self.assertIsNone(oci_runtime.home_directory(passwd, 1001))
self.assertIsNone(oci_runtime.home_directory(passwd, 1234))
class RuntimeHomeInstallerContractTests(unittest.TestCase):
def setUp(self):
self.source = (ROOT / "remote" / "install_oci.sh").read_text(encoding="utf-8")
def test_explicit_home_is_preserved_and_fallback_uses_effective_uid(self):
self.assertIn("ensure_runtime_home()", self.source)
self.assertIn("lxc.environment.runtime: HOME=", self.source)
self.assertIn('uid=${uid:-0}', self.source)
self.assertIn('home=$(python3 "$OCI_RUNTIME_RESOLVER" --home', self.source)
self.assertIn('set_runtime_environment HOME "$home"', self.source)
def test_home_fallback_runs_after_runtime_user_is_applied(self):
self.assertIn(
"apply_extra_hosts\napply_installer_profile\nensure_runtime_home\napply_rlimits",
self.source,
)
if __name__ == "__main__":
unittest.main()