diff --git a/.env.example b/.env.example index e8f6c5d..00c6b32 100644 --- a/.env.example +++ b/.env.example @@ -10,3 +10,5 @@ PUBLIC_GROUP_SID=S-1-5-21-1111111111-2222222222-3333333333-513 # NETBIOS_NAME=ADSAMBAFSRV # LDAP_URI=ldaps://example.com # LDAP_BASE_DN=DC=example,DC=com +# PRIVATE_SKIP_USERS=svc_backup,svc_sql +# PRIVATE_SKIP_PREFIXES=svc_,sql_ diff --git a/README.md b/README.md index d519dbf..be94f34 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ This repository provides a production-oriented Samba file server container that - Setup prompts for well-known authorization groups by SID (`DOMAIN_USERS_SID`, `DOMAIN_ADMINS_SID`) to avoid localized group names. - Startup resolves those SIDs to NSS group names via winbind, then uses those resolved groups in Samba `valid users` rules. - Share operations are audited with Samba `full_audit` (connect, list, read, write, create, delete, rename) and written to Samba log files. +- Private home creation skips well-known/service accounts by default (including `krbtgt`, `msol_*`, `FileShare_ServiceAcc`). - Reconciliation is executed: - once on startup - every 5 minutes via cron @@ -130,6 +131,8 @@ Kerberos requires close time alignment. - Root path: `/data/private` - Per-user path: `/data/private/` - Script ensures user directories exist and assigns ownership through winbind identity resolution. +- Root `/data/private` is enforced read/execute-only (`0555`) to prevent folder creation directly under `\\server\Private`. +- SMB-side permission changes on `\\server\Private` are blocked (`nt acl support = no` and security masks set to `0000`). - Permissions: - owner user: full control - Domain Admins: ACL full control diff --git a/app/reconcile_shares.py b/app/reconcile_shares.py index 8236e4a..ccfed0d 100755 --- a/app/reconcile_shares.py +++ b/app/reconcile_shares.py @@ -28,6 +28,16 @@ GROUP_PREFIX = "FileShare_" REQUIRED_ENV = ["REALM", "WORKGROUP", "DOMAIN"] ATTR_RE = re.compile(r"^([^:]+)(::?):\s*(.*)$") SHARE_NAME_INVALID_RE = re.compile(r"[\\/:*?\"<>|;\[\],+=]") +PRIVATE_SKIP_EXACT = { + "krbtgt", + "administrator", + "guest", + "defaultaccount", + "wdagutilityaccount", + "fileshare_serviceacc", + "fileshare_serviceaccount", +} +PRIVATE_SKIP_PREFIXES = ("msol_", "fileshare_service", "aad_") def now_utc() -> str: @@ -460,10 +470,40 @@ def list_domain_users() -> List[str]: candidate = candidate.split("\\", 1)[1] if not candidate or candidate.endswith("$"): continue + if should_skip_private_user(candidate): + continue users.append(candidate) return sorted(set(users)) +def should_skip_private_user(username: str) -> bool: + normalized = username.strip().lower() + if not normalized: + return True + if normalized in PRIVATE_SKIP_EXACT: + return True + if any(normalized.startswith(prefix) for prefix in PRIVATE_SKIP_PREFIXES): + return True + + extra_skip_users = { + value.strip().lower() + for value in os.getenv("PRIVATE_SKIP_USERS", "").split(",") + if value.strip() + } + if normalized in extra_skip_users: + return True + + extra_skip_prefixes = [ + value.strip().lower() + for value in os.getenv("PRIVATE_SKIP_PREFIXES", "").split(",") + if value.strip() + ] + if any(normalized.startswith(prefix) for prefix in extra_skip_prefixes): + return True + + return False + + def sync_public_directory() -> None: workgroup = os.environ["WORKGROUP"] public_group = os.getenv("PUBLIC_GROUP", "Domain Users") @@ -488,7 +528,9 @@ def sync_private_directories() -> None: admin_gid = resolve_group_gid_flexible(workgroup, admin_group) os.makedirs(PRIVATE_ROOT, exist_ok=True) - os.chmod(PRIVATE_ROOT, 0o755) + os.chown(PRIVATE_ROOT, 0, 0) + run_command(["setfacl", "-b", PRIVATE_ROOT], check=False) + os.chmod(PRIVATE_ROOT, 0o555) users = list_domain_users() for username in users: diff --git a/etc/samba/smb.conf b/etc/samba/smb.conf index 1f715af..a156a5f 100644 --- a/etc/samba/smb.conf +++ b/etc/samba/smb.conf @@ -49,10 +49,14 @@ full_audit:failure = all full_audit:syslog = false valid users = +"${DOMAIN_USERS_GROUP}" - admin users = +"${DOMAIN_ADMINS_GROUP}" hide unreadable = yes access based share enum = yes ea support = yes + nt acl support = no + security mask = 0000 + force security mode = 0000 + directory security mask = 0000 + force directory security mode = 0000 [Public] path = /data/public diff --git a/setup b/setup index 3adb2bb..f325a51 100755 --- a/setup +++ b/setup @@ -214,6 +214,8 @@ NETBIOS_NAME=${netbios_name} # Optional overrides: # LDAP_URI=ldaps://${domain} # LDAP_BASE_DN=DC=example,DC=com +# PRIVATE_SKIP_USERS=svc_backup,svc_sql +# PRIVATE_SKIP_PREFIXES=svc_,sql_ EOF chmod 600 "$ENV_FILE"