attempted fix on authentication failures
This commit is contained in:
@@ -16,6 +16,7 @@ This repository provides a production-oriented Samba file server container that
|
|||||||
- Container hostname is fixed (`SAMBA_HOSTNAME`) to keep AD computer identity stable.
|
- 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).
|
- 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.
|
- 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.
|
||||||
- Reconciliation is executed:
|
- Reconciliation is executed:
|
||||||
- once on startup
|
- once on startup
|
||||||
- every 5 minutes via cron
|
- every 5 minutes via cron
|
||||||
|
|||||||
30
app/init.sh
30
app/init.sh
@@ -37,22 +37,40 @@ derive_netbios_name() {
|
|||||||
|
|
||||||
resolve_sid_to_group() {
|
resolve_sid_to_group() {
|
||||||
local sid="$1"
|
local sid="$1"
|
||||||
|
local resolved_name=""
|
||||||
local group_name=""
|
local group_name=""
|
||||||
|
local short_name=""
|
||||||
local sid_output=""
|
local sid_output=""
|
||||||
|
|
||||||
if sid_output="$(wbinfo --sid-to-fullname "$sid" 2>/dev/null)"; then
|
if sid_output="$(wbinfo --sid-to-fullname "$sid" 2>/dev/null)"; then
|
||||||
group_name="${sid_output%%$'\t'*}"
|
resolved_name="${sid_output%%$'\t'*}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "$group_name" ]] && sid_output="$(wbinfo -s "$sid" 2>/dev/null)"; then
|
if [[ -z "$resolved_name" ]] && sid_output="$(wbinfo -s "$sid" 2>/dev/null)"; then
|
||||||
group_name="$(printf '%s' "$sid_output" | sed -E 's/[[:space:]]+[0-9]+$//')"
|
resolved_name="$(printf '%s' "$sid_output" | sed -E 's/[[:space:]]+[0-9]+$//')"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "$group_name" ]]; then
|
if [[ -z "$resolved_name" ]]; then
|
||||||
printf '[init] ERROR: unable to resolve SID %s via winbind\n' "$sid" >&2
|
printf '[init] ERROR: unable to resolve SID %s via winbind\n' "$sid" >&2
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
group_name="$resolved_name"
|
||||||
|
if getent group "$group_name" >/dev/null 2>&1; then
|
||||||
|
printf '%s\n' "$group_name"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
short_name="$group_name"
|
||||||
|
if [[ "$short_name" == *\\* ]]; then
|
||||||
|
short_name="${short_name#*\\}"
|
||||||
|
fi
|
||||||
|
if [[ -n "$short_name" ]] && getent group "$short_name" >/dev/null 2>&1; then
|
||||||
|
printf '%s\n' "$short_name"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "SID ${sid} resolved to '${resolved_name}', but NSS group lookup failed; using raw name."
|
||||||
printf '%s\n' "$group_name"
|
printf '%s\n' "$group_name"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,6 +83,10 @@ resolve_share_groups_from_sids() {
|
|||||||
|
|
||||||
export PUBLIC_GROUP
|
export PUBLIC_GROUP
|
||||||
PUBLIC_GROUP="$(resolve_sid_to_group "$PUBLIC_GROUP_SID")"
|
PUBLIC_GROUP="$(resolve_sid_to_group "$PUBLIC_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}'"
|
||||||
}
|
}
|
||||||
|
|
||||||
render_krb5_conf() {
|
render_krb5_conf() {
|
||||||
|
|||||||
@@ -275,10 +275,7 @@ def reconcile_db(conn: sqlite3.Connection, ad_groups: List[Dict[str, str]]) -> N
|
|||||||
|
|
||||||
|
|
||||||
def qualify_group(group_name: str) -> str:
|
def qualify_group(group_name: str) -> str:
|
||||||
workgroup = os.getenv("WORKGROUP", "").strip()
|
return f'+"{group_name}"'
|
||||||
if workgroup:
|
|
||||||
return f'@"{workgroup}\\{group_name}"'
|
|
||||||
return f"@{group_name}"
|
|
||||||
|
|
||||||
|
|
||||||
def is_valid_share_name(share_name: str) -> bool:
|
def is_valid_share_name(share_name: str) -> bool:
|
||||||
@@ -372,6 +369,38 @@ def resolve_group_gid(qualified_group: str) -> Optional[int]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_user_uid_flexible(workgroup: str, username: str) -> Optional[int]:
|
||||||
|
candidates: List[str] = []
|
||||||
|
if "\\" in username:
|
||||||
|
candidates.append(username)
|
||||||
|
candidates.append(username.split("\\", 1)[1])
|
||||||
|
else:
|
||||||
|
candidates.append(f"{workgroup}\\{username}")
|
||||||
|
candidates.append(username)
|
||||||
|
|
||||||
|
for candidate in candidates:
|
||||||
|
uid = resolve_user_uid(candidate)
|
||||||
|
if uid is not None:
|
||||||
|
return uid
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_group_gid_flexible(workgroup: str, group_name: str) -> Optional[int]:
|
||||||
|
candidates: List[str] = []
|
||||||
|
if "\\" in group_name:
|
||||||
|
candidates.append(group_name)
|
||||||
|
candidates.append(group_name.split("\\", 1)[1])
|
||||||
|
else:
|
||||||
|
candidates.append(f"{workgroup}\\{group_name}")
|
||||||
|
candidates.append(group_name)
|
||||||
|
|
||||||
|
for candidate in candidates:
|
||||||
|
gid = resolve_group_gid(candidate)
|
||||||
|
if gid is not None:
|
||||||
|
return gid
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def set_acl(path: str, user_uid: int, admin_gid: Optional[int]) -> None:
|
def set_acl(path: str, user_uid: int, admin_gid: Optional[int]) -> None:
|
||||||
run_command(["setfacl", "-b", path], check=False)
|
run_command(["setfacl", "-b", path], check=False)
|
||||||
acl_entries = [f"u:{user_uid}:rwx", f"d:u:{user_uid}:rwx"]
|
acl_entries = [f"u:{user_uid}:rwx", f"d:u:{user_uid}:rwx"]
|
||||||
@@ -433,12 +462,10 @@ def list_domain_users() -> List[str]:
|
|||||||
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")
|
||||||
qualified_group = (
|
qualified_group = public_group
|
||||||
public_group if "\\" in public_group else f"{workgroup}\\{public_group}"
|
|
||||||
)
|
|
||||||
|
|
||||||
os.makedirs(PUBLIC_ROOT, exist_ok=True)
|
os.makedirs(PUBLIC_ROOT, exist_ok=True)
|
||||||
gid = resolve_group_gid(qualified_group)
|
gid = resolve_group_gid_flexible(workgroup, qualified_group)
|
||||||
|
|
||||||
if gid is not None:
|
if gid is not None:
|
||||||
os.chown(PUBLIC_ROOT, 0, gid)
|
os.chown(PUBLIC_ROOT, 0, gid)
|
||||||
@@ -452,18 +479,17 @@ def sync_public_directory() -> None:
|
|||||||
|
|
||||||
def sync_private_directories() -> None:
|
def sync_private_directories() -> None:
|
||||||
workgroup = os.environ["WORKGROUP"]
|
workgroup = os.environ["WORKGROUP"]
|
||||||
admin_group = f"{workgroup}\\Domain Admins"
|
admin_group = os.getenv("DOMAIN_ADMINS_GROUP", "Domain Admins")
|
||||||
admin_gid = resolve_group_gid(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.chmod(PRIVATE_ROOT, 0o755)
|
||||||
|
|
||||||
users = list_domain_users()
|
users = list_domain_users()
|
||||||
for username in users:
|
for username in users:
|
||||||
qualified_user = f"{workgroup}\\{username}"
|
uid = resolve_user_uid_flexible(workgroup, username)
|
||||||
uid = resolve_user_uid(qualified_user)
|
|
||||||
if uid is None:
|
if uid is None:
|
||||||
log(f"Unable to resolve UID for {qualified_user}, skipping private folder")
|
log(f"Unable to resolve UID for {username}, skipping private folder")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
user_path = os.path.join(PRIVATE_ROOT, username)
|
user_path = os.path.join(PRIVATE_ROOT, username)
|
||||||
@@ -475,7 +501,8 @@ def sync_private_directories() -> None:
|
|||||||
|
|
||||||
def sync_dynamic_directory_permissions(conn: sqlite3.Connection) -> None:
|
def sync_dynamic_directory_permissions(conn: sqlite3.Connection) -> None:
|
||||||
workgroup = os.environ["WORKGROUP"]
|
workgroup = os.environ["WORKGROUP"]
|
||||||
admin_gid = resolve_group_gid(f"{workgroup}\\Domain Admins")
|
admin_group = os.getenv("DOMAIN_ADMINS_GROUP", "Domain Admins")
|
||||||
|
admin_gid = resolve_group_gid_flexible(workgroup, admin_group)
|
||||||
|
|
||||||
rows = conn.execute(
|
rows = conn.execute(
|
||||||
"SELECT samAccountName, path FROM shares WHERE isActive = 1"
|
"SELECT samAccountName, path FROM shares WHERE isActive = 1"
|
||||||
@@ -486,9 +513,9 @@ def sync_dynamic_directory_permissions(conn: sqlite3.Connection) -> None:
|
|||||||
os.makedirs(path, exist_ok=True)
|
os.makedirs(path, exist_ok=True)
|
||||||
os.chmod(path, 0o2770)
|
os.chmod(path, 0o2770)
|
||||||
|
|
||||||
gid = resolve_group_gid(f"{workgroup}\\{sam}")
|
gid = resolve_group_gid_flexible(workgroup, sam)
|
||||||
if gid is None:
|
if gid is None:
|
||||||
log(f"Unable to resolve GID for {workgroup}\\{sam}; leaving existing ACLs")
|
log(f"Unable to resolve GID for {sam}; leaving existing ACLs")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
os.chown(path, 0, gid)
|
os.chown(path, 0, gid)
|
||||||
|
|||||||
@@ -42,8 +42,8 @@
|
|||||||
read only = no
|
read only = no
|
||||||
browseable = yes
|
browseable = yes
|
||||||
guest ok = no
|
guest ok = no
|
||||||
valid users = @"${DOMAIN_USERS_GROUP}"
|
valid users = +"${DOMAIN_USERS_GROUP}"
|
||||||
admin users = @"${DOMAIN_ADMINS_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
|
||||||
@@ -53,7 +53,7 @@
|
|||||||
read only = no
|
read only = no
|
||||||
browseable = yes
|
browseable = yes
|
||||||
guest ok = no
|
guest ok = no
|
||||||
valid users = @"${PUBLIC_GROUP}"
|
valid users = +"${PUBLIC_GROUP}"
|
||||||
force group = "${PUBLIC_GROUP}"
|
force group = "${PUBLIC_GROUP}"
|
||||||
create mask = 0660
|
create mask = 0660
|
||||||
directory mask = 2770
|
directory mask = 2770
|
||||||
|
|||||||
Reference in New Issue
Block a user