better Private share folder handling
This commit is contained in:
@@ -10,3 +10,5 @@ PUBLIC_GROUP_SID=S-1-5-21-1111111111-2222222222-3333333333-513
|
|||||||
# NETBIOS_NAME=ADSAMBAFSRV
|
# NETBIOS_NAME=ADSAMBAFSRV
|
||||||
# LDAP_URI=ldaps://example.com
|
# LDAP_URI=ldaps://example.com
|
||||||
# LDAP_BASE_DN=DC=example,DC=com
|
# LDAP_BASE_DN=DC=example,DC=com
|
||||||
|
# PRIVATE_SKIP_USERS=svc_backup,svc_sql
|
||||||
|
# PRIVATE_SKIP_PREFIXES=svc_,sql_
|
||||||
|
|||||||
@@ -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.
|
- 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.
|
- 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.
|
- 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:
|
- Reconciliation is executed:
|
||||||
- once on startup
|
- once on startup
|
||||||
- every 5 minutes via cron
|
- every 5 minutes via cron
|
||||||
@@ -130,6 +131,8 @@ Kerberos requires close time alignment.
|
|||||||
- Root path: `/data/private`
|
- Root path: `/data/private`
|
||||||
- Per-user path: `/data/private/<samAccountName>`
|
- Per-user path: `/data/private/<samAccountName>`
|
||||||
- Script ensures user directories exist and assigns ownership through winbind identity resolution.
|
- 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:
|
- Permissions:
|
||||||
- owner user: full control
|
- owner user: full control
|
||||||
- Domain Admins: ACL full control
|
- Domain Admins: ACL full control
|
||||||
|
|||||||
@@ -28,6 +28,16 @@ GROUP_PREFIX = "FileShare_"
|
|||||||
REQUIRED_ENV = ["REALM", "WORKGROUP", "DOMAIN"]
|
REQUIRED_ENV = ["REALM", "WORKGROUP", "DOMAIN"]
|
||||||
ATTR_RE = re.compile(r"^([^:]+)(::?):\s*(.*)$")
|
ATTR_RE = re.compile(r"^([^:]+)(::?):\s*(.*)$")
|
||||||
SHARE_NAME_INVALID_RE = re.compile(r"[\\/:*?\"<>|;\[\],+=]")
|
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:
|
def now_utc() -> str:
|
||||||
@@ -460,10 +470,40 @@ def list_domain_users() -> List[str]:
|
|||||||
candidate = candidate.split("\\", 1)[1]
|
candidate = candidate.split("\\", 1)[1]
|
||||||
if not candidate or candidate.endswith("$"):
|
if not candidate or candidate.endswith("$"):
|
||||||
continue
|
continue
|
||||||
|
if should_skip_private_user(candidate):
|
||||||
|
continue
|
||||||
users.append(candidate)
|
users.append(candidate)
|
||||||
return sorted(set(users))
|
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:
|
def sync_public_directory() -> None:
|
||||||
workgroup = os.environ["WORKGROUP"]
|
workgroup = os.environ["WORKGROUP"]
|
||||||
public_group = os.getenv("PUBLIC_GROUP", "Domain Users")
|
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)
|
admin_gid = resolve_group_gid_flexible(workgroup, admin_group)
|
||||||
|
|
||||||
os.makedirs(PRIVATE_ROOT, exist_ok=True)
|
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()
|
users = list_domain_users()
|
||||||
for username in users:
|
for username in users:
|
||||||
|
|||||||
@@ -49,10 +49,14 @@
|
|||||||
full_audit:failure = all
|
full_audit:failure = all
|
||||||
full_audit:syslog = false
|
full_audit:syslog = false
|
||||||
valid users = +"${DOMAIN_USERS_GROUP}"
|
valid users = +"${DOMAIN_USERS_GROUP}"
|
||||||
admin users = +"${DOMAIN_ADMINS_GROUP}"
|
|
||||||
hide unreadable = yes
|
hide unreadable = yes
|
||||||
access based share enum = yes
|
access based share enum = yes
|
||||||
ea support = 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]
|
[Public]
|
||||||
path = /data/public
|
path = /data/public
|
||||||
|
|||||||
2
setup
2
setup
@@ -214,6 +214,8 @@ NETBIOS_NAME=${netbios_name}
|
|||||||
# Optional overrides:
|
# Optional overrides:
|
||||||
# LDAP_URI=ldaps://${domain}
|
# LDAP_URI=ldaps://${domain}
|
||||||
# LDAP_BASE_DN=DC=example,DC=com
|
# LDAP_BASE_DN=DC=example,DC=com
|
||||||
|
# PRIVATE_SKIP_USERS=svc_backup,svc_sql
|
||||||
|
# PRIVATE_SKIP_PREFIXES=svc_,sql_
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
chmod 600 "$ENV_FILE"
|
chmod 600 "$ENV_FILE"
|
||||||
|
|||||||
Reference in New Issue
Block a user