mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-10-09 06:56:37 +00:00
Merge pull request #413 from Vaso73/fix/oci-runtime-home
fix(oci): set HOME for native OCI runtime
This commit is contained in:
+33
-10
@@ -1011,6 +1011,37 @@ apply_runtime_user() {
|
|||||||
set_lxc_directive lxc.init.gid "$gid"
|
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() {
|
apply_runtime_groups() {
|
||||||
local groups_csv=$1 rootfs="/var/lib/lxc/${VMID}/rootfs"
|
local groups_csv=$1 rootfs="/var/lib/lxc/${VMID}/rootfs"
|
||||||
local group_file="${rootfs}/etc/group" existing resolved group failed=0
|
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.
|
# PVE represents the public env property as repeated native LXC runtime lines.
|
||||||
# Merge only Compose overrides while the newly-created CT is stopped.
|
# Merge only Compose overrides while the newly-created CT is stopped.
|
||||||
while IFS=$'\t' read -r NAME ENCODED; do
|
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=$(printf '%s' "$ENCODED" | base64 -d)
|
||||||
[[ $VALUE != *$'\n'* && $VALUE != *$'\r'* ]] || die "$(translate "The variable contains line breaks:") $NAME"
|
set_runtime_environment "$NAME" "$VALUE"
|
||||||
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"
|
|
||||||
done < <(jq -r '.environment[] | [.name, (.value | @base64)] | @tsv' "$DEPLOYMENT_FILE")
|
done < <(jq -r '.environment[] | [.name, (.value | @base64)] | @tsv' "$DEPLOYMENT_FILE")
|
||||||
|
|
||||||
apply_extra_hosts
|
apply_extra_hosts
|
||||||
apply_installer_profile
|
apply_installer_profile
|
||||||
|
ensure_runtime_home
|
||||||
apply_rlimits
|
apply_rlimits
|
||||||
apply_host_monitor
|
apply_host_monitor
|
||||||
apply_kept_settings
|
apply_kept_settings
|
||||||
|
|||||||
@@ -89,7 +89,29 @@ def effective_entrypoint(
|
|||||||
return " ".join(shlex.quote(item) for item in process)
|
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:
|
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):
|
if len(sys.argv) not in (3, 4):
|
||||||
print(
|
print(
|
||||||
f"{translate('Usage:')} {sys.argv[0]} OCI_ARCHIVE COMMAND_JSON [ENTRYPOINT_JSON]",
|
f"{translate('Usage:')} {sys.argv[0]} OCI_ARCHIVE COMMAND_JSON [ENTRYPOINT_JSON]",
|
||||||
|
|||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user