From 9bf0694bf297f6b9d966a91c44fc54eddc83f6c9 Mon Sep 17 00:00:00 2001 From: Ludwig Lehnert Date: Mon, 16 Mar 2026 18:50:29 +0100 Subject: [PATCH] added FSLogix share --- .env.example | 1 + Dockerfile | 2 +- README.md | 11 ++++++++++ app/init.sh | 10 ++++++++- app/reconcile_shares.py | 47 +++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 2 ++ etc/samba/smb.conf | 19 +++++++++++++++++ setup | 10 +++++++++ 8 files changed, 100 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 00c6b32..b5cda4a 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,7 @@ JOIN_PASSWORD=ReplaceWithLongRandomPassword DOMAIN_USERS_SID=S-1-5-21-1111111111-2222222222-3333333333-513 DOMAIN_ADMINS_SID=S-1-5-21-1111111111-2222222222-3333333333-512 PUBLIC_GROUP_SID=S-1-5-21-1111111111-2222222222-3333333333-513 +FSLOGIX_GROUP_SID=S-1-5-21-1111111111-2222222222-3333333333-513 # SAMBA_HOSTNAME=adsambafsrv # NETBIOS_NAME=ADSAMBAFSRV # LDAP_URI=ldaps://example.com diff --git a/Dockerfile b/Dockerfile index c181c15..eb61c3e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,7 @@ RUN apt-get update \ winbind \ && rm -rf /var/lib/apt/lists/* -RUN mkdir -p /app /data/private /data/public /data/groups /state /etc/samba/generated +RUN mkdir -p /app /data/private /data/public /data/fslogix /data/groups /state /etc/samba/generated COPY app/reconcile_shares.py /app/reconcile_shares.py COPY app/init.sh /app/init.sh diff --git a/README.md b/README.md index 7a99042..23ed8ac 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ This repository provides a production-oriented Samba file server container that - Static shares: - `\\server\Privat` -> `/data/private` - `\\server\Geteilt` -> `/data/public` + - `\\server\FSLogix` -> `/data/fslogix` - Dynamic shares are generated from AD groups matching `FileShare_*` or `FS_*` and written to `/etc/samba/generated/shares.conf`. - Dynamic share records are persisted in SQLite at `/state/shares.db`. - Backing storage is GUID-based and stable across group rename: @@ -16,6 +17,7 @@ This repository provides a production-oriented Samba file server container that - Container hostname is fixed (`SAMBA_HOSTNAME`) to keep AD computer identity stable. - NetBIOS name defaults to `ADSAMBAFSRV` and is clamped to 15 characters (`NETBIOS_NAME` override supported). - Setup prompts for well-known authorization groups by SID (`DOMAIN_USERS_SID`, `DOMAIN_ADMINS_SID`) to avoid localized group names. +- `FSLOGIX_GROUP_SID` controls who can access the default FSLogix share (defaults to `DOMAIN_USERS_SID`). - 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`). @@ -101,6 +103,7 @@ Kerberos requires close time alignment. - `DOMAIN_USERS_SID` - `DOMAIN_ADMINS_SID` - optional `PUBLIC_GROUP_SID` (defaults to `DOMAIN_USERS_SID`) + - optional `FSLOGIX_GROUP_SID` (defaults to `DOMAIN_USERS_SID`) Optional: - `SAMBA_HOSTNAME` (defaults to `adsambafsrv`) @@ -149,6 +152,14 @@ Kerberos requires close time alignment. - No guest access. - Permissions are reconciled recursively so all descendants remain homogeneous (dirs `2770`, files `0660`, shared group/admin ACLs). +### FSLogix + +- Share: `\\server\FSLogix` +- Path: `/data/fslogix` +- Access for authenticated users in configurable `FSLOGIX_GROUP_SID` (default: `DOMAIN_USERS_SID`, resolved through winbind). +- Semantics intentionally differ from `Geteilt`: only the share root is reconciled (`03770` + ACL defaults), while user-created profile container folders/files are not recursively normalized. +- Samba masks are profile-container oriented (`create mask = 0600`, `directory mask = 0700`) so profile payload stays user-private by default. + ### Dynamic Group Shares - AD groups: `FileShare_*` and `FS_*` diff --git a/app/init.sh b/app/init.sh index ae47f01..25b7ec3 100755 --- a/app/init.sh +++ b/app/init.sh @@ -115,9 +115,13 @@ resolve_share_groups_from_sids() { export PUBLIC_GROUP PUBLIC_GROUP="$(resolve_sid_to_group "$PUBLIC_GROUP_SID")" + export FSLOGIX_GROUP + FSLOGIX_GROUP="$(resolve_sid_to_group "$FSLOGIX_GROUP_SID")" + log "Resolved DOMAIN_USERS_SID to '${DOMAIN_USERS_GROUP}'" log "Resolved DOMAIN_ADMINS_SID to '${DOMAIN_ADMINS_GROUP}'" log "Resolved PUBLIC_GROUP_SID to '${PUBLIC_GROUP}'" + log "Resolved FSLOGIX_GROUP_SID to '${FSLOGIX_GROUP}'" } render_krb5_conf() { @@ -156,9 +160,11 @@ write_runtime_env_file() { printf 'export DOMAIN_USERS_SID=%q\n' "$DOMAIN_USERS_SID" printf 'export DOMAIN_ADMINS_SID=%q\n' "$DOMAIN_ADMINS_SID" printf 'export PUBLIC_GROUP_SID=%q\n' "$PUBLIC_GROUP_SID" + printf 'export FSLOGIX_GROUP_SID=%q\n' "$FSLOGIX_GROUP_SID" printf 'export DOMAIN_USERS_GROUP=%q\n' "$DOMAIN_USERS_GROUP" printf 'export DOMAIN_ADMINS_GROUP=%q\n' "$DOMAIN_ADMINS_GROUP" printf 'export PUBLIC_GROUP=%q\n' "$PUBLIC_GROUP" + printf 'export FSLOGIX_GROUP=%q\n' "$FSLOGIX_GROUP" if [[ -n "${JOIN_USER:-}" ]]; then printf 'export JOIN_USER=%q\n' "$JOIN_USER" fi @@ -222,9 +228,11 @@ require_env DOMAIN_ADMINS_SID export REALM WORKGROUP DOMAIN export PUBLIC_GROUP_SID="${PUBLIC_GROUP_SID:-${DOMAIN_USERS_SID}}" +export FSLOGIX_GROUP_SID="${FSLOGIX_GROUP_SID:-${DOMAIN_USERS_SID}}" export DOMAIN_USERS_GROUP="${DOMAIN_USERS_SID}" export DOMAIN_ADMINS_GROUP="${DOMAIN_ADMINS_SID}" export PUBLIC_GROUP="${PUBLIC_GROUP_SID}" +export FSLOGIX_GROUP="${FSLOGIX_GROUP_SID}" if [[ -n "${JOIN_USER:-}" ]]; then export JOIN_USER fi @@ -232,7 +240,7 @@ if [[ -n "${JOIN_PASSWORD:-}" ]]; then export JOIN_PASSWORD fi -mkdir -p /data/private /data/public /data/groups /state /etc/samba/generated /var/log/samba +mkdir -p /data/private /data/public /data/fslogix /data/groups /state /etc/samba/generated /var/log/samba touch /etc/samba/generated/shares.conf /var/log/reconcile.log append_winbind_to_nss diff --git a/app/reconcile_shares.py b/app/reconcile_shares.py index e52295b..9a76c38 100755 --- a/app/reconcile_shares.py +++ b/app/reconcile_shares.py @@ -20,6 +20,7 @@ LOCK_PATH = "/state/reconcile.lock" GROUP_ROOT = "/data/groups" PRIVATE_ROOT = "/data/private" PUBLIC_ROOT = "/data/public" +FSLOGIX_ROOT = "/data/fslogix" GENERATED_CONF = "/etc/samba/generated/shares.conf" LDAP_FILTER = ( @@ -715,6 +716,51 @@ def sync_public_directory() -> None: log(f"Unable to resolve GID for {group_display}; public ACLs unchanged") +def sync_fslogix_directory() -> None: + workgroup = os.environ["WORKGROUP"] + fslogix_group = os.getenv("FSLOGIX_GROUP", "") + fslogix_group_sid = os.getenv("FSLOGIX_GROUP_SID", "") + qualified_group = fslogix_group + + os.makedirs(FSLOGIX_ROOT, exist_ok=True) + + gid = None + if qualified_group: + gid = resolve_group_gid_flexible(workgroup, qualified_group) + if gid is None and fslogix_group_sid: + gid = resolve_gid_from_sid(fslogix_group_sid) + + if gid is None: + group_display = qualified_group or fslogix_group_sid or "" + log(f"Unable to resolve GID for {group_display}; fslogix ACLs unchanged") + return + + admin_group = os.getenv("DOMAIN_ADMINS_GROUP", "") + admin_gid = None + if admin_group: + admin_gid = resolve_group_gid_flexible(workgroup, admin_group) + if admin_gid is None: + admin_gid = resolve_gid_from_sid(os.getenv("DOMAIN_ADMINS_SID", "")) + + os.chown(FSLOGIX_ROOT, 0, gid) + os.chmod(FSLOGIX_ROOT, 0o3770) + run_command(["setfacl", "-b", FSLOGIX_ROOT], check=False) + + acl_entries = [f"g:{gid}:rwx", f"d:g:{gid}:rwx"] + if admin_gid is not None and admin_gid != gid: + acl_entries.append(f"g:{admin_gid}:rwx") + acl_entries.append(f"d:g:{admin_gid}:rwx") + + result = run_command( + ["setfacl", "-m", ",".join(acl_entries), FSLOGIX_ROOT], check=False + ) + if result.returncode != 0: + log( + "setfacl failed for fslogix root: " + f"{result.stderr.strip() or result.stdout.strip()}" + ) + + def sync_private_directories() -> None: workgroup = os.environ["WORKGROUP"] admin_group = os.getenv("DOMAIN_ADMINS_GROUP", "") @@ -800,6 +846,7 @@ def with_lock() -> bool: conn.close() sync_public_directory() + sync_fslogix_directory() sync_private_directories() reload_samba() log("Reconciliation completed") diff --git a/docker-compose.yml b/docker-compose.yml index a55621d..5d98dcf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,6 +15,7 @@ services: volumes: - private_data:/data/private - public_data:/data/public + - fslogix_data:/data/fslogix - group_data:/data/groups - state_data:/state - samba_lib_data:/var/lib/samba @@ -28,6 +29,7 @@ services: volumes: private_data: public_data: + fslogix_data: group_data: state_data: samba_lib_data: diff --git a/etc/samba/smb.conf b/etc/samba/smb.conf index ee2d031..2a5a20b 100644 --- a/etc/samba/smb.conf +++ b/etc/samba/smb.conf @@ -71,3 +71,22 @@ directory mask = 2770 inherit permissions = yes access based share enum = yes + +[FSLogix] + path = /data/fslogix + read only = no + browseable = yes + guest ok = no + vfs objects = acl_xattr full_audit + full_audit:prefix = %T|%u|%I|%m|%S + full_audit:success = all + full_audit:failure = all + full_audit:syslog = false + valid users = @"${FSLOGIX_GROUP}" + force group = "${FSLOGIX_GROUP}" + create mask = 0600 + directory mask = 0700 + force create mode = 0600 + force directory mode = 0700 + hide unreadable = yes + access based share enum = yes diff --git a/setup b/setup index f325a51..f5ffbcd 100755 --- a/setup +++ b/setup @@ -84,11 +84,13 @@ write_env_file() { local domain_users_sid="" local domain_admins_sid="" local public_group_sid="" + local fslogix_group_sid="" local samba_hostname="adsambafsrv" local netbios_name="ADSAMBAFSRV" local service_password="" local service_account_sam="" local public_group_prompt="" + local fslogix_group_prompt="" local samba_hostname_input="" local netbios_name_input="" local sanitized_netbios_name="" @@ -107,6 +109,12 @@ write_env_file() { public_group_sid="$domain_users_sid" fi + fslogix_group_prompt="FSLOGIX_GROUP_SID (press Enter to reuse DOMAIN_USERS_SID)" + read -r -p "${fslogix_group_prompt}: " fslogix_group_sid + if [[ -z "$fslogix_group_sid" ]]; then + fslogix_group_sid="$domain_users_sid" + fi + read -r -p "SAMBA_HOSTNAME [adsambafsrv]: " samba_hostname_input if [[ -n "${samba_hostname_input:-}" ]]; then samba_hostname="$samba_hostname_input" @@ -158,6 +166,7 @@ SERVICE_ACCOUNT_PASSWORD=${service_password} DOMAIN_USERS_SID=${domain_users_sid} DOMAIN_ADMINS_SID=${domain_admins_sid} PUBLIC_GROUP_SID=${public_group_sid} +FSLOGIX_GROUP_SID=${fslogix_group_sid} SAMBA_HOSTNAME=${samba_hostname} NETBIOS_NAME=${netbios_name} EOF @@ -209,6 +218,7 @@ JOIN_PASSWORD=${service_password} DOMAIN_USERS_SID=${domain_users_sid} DOMAIN_ADMINS_SID=${domain_admins_sid} PUBLIC_GROUP_SID=${public_group_sid} +FSLOGIX_GROUP_SID=${fslogix_group_sid} SAMBA_HOSTNAME=${samba_hostname} NETBIOS_NAME=${netbios_name} # Optional overrides: