mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-01 10:22:08 +00:00
fix(oci): the access rule covers the container's root, and a shared volume is writable by its group
This commit is contained in:
+1
-1
@@ -190,7 +190,7 @@
|
||||
"Access bridge for Nextcloud": "Access Bridge für Nextcloud",
|
||||
"Access bridge for Paperless": "Access Bridge für Paperless",
|
||||
"Access bridge for Tandoor": "Access Bridge für Tandoor",
|
||||
"Access could not be granted; the application cannot write to:": "Der Zugriff konnte nicht gewährt werden;Die Anwendung kann nicht schreiben an:",
|
||||
"Access could not be granted; the application cannot write to:": "Der Zugriff konnte nicht gewährt werden; die Anwendung kann nicht schreiben an:",
|
||||
"Access granted to the application on:": "Zugriff auf die Anwendung gewährt am:",
|
||||
"Access profile:": "Zugangsprofil:",
|
||||
"Access token of the Jupyter Lab web interface": "Access-Token des Jupyter Lab Webinterfaces",
|
||||
|
||||
@@ -767,6 +767,7 @@
|
||||
"Changing between a privileged and an unprivileged container, or the host monitor mode, requires installing the application again": "Cambiar entre contenedor privilegiado y sin privilegios, o el modo de monitor del host, requiere instalar la aplicación de nuevo",
|
||||
"Changing the machine type on an existing installed VM is not safe: it changes the chipset and PCI slot layout, which typically prevents the guest OS from booting.": "Cambiar el tipo de máquina de una VM ya instalada no es seguro: cambia el chipset y la disposición de las ranuras PCI, lo que normalmente impide que arranque el sistema operativo de la VM.",
|
||||
"Changing the rootfs or its storage requires a separate migration": "Cambiar los rootfs o su almacenamiento requiere una migración separada",
|
||||
"Changing the shared identity (1000:1000) in Settings of the panel means giving the new one access on the host again: the volumes and the access rules are prepared for 1000:1000.": "Cambiar la identidad compartida (1000:1000) en Ajustes del panel obliga a dar acceso de nuevo a la nueva en el host: los volúmenes y las reglas de acceso se preparan para 1000:1000.",
|
||||
"Changing the storage or size of a disk requires a migration; empty disks are not created": "Cambiar el almacenamiento o el tamaño de un disco requiere una migración; los discos vacíos no se crean",
|
||||
"Check": "Controlar",
|
||||
"Check BIOS/UEFI in Hardware > BIOS — must match what the original VM used": "Verifique BIOS/UEFI en Hardware > BIOS: debe coincidir con lo que usó la VM original",
|
||||
|
||||
+1
-1
@@ -190,7 +190,7 @@
|
||||
"Access bridge for Nextcloud": "Pont d'accès pour Nextcloud",
|
||||
"Access bridge for Paperless": "Pont d'accès pour sans papier",
|
||||
"Access bridge for Tandoor": "Pont d'accès pour Tandoor",
|
||||
"Access could not be granted; the application cannot write to:": "L'accès n'a pas pu être accordé ;l'application ne peut pas écrire dans :",
|
||||
"Access could not be granted; the application cannot write to:": "L'accès n'a pas pu être accordé ; l'application ne peut pas écrire dans :",
|
||||
"Access granted to the application on:": "Accès accordé à l’application sur :",
|
||||
"Access profile:": "Profil d'accès :",
|
||||
"Access token of the Jupyter Lab web interface": "Jeton d'accès de l'interface web Jupyter Lab",
|
||||
|
||||
+2
-2
@@ -190,7 +190,7 @@
|
||||
"Access bridge for Nextcloud": "Bridge di accesso per Nextcloud",
|
||||
"Access bridge for Paperless": "Bridge di accesso per Paperless",
|
||||
"Access bridge for Tandoor": "Bridge di accesso per Tandoor",
|
||||
"Access could not be granted; the application cannot write to:": "impossibile concedere l'accesso;l'applicazione non può scrivere a:",
|
||||
"Access could not be granted; the application cannot write to:": "Impossibile concedere l'accesso; l'applicazione non può scrivere a:",
|
||||
"Access granted to the application on:": "Accesso concesso all'applicazione su:",
|
||||
"Access profile:": "Profilo di accesso:",
|
||||
"Access token of the Jupyter Lab web interface": "Token di accesso all'interfaccia web di Jupyter Lab",
|
||||
@@ -6122,7 +6122,7 @@
|
||||
"The Valkey volume needs at least 1 GB": "Il volume Valkey richiede almeno 1 GB",
|
||||
"The acceleration evidence differs from the verified inventory": "I dati sull'accelerazione sono diversi dall'inventario verificato",
|
||||
"The acceleration profile does not support this architecture:": "Il profilo di accelerazione non supporta questa architettura:",
|
||||
"The access rule added to this host directory is kept:": "la regola di accesso aggiunta a questa directory host viene mantenuta:",
|
||||
"The access rule added to this host directory is kept:": "La regola di accesso aggiunta a questa directory host viene mantenuta:",
|
||||
"The active kernel driver is not vfio-pci, but the entry in": "Il driver del kernel attivo non è vfio-pci, ma la voce in",
|
||||
"The active kernel driver is not vfio-pci, but the entry will rebind the GPU to vfio-pci on the next reboot, breaking the LXC passthrough about to be configured.": "Il driver del kernel attivo non è vfio-pci, ma la voce ricollegherà la GPU a vfio-pci al prossimo riavvio, interrompendo il passthrough LXC che sta per essere configurato.",
|
||||
"The active kernel driver is not vfio-pci, but the entry will rebind the GPU to vfio-pci on the next reboot.": "Il driver del kernel attivo non è vfio-pci, ma la voce ricollegherà la GPU a vfio-pci al prossimo riavvio.",
|
||||
|
||||
+1
-1
@@ -190,7 +190,7 @@
|
||||
"Access bridge for Nextcloud": "Ponte de acesso para Nextcloud",
|
||||
"Access bridge for Paperless": "Ponte de acesso para Paperless",
|
||||
"Access bridge for Tandoor": "Ponte de acesso para Tandoor",
|
||||
"Access could not be granted; the application cannot write to:": "O acesso não pôde ser concedido;o aplicativo não pode gravar em:",
|
||||
"Access could not be granted; the application cannot write to:": "O acesso não pôde ser concedido; o aplicativo não pode gravar em:",
|
||||
"Access granted to the application on:": "Acesso concedido ao aplicativo em:",
|
||||
"Access profile:": "Perfil de acesso:",
|
||||
"Access token of the Jupyter Lab web interface": "Token de acesso da interface web Jupyter Lab",
|
||||
|
||||
+1
-1
@@ -190,7 +190,7 @@
|
||||
"Access bridge for Nextcloud": "Prístupový mostík pre Nextcloud",
|
||||
"Access bridge for Paperless": "Prístupový mostík pre bezpapierové",
|
||||
"Access bridge for Tandoor": "Prístupový mostík pre Tandoor",
|
||||
"Access could not be granted; the application cannot write to:": "Prístup nebolo možné udeliť;aplikácia nemôže písať na:",
|
||||
"Access could not be granted; the application cannot write to:": "Prístup nebolo možné udeliť; aplikácia nemôže písať na:",
|
||||
"Access granted to the application on:": "Prístup udelený aplikácii dňa:",
|
||||
"Access profile:": "Profil prístupu:",
|
||||
"Access token of the Jupyter Lab web interface": "Prístupový token webového rozhrania Jupyter Lab",
|
||||
|
||||
+1
-1
@@ -190,7 +190,7 @@
|
||||
"Access bridge for Nextcloud": "Tillgångsbro för Nextcloud",
|
||||
"Access bridge for Paperless": "Tillgångsbro för papperslös",
|
||||
"Access bridge for Tandoor": "Tillgångsbro för Tandoor",
|
||||
"Access could not be granted; the application cannot write to:": "Åtkomst kunde inte beviljas;ansökan kan inte skriva till:",
|
||||
"Access could not be granted; the application cannot write to:": "Åtkomst kunde inte beviljas; ansökan kan inte skriva till:",
|
||||
"Access granted to the application on:": "Tillgång beviljad till ansökan på:",
|
||||
"Access profile:": "Åtkomstprofil:",
|
||||
"Access token of the Jupyter Lab web interface": "Tillgångstoken av Jupyter Labs webbgränssnitt",
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"tips": [
|
||||
"Each directory to share is mounted under /shares with the name given to it during the installation; more can be added later with Modify.",
|
||||
"A directory of the host keeps the owner it already has. The panel shows whether the shared identity (1000:1000 by default) can write to it.",
|
||||
"Changing the shared identity (1000:1000) in Settings of the panel means giving the new one access on the host again: the volumes and the access rules are prepared for 1000:1000.",
|
||||
"Create the Samba users in Settings of the panel, then publish each directory from Resources.",
|
||||
"NFS is served as NFSv3. NFSv4 needs the privileged profile offered by the advanced installation.",
|
||||
"/config: Proxmox volume included in backup and kept when the application is updated or modified."
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
"tips": [
|
||||
"Each directory to share is mounted under /shares with the name given to it during the installation; more can be added later with Modify.",
|
||||
"A directory of the host keeps the owner it already has. The panel shows whether the shared identity (1000:1000 by default) can write to it.",
|
||||
"Changing the shared identity (1000:1000) in Settings of the panel means giving the new one access on the host again: the volumes and the access rules are prepared for 1000:1000.",
|
||||
"Create the Samba users in Settings of the panel, then publish each directory from Resources.",
|
||||
"NFS is served as NFSv3. NFSv4 needs the privileged profile offered by the advanced installation.",
|
||||
"/config: Proxmox volume included in backup and kept when the application is updated or modified."
|
||||
|
||||
+29
-10
@@ -1093,7 +1093,7 @@ apply_installer_profile() {
|
||||
local generated_count preparation_count tls_count seeded_volumes mounted=0 failed=0
|
||||
local rootfs="/var/lib/lxc/${VMID}/rootfs"
|
||||
local encoded item path mode owner destination target remove_lost_found owner_strategy
|
||||
local only_when_mount_type selected_mount_type
|
||||
local only_when_mount_type selected_mount_type preparation_mode
|
||||
local entrypoint working_directory halt_signal command_json compose_entrypoint_json user_spec
|
||||
local supplemental_groups tls_config cert_path key_path cert_destination key_destination
|
||||
local tls_directory valid_days common_name san
|
||||
@@ -1198,6 +1198,13 @@ apply_installer_profile() {
|
||||
*) oci_log "Unsupported owner strategy: $owner_strategy"; failed=1 ;;
|
||||
esac
|
||||
(( failed == 0 )) || break
|
||||
# A mode the preparation states, for a directory several users write to.
|
||||
preparation_mode=$(jq -r '.mode // empty' <<<"$item")
|
||||
if [[ -n $preparation_mode ]]; then
|
||||
[[ $preparation_mode =~ ^[0-7]{3,4}$ ]] \
|
||||
|| { oci_log "Invalid volume preparation mode: $preparation_mode"; failed=1; break; }
|
||||
chmod "$preparation_mode" "${rootfs}${target}" || { failed=1; break; }
|
||||
fi
|
||||
oci_log "Volume prepared before the first start: $target"
|
||||
volumes_prepared=$((volumes_prepared + 1))
|
||||
done < <(jq -r '.proxmox.installer_profile.volume_preparations[]? | @base64' "$TEMPLATE_FILE")
|
||||
@@ -1779,21 +1786,33 @@ fi
|
||||
if [[ $SELINUX_LABEL_DISABLED == true ]]; then
|
||||
oci_log "label:disable kept as metadata; Proxmox uses AppArmor, not SELinux, for this LXC"
|
||||
fi
|
||||
# Gives the identity the application writes with access to a host directory
|
||||
# that belongs to someone else, with an ACL: the owner and the permissions of
|
||||
# what is there stay as they are, and the owner of the directory keeps access
|
||||
# to what the application creates in it.
|
||||
# Gives the application access to a host directory that belongs to someone
|
||||
# else, with an ACL: the owner and the permissions of what is there stay as
|
||||
# they are, and the owner of the directory keeps access to what the
|
||||
# application creates in it. The rule names the identity the application
|
||||
# writes with and, in an unprivileged container, its root user as well, which
|
||||
# is who lists the folders and answers the access checks of an NFS client.
|
||||
grant_shared_access() {
|
||||
local source=$1 owner group triplet default_spec
|
||||
command -v setfacl >/dev/null 2>&1 && command -v getfacl >/dev/null 2>&1 || return 1
|
||||
local source=$1 owner group triplet spec default_spec
|
||||
if ! command -v setfacl >/dev/null 2>&1 || ! command -v getfacl >/dev/null 2>&1; then
|
||||
# A host installed on ZFS does not bring the ACL tools.
|
||||
oci_log "Installing the acl package to grant access to $source"
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y acl >>"${OCI_LOG:-/dev/null}" 2>&1 || return 1
|
||||
fi
|
||||
spec="u:${HOST_BIND_UID}:rwX,g:${HOST_BIND_GID}:rwX"
|
||||
default_spec="d:u:${HOST_BIND_UID}:rwx,d:g:${HOST_BIND_GID}:rwx"
|
||||
if [[ $HOST_ROOT_UID != 0 ]]; then
|
||||
spec="${spec},u:${HOST_ROOT_UID}:rwX,g:${HOST_ROOT_GID}:rwX"
|
||||
default_spec="${default_spec},d:u:${HOST_ROOT_UID}:rwx,d:g:${HOST_ROOT_GID}:rwx"
|
||||
fi
|
||||
# Granted already: an update or a modification installs the container again.
|
||||
if getfacl -cpn "$source" 2>/dev/null | grep -q "^user:${HOST_BIND_UID}:rwx" \
|
||||
&& getfacl -cpn "$source" 2>/dev/null | grep -q "^default:user:${HOST_BIND_UID}:rwx"; then
|
||||
&& getfacl -cpn "$source" 2>/dev/null | grep -q "^default:user:${HOST_BIND_UID}:rwx" \
|
||||
&& { [[ $HOST_ROOT_UID == 0 ]] || getfacl -cpn "$source" 2>/dev/null | grep -q "^user:${HOST_ROOT_UID}:rwx"; }; then
|
||||
return 0
|
||||
fi
|
||||
owner=$(stat -c '%u' "$source") || return 1
|
||||
group=$(stat -c '%g' "$source") || return 1
|
||||
default_spec="d:u:${HOST_BIND_UID}:rwx,d:g:${HOST_BIND_GID}:rwx"
|
||||
if [[ $owner != 0 && $owner != "$HOST_BIND_UID" ]]; then
|
||||
default_spec="${default_spec},d:u:${owner}:rwx"
|
||||
fi
|
||||
@@ -1804,7 +1823,7 @@ grant_shared_access() {
|
||||
triplet=${triplet//S/-}
|
||||
default_spec="${default_spec},d:g:${group}:${triplet}"
|
||||
fi
|
||||
setfacl -R -P -m "u:${HOST_BIND_UID}:rwX,g:${HOST_BIND_GID}:rwX" "$source" 2>>"${OCI_LOG:-/dev/null}" || return 1
|
||||
setfacl -R -P -m "$spec" "$source" 2>>"${OCI_LOG:-/dev/null}" || return 1
|
||||
find "$source" -xdev -type d -exec setfacl -m "$default_spec" {} + 2>>"${OCI_LOG:-/dev/null}" || return 1
|
||||
}
|
||||
|
||||
|
||||
@@ -176,9 +176,10 @@ def ask_shared_directories(ui, template: dict[str, Any], mounts: list[dict[str,
|
||||
if advanced:
|
||||
mount["source"] = ask_storage(ui, f"{translate('Storage for')} {target}", "rootdir", storage)
|
||||
mount["size_gb"] = _ask_size(ui, target, int(declared.get("default_size_gb", 32)))
|
||||
# The application writes to it as its own user, not as root.
|
||||
# The application writes to it as its own user, not as root, and
|
||||
# its group writes too: what is created inside keeps that group.
|
||||
preparation = {"container_path": target, "remove_lost_found": True,
|
||||
"owner_strategy": "mapped-application-user",
|
||||
"owner_strategy": "mapped-application-user", "mode": "2775",
|
||||
"only_when_mount_type": "managed-volume"}
|
||||
if preparation not in profile.setdefault("volume_preparations", []):
|
||||
profile["volume_preparations"].append(preparation)
|
||||
|
||||
@@ -92,6 +92,10 @@ class SharedDirectoriesTests(unittest.TestCase):
|
||||
prepared = {item["container_path"]: item["owner_strategy"]
|
||||
for item in template["proxmox"]["installer_profile"]["volume_preparations"]}
|
||||
self.assertEqual(prepared, {"/config": "mapped-root", "/shares/backups": "mapped-application-user"})
|
||||
# Its group writes as well, for when each Samba user acts as itself.
|
||||
modes = {item["container_path"]: item.get("mode")
|
||||
for item in template["proxmox"]["installer_profile"]["volume_preparations"]}
|
||||
self.assertEqual(modes, {"/config": None, "/shares/backups": "2775"})
|
||||
|
||||
def test_a_default_installation_stays_unprivileged_and_generates_the_panel_password(self, *_):
|
||||
ui, _template, plan = self.build({NAME: ["media"]})
|
||||
@@ -194,7 +198,14 @@ class SharedDirectoriesTests(unittest.TestCase):
|
||||
self.assertNotIn("chown", grant)
|
||||
self.assertNotIn("chmod", grant)
|
||||
self.assertIn('grep -q "^default:user:${HOST_BIND_UID}:rwx"', grant)
|
||||
self.assertIn('setfacl -R -P -m "u:${HOST_BIND_UID}:rwX,g:${HOST_BIND_GID}:rwX"', grant)
|
||||
self.assertIn('spec="u:${HOST_BIND_UID}:rwX,g:${HOST_BIND_GID}:rwX"', grant)
|
||||
self.assertIn('setfacl -R -P -m "$spec" "$source"', grant)
|
||||
# In an unprivileged container its root user is named too: it lists the folders.
|
||||
self.assertIn('spec="${spec},u:${HOST_ROOT_UID}:rwX,g:${HOST_ROOT_GID}:rwX"', grant)
|
||||
self.assertIn("apt-get install -y acl", grant)
|
||||
# A preparation may state the mode of its directory.
|
||||
self.assertIn('chmod "$preparation_mode" "${rootfs}${target}"', script)
|
||||
self.assertIn("[[ $preparation_mode =~ ^[0-7]{3,4}$ ]]", script)
|
||||
|
||||
def test_a_name_that_cannot_identify_a_share_is_refused(self, *_):
|
||||
for name in ("Media", "my files", "9lives", "config", "", "a" * 33):
|
||||
|
||||
Reference in New Issue
Block a user