Fix: Mount-Verzeichnisse bekommen explizite Rechte statt sich auf Umask zu verlassen
mount_base_dir (von allen Paaren gemeinsam genutzt) sowie die versteckten Backing-Verzeichnisse eines Paares wurden bisher nur per create_dir_all() angelegt, ohne eigenes chmod/chown - die tatsaechlichen Zugriffsrechte hingen also allein vom Umask des root-Prozesses ab statt von einer bewussten Entscheidung. - activate_symlink() setzt den gemeinsamen Elternordner (mount_base_dir) jetzt hart auf 0755, unabhaengig vom Umask. - reconcile::mount_side() chownt das jeweilige Backing-Verzeichnis eines Paares mit gesetztem owner_user auf diesen Nutzer und setzt 0700 (neue Funktion target::ensure_owner_only_dir) - insbesondere fuer NFS relevant, wo es (anders als CIFS/WebDAV) keine uid=/gid=-Mount-Option gibt, die den Zugriff nach dem Mounten sonst regeln wuerde. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -105,9 +105,12 @@ Jedes Laufwerkspaar bekommt sein **eigenes** Unterverzeichnis unter `settings.mo
|
||||
root, siehe oben) - `/run/media` ist auf den meisten Systemen bereits die übliche Konvention
|
||||
für eingebundene Wechseldatenträger/Netzlaufwerke (z. B. udisks2/GNOME) und liegt auf `tmpfs`,
|
||||
muss also nie persistieren. Der Mountpoint selbst (und alle nötigen Elternverzeichnisse) werden
|
||||
bei jedem `mount`/`watch`-Lauf automatisch angelegt, falls sie fehlen. Ist für ein Paar
|
||||
`owner_user` gesetzt, bekommt genau dieser Nutzer über die Mount-Optionen (`uid=`/`gid=`, siehe
|
||||
"Architekturentscheidungen" unten) vollen Zugriff auf den Inhalt - die Trennung passiert also
|
||||
bei jedem `mount`/`watch`-Lauf automatisch angelegt, falls sie fehlen - `mount_base_dir` selbst
|
||||
fest mit `0755` (durchquerbar für jeden lokalen Nutzer, unabhängig vom Umask des root-Prozesses),
|
||||
das jeweilige Backing-Verzeichnis (siehe unten) bei gesetztem `owner_user` zusätzlich fest
|
||||
`chown`t und `0700` (nur der Owner). Ist für ein Paar `owner_user` gesetzt, bekommt genau dieser
|
||||
Nutzer bei CIFS/WebDAV zusätzlich über die Mount-Optionen (`uid=`/`gid=`, siehe
|
||||
"Architekturentscheidungen" unten) vollen Zugriff auf den *Inhalt* - die Trennung passiert also
|
||||
über Zugriffsrechte, nicht über getrennte Mountpoint-Namensräume pro Nutzer.
|
||||
|
||||
Der Standard lässt sich in `config.toml` unter `[settings] mount_base_dir = "..."` jederzeit
|
||||
@@ -207,7 +210,9 @@ Eintragen ausgegeben:
|
||||
vollen Zugriff darauf, sofern nicht per `--owner-user` (nicht-interaktiv) oder im
|
||||
interaktiven Prompt bewusst ein anderer Nutzer angegeben bzw. abgelehnt wird. Bei NFS gibt es
|
||||
keine clientseitige `uid=`/`gid=`-Option, Zugriff bestimmt dort ausschließlich der Server über
|
||||
die tatsächlichen Datei-Eigentümer/-Rechte des Exports.
|
||||
die tatsächlichen Datei-Eigentümer/-Rechte des Exports - `owner_user` wirkt sich hier nur auf
|
||||
das lokale Backing-Verzeichnis selbst aus (`chown`+`0700`, s. o.), nicht auf das, was der
|
||||
Server tatsächlich exportiert.
|
||||
- **davfs2-Konfiguration**: SmartMount setzt in `davfs2.conf` automatisch `gui_optimize 1`
|
||||
(bündelt PROPFIND-Anfragen, wichtig für grafische Dateimanager) sowie `buf_size 16384`
|
||||
(deutlich über dem Standard von 16 KiB) - Letzteres behebt ein bekanntes Praxisproblem, bei
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
//! kurz während eines Umschaltens (siehe [`crate::reconcile`]) mehr als ein Backing-Verzeichnis
|
||||
//! gemountet - im Ruhezustand immer nur eines, wie ursprünglich vorgesehen.
|
||||
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::config::{CloudSide, DrivePair, GlobalSettings, LocalSide, MountKind};
|
||||
@@ -159,6 +160,21 @@ fn resolve_uid_gid(username: &str) -> Result<(u32, u32)> {
|
||||
Ok((run_id(username, "-u")?, run_id(username, "-g")?))
|
||||
}
|
||||
|
||||
/// Macht `owner` (Nutzer + dessen primäre Gruppe) zum alleinigen Besitzer von `dir` (`0700`).
|
||||
/// Für das Backing-Verzeichnis eines Paares mit gesetztem `owner_user` - unabhängig vom
|
||||
/// Mount-Typ, also auch für NFS, wo es (anders als bei CIFS/WebDAV) keine `uid=`/`gid=`-
|
||||
/// Mount-Option gibt, die die nach dem Mounten sichtbaren Rechte übersteuern könnte: das
|
||||
/// Verzeichnis selbst gehört so wenigstens `owner` - reicht z. B. aus, um es zu betreten, bevor/
|
||||
/// falls etwas gemountet ist, ersetzt aber nicht die serverseitige NFS-Export-Rechtevergabe
|
||||
/// (siehe README, Abschnitt "Voller Zugriff für einen bestimmten Nutzer").
|
||||
pub(crate) fn ensure_owner_only_dir(dir: &Path, owner: &str) -> Result<()> {
|
||||
let (uid, gid) = resolve_uid_gid(owner)?;
|
||||
std::os::unix::fs::chown(dir, Some(uid), Some(gid)).map_err(|e| Error::io(dir, e))?;
|
||||
std::fs::set_permissions(dir, std::fs::Permissions::from_mode(0o700))
|
||||
.map_err(|e| Error::io(dir, e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_id(username: &str, flag: &str) -> Result<u32> {
|
||||
let output = std::process::Command::new("id")
|
||||
.arg(flag)
|
||||
@@ -263,6 +279,13 @@ pub fn activate_symlink(pair: &DrivePair, side: Side) -> Result<()> {
|
||||
|
||||
if let Some(parent) = link_path.parent() {
|
||||
std::fs::create_dir_all(parent).map_err(|e| Error::io(parent, e))?;
|
||||
// `mount_base_dir` ist von allen Paaren (mit ggf. unterschiedlichem `owner_user`)
|
||||
// gemeinsam genutzt - hart auf `0755` setzen statt sich auf das Umask des root-Prozesses
|
||||
// zu verlassen, damit jeder lokale Nutzer bis zum eigentlichen Mountpoint durchqueren
|
||||
// kann (die eigentliche Zugriffsbeschränkung passiert weiter unten je Paar, siehe
|
||||
// `ensure_owner_only_dir`/die `uid=`/`gid=`-Mount-Optionen).
|
||||
std::fs::set_permissions(parent, std::fs::Permissions::from_mode(0o755))
|
||||
.map_err(|e| Error::io(parent, e))?;
|
||||
}
|
||||
|
||||
let target = backing_dir(pair, side);
|
||||
@@ -466,4 +489,41 @@ mod tests {
|
||||
let err = activate_symlink(&pair, Side::Local).unwrap_err();
|
||||
assert!(err.to_string().contains("real directory"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_owner_only_dir_sets_mode_0700() {
|
||||
let user = std::env::var("USER").expect("USER env var set in test environment");
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
|
||||
ensure_owner_only_dir(dir.path(), &user).expect("ensure_owner_only_dir");
|
||||
|
||||
let mode = std::fs::metadata(dir.path())
|
||||
.expect("metadata")
|
||||
.permissions()
|
||||
.mode();
|
||||
assert_eq!(mode & 0o777, 0o700);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_owner_only_dir_fails_clearly_for_unknown_user() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let err = ensure_owner_only_dir(dir.path(), "no-such-user-xyz").unwrap_err();
|
||||
assert!(err.to_string().contains("not found"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn activate_symlink_sets_shared_parent_dir_to_0755() {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let parent = dir.path().join("mount-base");
|
||||
let mut pair = sample_pair();
|
||||
pair.mount_point = parent.join("pair-1");
|
||||
|
||||
activate_symlink(&pair, Side::Local).expect("activate local");
|
||||
|
||||
let mode = std::fs::metadata(&parent)
|
||||
.expect("metadata")
|
||||
.permissions()
|
||||
.mode();
|
||||
assert_eq!(mode & 0o777, 0o755);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,6 +282,12 @@ async fn mount_side(
|
||||
target::build_target_with_cached_local_ip(pair, settings, side, cached_local_ip)?;
|
||||
std::fs::create_dir_all(&mount_target.mount_point)
|
||||
.map_err(|e| crate::error::Error::io(&mount_target.mount_point, e))?;
|
||||
// Backing-Verzeichnis auf `owner_user` chownen (0700) - relevant vor allem für NFS, wo es
|
||||
// (anders als CIFS/WebDAV) keine `uid=`/`gid=`-Mount-Option gibt, die den Zugriff nach dem
|
||||
// Mounten sonst regeln würde (siehe `target::ensure_owner_only_dir`).
|
||||
if let Some(owner) = &pair.owner_user {
|
||||
target::ensure_owner_only_dir(&mount_target.mount_point, owner)?;
|
||||
}
|
||||
|
||||
let backend = mount::backend_for(target::side_kind(pair, side));
|
||||
backend.check_available()?;
|
||||
|
||||
Reference in New Issue
Block a user