LDAP: Gruppenpruefung auf direktes memberOf umstellen

Matching-Rule-In-Chain liefert gegen die insight.local-DCs grundsaetzlich
keine Treffer (auch nicht bei ASCII-Gruppen und direkter Mitgliedschaft) -
live im Container verifiziert. Direkte memberOf-Pruefung funktioniert,
verschachtelte Gruppen werden bewusst nicht aufgeloest.
This commit is contained in:
B0rbor4d
2026-09-28 22:16:53 +00:00
parent 80610fdd30
commit 95291df971
3 changed files with 17 additions and 12 deletions
+2 -1
View File
@@ -103,7 +103,8 @@ WinRM/SSH-Config ist in den Satellite gewandert (dessen `config.yaml`).
Dashboard-Login per AD (User-Bind, kein Servicekonto): Der User bindet mit
seinen eigenen Credentials als `user@domain`, die Gruppenmitgliedschaft wird
ueber die Matching-Rule-In-Chain geprueft (verschachtelte Gruppen aufloesend).
ueber das `memberOf`-Attribut geprueft (direkte Mitgliedschaft; die
Matching-Rule-In-Chain funktioniert gegen die insight.local-DCs nicht).
Nur Mitglieder von `LDAP_ADMIN_GROUP` bekommen Zugang. Lokal angelegte User
(Initial-Admin) bleiben als Fallback wirksam.
+13 -8
View File
@@ -4,11 +4,16 @@ User-bind design (no service account required):
1. The user binds with their own credentials as ``user@domain`` (AD accepts
userPrincipalName as bind DN).
2. Over that authenticated connection we search the user's own directory
entry, restricted by the in-chain matching rule to members of the
configured admin group (direct or nested membership).
entry and verify direct membership in the configured admin group via
the ``memberOf`` attribute.
Only members of the configured group may log in. All other users are
rejected before any local session is created.
Note: nested group membership is intentionally not resolved. The
Matching-Rule-In-Chain (1.2.840.113556.1941) was tested against the
insight.local DCs and returned no matches at all, even for direct
members with ASCII-only group DNs - so we check ``memberOf`` directly.
"""
import ssl
@@ -20,9 +25,6 @@ from ldap3.utils.conv import escape_filter_chars
from app.core.config import Settings
# LDAP_MATCHING_RULE_IN_CHAIN: resolves nested group membership server-side
_MATCHING_RULE_IN_CHAIN = "1.2.840.113556.1941"
class LdapAuthError(Exception):
"""Authentication failed. Message is safe to show to the user."""
@@ -89,9 +91,12 @@ def authenticate(username: str, password: str, settings: Settings) -> LdapUser:
user_filter = settings.ldap_user_filter.format(
username=escape_filter_chars(username.strip())
)
# One search proves: user exists AND is (transitively) in the admin
# group. Non-members simply match nothing.
group_filter = f"(memberOf:{_MATCHING_RULE_IN_CHAIN}:={settings.ldap_admin_group})"
# One search proves: user exists AND is a direct member of the
# configured admin group. Non-members simply match nothing.
# (memberOf is the backlink of the group's member attribute; the
# matching-rule-in-chain was rejected because it does not work
# against these DCs, see module docstring.)
group_filter = f"(memberOf={settings.ldap_admin_group})"
ok = conn.search(
search_base=settings.ldap_user_search_base,
search_filter=f"(&{user_filter}{group_filter})",
+2 -3
View File
@@ -98,10 +98,9 @@ def test_success_returns_admin_user(monkeypatch: pytest.MonkeyPatch) -> None:
assert user.is_admin is True
# bind DN uses user@domain format
assert mock_ldap3.Connection.call_args.kwargs["user"] == "flo@insight.local"
# search filter proves admin-group membership via in-chain rule
# search filter proves admin-group membership via memberOf
search_kwargs = conn.search.call_args.kwargs
assert "memberOf:1.2.840.113556.1941:=" in search_kwargs["search_filter"]
assert "CN=Domain Admins,CN=Users,DC=insight,DC=local" in search_kwargs["search_filter"]
assert "(memberOf=CN=Domain Admins,CN=Users,DC=insight,DC=local)" in search_kwargs["search_filter"]
assert search_kwargs["search_base"] == "DC=insight,DC=local"