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 Dashboard-Login per AD (User-Bind, kein Servicekonto): Der User bindet mit
seinen eigenen Credentials als `user@domain`, die Gruppenmitgliedschaft wird 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 Nur Mitglieder von `LDAP_ADMIN_GROUP` bekommen Zugang. Lokal angelegte User
(Initial-Admin) bleiben als Fallback wirksam. (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 1. The user binds with their own credentials as ``user@domain`` (AD accepts
userPrincipalName as bind DN). userPrincipalName as bind DN).
2. Over that authenticated connection we search the user's own directory 2. Over that authenticated connection we search the user's own directory
entry, restricted by the in-chain matching rule to members of the entry and verify direct membership in the configured admin group via
configured admin group (direct or nested membership). the ``memberOf`` attribute.
Only members of the configured group may log in. All other users are Only members of the configured group may log in. All other users are
rejected before any local session is created. 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 import ssl
@@ -20,9 +25,6 @@ from ldap3.utils.conv import escape_filter_chars
from app.core.config import Settings 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): class LdapAuthError(Exception):
"""Authentication failed. Message is safe to show to the user.""" """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( user_filter = settings.ldap_user_filter.format(
username=escape_filter_chars(username.strip()) username=escape_filter_chars(username.strip())
) )
# One search proves: user exists AND is (transitively) in the admin # One search proves: user exists AND is a direct member of the
# group. Non-members simply match nothing. # configured admin group. Non-members simply match nothing.
group_filter = f"(memberOf:{_MATCHING_RULE_IN_CHAIN}:={settings.ldap_admin_group})" # (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( ok = conn.search(
search_base=settings.ldap_user_search_base, search_base=settings.ldap_user_search_base,
search_filter=f"(&{user_filter}{group_filter})", 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 assert user.is_admin is True
# bind DN uses user@domain format # bind DN uses user@domain format
assert mock_ldap3.Connection.call_args.kwargs["user"] == "flo@insight.local" 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 search_kwargs = conn.search.call_args.kwargs
assert "memberOf:1.2.840.113556.1941:=" in search_kwargs["search_filter"] assert "(memberOf=CN=Domain Admins,CN=Users,DC=insight,DC=local)" in search_kwargs["search_filter"]
assert "CN=Domain Admins,CN=Users,DC=insight,DC=local" in search_kwargs["search_filter"]
assert search_kwargs["search_base"] == "DC=insight,DC=local" assert search_kwargs["search_base"] == "DC=insight,DC=local"