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:
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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})",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user