better folder/share names
This commit is contained in:
19
README.md
19
README.md
@@ -1,12 +1,12 @@
|
||||
# AD-Integrated Containerized Samba File Server
|
||||
|
||||
This repository provides a production-oriented Samba file server container that joins an existing Active Directory domain and exposes three SMB shares: `Privat`, `Data`, and `FSLogix`.
|
||||
This repository provides a production-oriented Samba file server container that joins an existing Active Directory domain and exposes three SMB shares: `Private`, `Data`, and `FSLogix`.
|
||||
|
||||
## Architecture
|
||||
|
||||
- Samba runs in ADS mode with `winbind` identity mapping.
|
||||
- Static shares:
|
||||
- `\\server\Privat` -> `/data/private`
|
||||
- `\\server\Private` -> `/data/private`
|
||||
- `\\server\Data` -> `/data/groups/data`
|
||||
- `\\server\FSLogix` -> `/data/fslogix`
|
||||
- FS_* groups are projected as folders inside the Data share (`/data/groups/data/<groupName>`).
|
||||
@@ -62,8 +62,9 @@ CREATE TABLE shares (
|
||||
- Initial admin credentials with rights to create/reset `FileShare_ServiceAccount` during `./setup`.
|
||||
- `FileShare_ServiceAccount` must be allowed to join computers to the domain (`net ads join`) in your AD policy.
|
||||
- Dynamic group discovery primarily uses machine-account LDAP (`net ads search -P`); join credentials are only used as a fallback LDAP bind path.
|
||||
- Group naming convention for Data folders:
|
||||
- `FS_<FolderName>`
|
||||
- Group naming convention for Data folder eligibility:
|
||||
- `FS_<Anything>`
|
||||
- Folder names use AD group display names (`displayName`, then `name`/`cn` fallback), not pre-2000 (`sAMAccountName`) names.
|
||||
|
||||
## DNS Requirements
|
||||
|
||||
@@ -137,14 +138,14 @@ Kerberos requires close time alignment.
|
||||
|
||||
## SMB Shares
|
||||
|
||||
### Privat
|
||||
### Private
|
||||
|
||||
- Share: `\\server\Privat`
|
||||
- Share: `\\server\Private`
|
||||
- Root path: `/data/private`
|
||||
- Per-user path: `/data/private/<samAccountName>`
|
||||
- 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\Privat`.
|
||||
- SMB-side ACL changes on `\\server\Privat` are blocked (`nt acl support = no`).
|
||||
- Root `/data/private` is enforced read/execute-only (`0555`) to prevent folder creation directly under `\\server\Private`.
|
||||
- SMB-side ACL changes on `\\server\Private` are blocked (`nt acl support = no`).
|
||||
- Auto-creation skips well-known/service/non-login accounts (disabled, locked, or expired).
|
||||
- Each private user tree is reconciled recursively to homogeneous permissions (dirs `0700`, files `0600`, user/admin ACLs).
|
||||
- Permissions:
|
||||
@@ -279,7 +280,7 @@ docker compose exec samba sh -lc 'tail -n 200 /var/log/backup.log'
|
||||
docker compose exec samba sh -lc 'mods="$(smbd -b | sed -n "s/^ *MODULESDIR: //p" | head -n1)/vfs"; ls -1 "$mods"/acl_xattr.so "$mods"/full_audit.so'
|
||||
```
|
||||
|
||||
### Permissions in Privat share are incorrect
|
||||
### Permissions in Private share are incorrect
|
||||
|
||||
- Re-run reconciliation to rebuild private directories and ACLs:
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ FSLOGIX_ROOT = "/data/fslogix"
|
||||
|
||||
LDAP_FILTER = "(&(objectClass=group)(sAMAccountName=FS_*))"
|
||||
GROUP_PREFIXES = ("FS_",)
|
||||
GROUP_TITLE_ATTRS = ("displayname", "name", "cn")
|
||||
USER_STATUS_FILTER = "(&(objectClass=user)(!(objectClass=computer))(sAMAccountName=*))"
|
||||
|
||||
REQUIRED_ENV = ["REALM", "WORKGROUP", "DOMAIN"]
|
||||
@@ -122,6 +123,15 @@ def derive_share_name(sam_account_name: str) -> Optional[str]:
|
||||
return None
|
||||
|
||||
|
||||
def derive_group_title(entry: Dict[str, Tuple[str, bool]]) -> Optional[str]:
|
||||
for attr in GROUP_TITLE_ATTRS:
|
||||
if attr in entry:
|
||||
value = entry[attr][0].strip()
|
||||
if value:
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
def parse_groups_from_ldap_output(output: str) -> List[Dict[str, str]]:
|
||||
entries = parse_ldap_entries(output)
|
||||
|
||||
@@ -132,7 +142,7 @@ def parse_groups_from_ldap_output(output: str) -> List[Dict[str, str]]:
|
||||
|
||||
sam_value, _ = entry["samaccountname"]
|
||||
sam = sam_value.strip()
|
||||
share_name = derive_share_name(sam)
|
||||
share_name = derive_group_title(entry) or derive_share_name(sam)
|
||||
if not share_name:
|
||||
continue
|
||||
|
||||
@@ -223,7 +233,18 @@ def next_available_path(path: str) -> str:
|
||||
|
||||
def fetch_groups_via_net_ads() -> List[Dict[str, str]]:
|
||||
result = run_command(
|
||||
["net", "ads", "search", "-P", LDAP_FILTER, "objectGUID", "sAMAccountName"],
|
||||
[
|
||||
"net",
|
||||
"ads",
|
||||
"search",
|
||||
"-P",
|
||||
LDAP_FILTER,
|
||||
"objectGUID",
|
||||
"sAMAccountName",
|
||||
"displayName",
|
||||
"name",
|
||||
"cn",
|
||||
],
|
||||
check=False,
|
||||
)
|
||||
if result.returncode != 0:
|
||||
@@ -270,6 +291,9 @@ def fetch_groups_via_ldap_bind() -> List[Dict[str, str]]:
|
||||
LDAP_FILTER,
|
||||
"objectGUID",
|
||||
"sAMAccountName",
|
||||
"displayName",
|
||||
"name",
|
||||
"cn",
|
||||
]
|
||||
)
|
||||
return parse_groups_from_ldap_output(result.stdout)
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
logging = file
|
||||
log level = 1 auth:5 passdb:5 winbind:3
|
||||
|
||||
[Privat]
|
||||
[Private]
|
||||
path = /data/private
|
||||
read only = no
|
||||
browseable = yes
|
||||
|
||||
Reference in New Issue
Block a user