--- source_sha: "c3245b98d9c8" --- # Verzeichnisanmeldung und Gruppen { #directory-sign-in-and-groups } Ein Unternehmen, das Active Directory, OpenLDAP oder FreeIPA betreibt, weiß bereits, wer dort arbeitet und zu welchen Teams diese Menschen gehören. Diese Seite beschreibt die drei Wege, auf denen AgenticOS dieses Wissen nutzen kann. Menschen können sich mit ihrem Verzeichniskonto anmelden. Ein Browser auf einem Rechner in der Domäne kann sie mit seinem Kerberos-Ticket anmelden. Und Verzeichnisgruppen können entscheiden, welchen Organisationen Menschen beitreten, mit welcher Rolle, und in welchen Gruppen sie sind. Drei Bausteine ergeben das, und sie funktionieren unabhängig voneinander: | Baustein | Was er tut | Konfiguriert von | |---|---|---| | [Gruppen](#groups) | Benannte Mengen von Mitgliedern einer Organisation, mit denen eine Ressource geteilt werden kann | Mitgliedern mit `members:manage`, in der Konsole | | [Zuordnungen von Verzeichnisgruppen](#directory-group-mappings) | „Jeder in dieser Verzeichnisgruppe tritt mit dieser Rolle bei, in diese Gruppe“ | Mitgliedern mit `members:manage` und `roles:manage` | | [Verzeichnisanmeldung](#signing-in-with-a-directory-account) | Anmeldung per LDAP-Passwort und per Kerberos (SPNEGO) | Dem Betreiber, in `backend/.env` | Die Zuordnungen gelten auch für Menschen, die sich über [OIDC](configuration.md#single-sign-on-generic-oidc) anmelden, wenn der Identitätsanbieter ihre Gruppen meldet. Betreibt Ihr Unternehmen bereits Keycloak oder Entra ID vor seinem Verzeichnis, brauchen Sie die native LDAP-Anmeldung womöglich gar nicht. Siehe [Stattdessen über einen Identitätsanbieter](#through-an-identity-provider-instead). ## Gruppen { #groups } Eine Gruppe ist eine benannte Menge von Mitgliedern innerhalb einer Organisation: *Finance*, *Support team*, *Platform admins*. Einen Agent, einen Skill, eine Collection, eine Context-Datei, ein Vault-Secret oder eine App teilen Sie mit einer Gruppe auf dieselbe Weise wie mit einer Person, über das Panel **Sharing** der Ressource. Ein Grant erreicht dann jeden in der Gruppe. Ein Gruppen-Grant erreicht, wer in der Gruppe ist, **wenn der Zugriff geprüft wird**. Wer der Gruppe nächsten Monat beitritt, erreicht die Ressource; wer sie verlässt, erreicht sie nicht mehr. Es gibt nichts, was je Person widerrufen werden müsste. Eine Gruppe trägt keine Rolle. Was ein Mitglied in der Organisation darf, ist weiterhin die Rolle seiner Mitgliedschaft, und eine Gruppe fügt nur die Grants hinzu, die an sie vergeben wurden. Der effektive Zugriff bleibt die Regel, die unter [Berechtigungen](permissions.md#how-the-layers-combine) beschrieben ist: ``` effective access to one row = max(role scope, the person's grant, their groups' grants) ``` Erreicht eine Person eine Zeile über mehrere Grants, gewinnt die höchste Stufe. Lesen über den eigenen Grant und Bearbeiten über den der Gruppe ergibt Bearbeiten. | Handlung | Wer darf | |---|---| | Gruppen auflisten und sehen, wer in ihnen ist | Jedes Mitglied der Organisation | | Eine Gruppe anlegen, umbenennen oder löschen; Personen hinzufügen oder entfernen | `members:manage` | | Eine Ressource mit einer Gruppe teilen | Wer diese Ressource bearbeiten darf | Gruppen liegen unter **Organizations → Members → Groups**. Nur Mitglieder der Organisation können hinzugefügt werden, und wer aus der Organisation entfernt wird, wird aus all ihren Gruppen entfernt. Eine Gruppe zu löschen löscht jeden an sie vergebenen Grant und jede Verzeichniszuordnung, die sie benennt, und verlangt deshalb dieselbe Berechtigung wie das Löschen dieser Zuordnungen: Ein Admin kann keine Gruppe löschen, auf die eine Zuordnung auf Admin-Ebene zeigt. ## Zuordnungen von Verzeichnisgruppen { #directory-group-mappings } Eine Zuordnung sagt, was eine Verzeichnisgruppe innerhalb einer Organisation bedeutet: > Jeder in `CN=Finance,OU=Groups,DC=corp,DC=example,DC=com` ist hier **Mitglied** > (`member`) und ist in der Gruppe **Finance**. Zuordnungen liegen unter **Organizations → Members → Directory**. Sie zu lesen verlangt `members:manage`. Eine anzulegen oder zu löschen verlangt `members:manage` und `roles:manage`, denn eine Zuordnung lässt Menschen sowohl ein als auch gibt sie ihnen eine Rolle. Die Rolle ist auf dieselbe Weise begrenzt wie die einer Einladung. Sie können eine Verzeichnisgruppe nur einer Rolle zuordnen, die Ihre eigene Rolle strikt übertrifft. Niemand kann eine Gruppe `owner` zuordnen, und ein Admin (`admin`) kann eine Gruppe nicht Admin zuordnen. Eine Zuordnung zu löschen verlangt dieselbe Befugnis, denn es stuft jeden herab, den sie eingeordnet hat. ### Was bei der Anmeldung geschieht { #what-happens-at-sign-in } Jedes Mal, wenn sich jemand per LDAP, per Kerberos oder über OIDC mit gesetztem [`OIDC_GROUPS_CLAIM`](#the-groups-claim-over-oidc) anmeldet, werden seine Verzeichnisgruppen gegen die Zuordnungen jeder Organisation gelesen: 1. **Einer Organisation mit passender Zuordnung** wird beigetreten, falls die Person noch kein Mitglied ist, mit der zugeordneten Rolle. Die Mitgliedschaft wird als **directory** gekennzeichnet. 2. **Bei einer Verzeichnismitgliedschaft** wird die Rolle an die Zuordnungen angeglichen. 3. **Die Person tritt den zugeordneten Gruppen bei** und verlässt die vom Verzeichnis angelegten Gruppenmitgliedschaften, die keine Zuordnung mehr benennt. 4. **Eine Verzeichnismitgliedschaft ohne verbliebene passende Zuordnung** wird entfernt, zusammen mit den Gruppenmitgliedschaften der Person in dieser Organisation. Gruppen werden ohne Rücksicht auf Groß- und Kleinschreibung oder umgebende Leerzeichen verglichen, denn jedes Verzeichnis vergleicht sie so. Passen in einer Organisation mehrere Zuordnungen, ist die Rolle die erste dieser Liste, die irgendeine von ihnen benennt: **admin → builder → operator → member → viewer** Rollen sind nicht entlang einer einzigen Befugnislinie geordnet. Ein Builder und ein Operator halten jeweils etwas, das der andere nicht hält, deshalb wird die Reihenfolge hier festgelegt statt hergeleitet. ### Was die Synchronisierung nie anfasst { #what-the-sync-never-touches } Für den automatischen Mitgliedschaftsabgleich gelten vier Regeln: - **Eine Mitgliedschaft, die ein Administrator angelegt hat, behält ihre Rolle.** Wer von Hand eingeladen wurde, behält die vergebene Rolle, selbst wenn eine Zuordnung auf ihn passt. Den zugeordneten Gruppen tritt er trotzdem bei. - **Die Rolle eines Verzeichnismitglieds von Hand zu ändern übernimmt die Mitgliedschaft.** Sie wird zu einer manuellen Mitgliedschaft, und spätere Anmeldungen lassen ihre Rolle unberührt. - **Eine von Hand hinzugefügte Gruppenmitgliedschaft bleibt bestehen**, wenn das Verzeichnis die Person aus dieser Gruppe nehmen würde. - **Ein Owner wird nie herabgestuft oder entfernt**, und keine Zuordnung kann einen erzeugen. Eigentum wandert ausschließlich über **Transfer ownership**. Einer persönlichen Organisation wird nie über eine Zuordnung beigetreten, und für eine solche kann keine Zuordnung angelegt werden. Wenn eine Verzeichniszuordnung und eine [Discord-Rollenzuordnung](channels.md#discord-roles) den Zugang gewähren, hat das Verzeichnis bei der Anmeldung Vorrang: Es übernimmt die von Discord verwaltete Mitgliedschaft mit der Verzeichnisrolle. Der Verlust der Discord-Rolle entfernt diese Person dann nicht. Entfällt die Verzeichnisfreigabe, prüft die Anmeldung zuerst, ob ein verknüpftes Discord-Konto weiterhin Zugang gewährt. Dann wechselt dieselbe Mitgliedschaft zu **Discord**; API-Schlüssel, App-Einwilligungen und unabhängige Gruppenmitgliedschaften bleiben erhalten. Gewährt keine Quelle Zugang, wird die Mitgliedschaft regulär entfernt. Kann Discord nicht antworten oder läuft das Netzwerkbudget von zehn Sekunden ab, übernimmt Discord die erhaltene Mitgliedschaft, damit der periodische Abgleich sie ohne erneute Anmeldung prüft. Die Rolle behält nur Befugnisse, die sowohl die bisherige Rolle als auch eine mögliche ungelesene Freigabe rechtfertigen. Alle Organisationen einer Anmeldung teilen dieses Netzwerkbudget. Manuelle Mitgliedschaften und Eigentümer werden nie übernommen. Das Audit protokolliert `directory.member_taken_over` und `directory.member_handed_to_discord`. Gruppenmitgliedschaften behalten ihre eigene Quelle und ihre Abgleichregeln. !!! warning "Eine Zuordnung zu löschen entfernt die Menschen, die sie eingeordnet hat" Das Entfernen geschieht bei der **nächsten Anmeldung** der jeweiligen Person, nicht sofort. Um jemanden zu behalten, nachdem Sie die Zuordnung gelöscht haben, die ihn hereingebracht hat, ändern Sie vorher seine Rolle von Hand. Das übernimmt die Mitgliedschaft. ### Zuordnungen lassen neue Konten zu { #mappings-admit-new-accounts } Eine Zuordnung zählt als Einladung. Auf einem `invite_only`-Deployment oder einem mit einer Liste erlaubter Domains legt eine erste Anmeldung, deren Gruppen auf die Zuordnung irgendeiner Organisation passen, das Konto an. Jemand, der sowohl `members:manage` als auch `roles:manage` hält, hat entschieden, dass jeder in dieser Gruppe dazugehört. Ein `closed`-Deployment weist sie trotzdem ab. Siehe [Wer sich registrieren darf](deployment.md#who-may-register). ## Anmeldung mit einem Verzeichniskonto { #signing-in-with-a-directory-account } Ist `LDAP_URL` gesetzt, bietet die Anmeldeseite ein Verzeichnisformular an. Menschen geben den Benutzernamen ein, den sie überall sonst verwenden, und ihr Verzeichnispasswort. AgenticOS prüft das Passwort mit dem üblichen zweistufigen Bind: 1. Es durchsucht `LDAP_USER_BASE_DN` mit `LDAP_USER_FILTER`, unter Verwendung des Dienstkontos (`LDAP_BIND_DN`), nach genau einem Konto, das auf den Benutzernamen passt. 2. Es bindet **als dieses Konto** mit dem eingegebenen Passwort. Nur das Verzeichnis prüft das Passwort. Es wird hier weder gespeichert noch gehasht noch protokolliert. Das Konto hängt an der eigenen stabilen Kennung des Verzeichnisses: `LDAP_ID_ATTRIBUTE`, `objectGUID` bei Active Directory und `entryUUID` bei OpenLDAP und FreeIPA. Es hängt nicht an der Adresse, sodass eine umbenannte Person ihre Historie behält. Bei der ersten Anmeldung wird das Konto nach der [Registrierungsrichtlinie](deployment.md#who-may-register) angelegt, und ein bestehendes Konto mit derselben Adresse wird damit verknüpft. ```bash # backend/.env - Active Directory over LDAPS LDAP_URL=ldaps://dc01.corp.example.com LDAP_CA_CERT_FILE=/etc/ssl/certs/corp-ca.pem LDAP_BIND_DN=CN=agenticos,OU=Service Accounts,DC=corp,DC=example,DC=com LDAP_BIND_PASSWORD=... LDAP_USER_BASE_DN=OU=Staff,DC=corp,DC=example,DC=com LDAP_ID_ATTRIBUTE=objectGUID ``` ```bash # frontend - show the form on the sign-in page OAUTH_PROVIDERS=ldap LDAP_DISPLAY_NAME=Active Directory ``` Jede Einstellung samt ihrem Standardwert steht unter [Konfiguration](configuration.md#directory-sign-in-ldap). ### Was die Anmeldung abweist { #what-the-sign-in-refuses } | Abgewiesen | Warum | |---|---| | Ein leeres Passwort | Ein LDAP-Bind mit einem Namen und ohne Passwort ist ein *nicht authentifizierter* Bind, den viele Server akzeptieren (RFC 4513). Es erreicht das Verzeichnis nie | | Zwei Konten, die auf einen Benutzernamen passen | Als dasjenige zu binden, das zuerst zurückkam, könnte eine Person als eine andere anmelden | | Ein Benutzername mit Filtersyntax | Er wird maskiert (RFC 4515), sodass `*` niemanden findet statt jeden | | Ein Konto ohne Adresse oder ohne Kennung | Das Konto, die Einladung und die Registrierungsrichtlinie hängen alle daran | | `ldap://` ohne StartTLS | Es sendet jedes Passwort im Klartext. Der Prozess verweigert den Start, sofern `LDAP_ALLOW_PLAINTEXT=true` das nicht absichtlich erlaubt | Ein falscher Benutzername und ein falsches Passwort bekommen dieselbe Antwort. Ein Verzeichnis, das nicht erreichbar ist, ein abgewiesenes Dienstkonto und eine Suche, die das Verzeichnis nicht beantworten konnte (etwa ein falscher Base DN), werden als nicht verfügbares Verzeichnis gemeldet. Sie werden nicht als falsches Passwort gemeldet, denn das würde jede Person im Unternehmen dazu bringen, ihres zurückzusetzen. Das Zertifikat wird gegen den Trust Store des Systems geprüft, oder gegen `LDAP_CA_CERT_FILE` für eine unternehmenseigene CA, und ebenso der Hostname. Die Anmeldung ist je Adresse und je Benutzername rate-limitiert, wie die Passwortanmeldung. ### Gruppen aus dem Verzeichnis { #groups-from-the-directory } Standardmäßig sind die Gruppen einer Person die DNs in ihrem Attribut `memberOf`. Ein Verzeichnis ohne `memberOf` kann stattdessen durchsucht werden: Setzen Sie `LDAP_GROUP_BASE_DN` und `LDAP_GROUP_FILTER` mit `{dn}` oder `{username}` darin. Für die verschachtelten Gruppen von Active Directory suchen Sie mit der Matching Rule, die der Kette folgt: ```bash LDAP_GROUP_BASE_DN=OU=Groups,DC=corp,DC=example,DC=com LDAP_GROUP_FILTER=(member:1.2.840.113556.1.4.1941:={dn}) ``` Um überhaupt nur eine Gruppe anmelden zu lassen, setzen Sie sie in `LDAP_USER_FILTER`: `(&(objectClass=user)(sAMAccountName={username})(memberOf=CN=AgenticOS Users,OU=Groups,DC=corp,DC=example,DC=com))`. ## Integrierte Windows-Anmeldung (Kerberos) { #integrated-windows-sign-in-kerberos } Auf einem Rechner in der Domäne beantwortet ein Browser, der auf einen Host zeigt, dem seine Richtlinie vertraut, `WWW-Authenticate: Negotiate` mit einem Kerberos-Ticket. Mit `KERBEROS_ENABLED=true` bietet die Anmeldeseite eine Schaltfläche an, die die Person mit diesem Ticket anmeldet, und niemand tippt ein Passwort. Das Ticket benennt einen Principal, `jane@CORP.EXAMPLE.COM`. AgenticOS löst ihn über das Verzeichnis oben auf, mit `LDAP_KERBEROS_FILTER` (standardmäßig `(userPrincipalName={principal})`), zu **demselben Konto**, das die LDAP-Passwortanmeldung erreicht. Kerberos braucht also ebenfalls `LDAP_URL`, und das Verzeichnis entscheidet Adresse und Gruppen für beide auf dieselbe Weise. Was es braucht: 1. **Ein Image, das mit dem Extra `kerberos` gebaut wurde.** `gssapi` wird gegen die Kerberos-Bibliotheken des Systems kompiliert und ist daher nicht im Standard-Image: `docker build --build-arg EXTRAS="--extra kerberos" -f backend/Dockerfile .` 2. **Einen Service Principal und seine Keytab.** Legen Sie in Active Directory ein Dienstkonto an, geben Sie ihm den SPN `HTTP/agenticos.corp.example.com` und exportieren Sie mit `ktpass` eine Keytab. Binden Sie sie in den Backend-Container ein. 3. **Die Backend-Einstellungen.** ```bash KERBEROS_ENABLED=true KERBEROS_KEYTAB=/etc/agenticos/http.keytab KERBEROS_SERVICE_PRINCIPAL=HTTP/agenticos.corp.example.com@CORP.EXAMPLE.COM ``` 4. **Die Schaltfläche**, mit `kerberos` in `OAUTH_PROVIDERS` des Frontends und `KERBEROS_DISPLAY_NAME=Windows sign-in`, falls sie so heißen soll. 5. **Eine Browser-Richtlinie**, die den Browser mit dem API-Host verhandeln lässt. Das heißt die lokale Intranetzone unter Windows, oder `AuthServerAllowlist` für Chrome und Edge, oder `network.negotiate-auth.trusted-uris` für Firefox. Der Service Principal muss den Host benennen, unter dem der Browser die **API** erreicht. Das ist `PUBLIC_API_URL`, denn die Verhandlung findet dort statt, nicht auf dem Host der Konsole. Ein Browser, der die Challenge nicht beantworten kann, wird direkt zur Anmeldeseite zurückgeschickt, mit einem Satz, der das sagt. Das betrifft jeden Rechner außerhalb der Domäne und jeden Browser, dessen Richtlinie den Host nicht aufführt. Nur ein in einem Schritt angenommenes Ticket wird überhaupt angenommen. NTLM wird nicht angeboten. ## Stattdessen über einen Identitätsanbieter { #through-an-identity-provider-instead } Stellt Ihr Unternehmen bereits einen Identitätsanbieter vor sein Verzeichnis, richten Sie AgenticOS stattdessen auf diesen, über [OIDC](configuration.md#single-sign-on-generic-oidc). Nutzen Sie seinen Gruppen-Claim für die Zuordnungen. Die native LDAP- und Kerberos-Anmeldung wird dann nicht gebraucht. Das ist auch die Antwort auf Multi-Faktor-Authentifizierung, die die native Anmeldung nicht leistet. **Keycloak** föderiert beides. Fügen Sie unter **User federation** einen **LDAP**-Provider für Ihr Verzeichnis hinzu, und schalten Sie dort **Allow Kerberos authentication** ein, wenn Sie die integrierte Anmeldung wollen. Sein **group-ldap-mapper** bringt die Gruppen des Verzeichnisses herüber. Geben Sie dann dem AgenticOS-Client einen **Group Membership**-Mapper, dessen Token-Claim `groups` ist, und setzen Sie: ```bash OIDC_ISSUER=https://id.corp.example.com/realms/staff OIDC_GROUPS_CLAIM=groups ``` Die Verzeichnisgruppe der Zuordnung ist dann das, was Keycloak in den Claim legt: der Gruppenpfad, etwa `/Finance`, oder der bloße Name, wenn **Full group path** ausgeschaltet ist. **Microsoft Entra ID** legt, mit aus Active Directory synchronisierten Gruppen, einen Claim `groups` ins Token, sobald die App-Registrierung einen unter **Token configuration** hat. Standardmäßig trägt er die Objekt-ID jeder Gruppe, und diese ID ist das, was Sie zuordnen. ### Der Gruppen-Claim über OIDC { #the-groups-claim-over-oidc } `OIDC_GROUPS_CLAIM` benennt den Claim, der die Gruppen einer Person auflistet. Ist er nicht gesetzt, lassen OIDC-Anmeldungen Mitgliedschaften unberührt. Ist er gesetzt, wendet jede Anmeldung über den eigenen Provider des Deployments die Zuordnungen genau so an, wie es eine Verzeichnisanmeldung tut. Die Google-Anmeldung tut das nie, denn nur dem eigenen Provider des Deployments wird zugetraut zu sagen, in welchen seiner Gruppen jemand ist. Der Claim wird aus dem ID-Token gelesen, oder aus dem UserInfo-Endpunkt, wenn das Token ihn nicht trägt. Ein konfigurierter Claim, der fehlt, zählt als **keine Gruppen**. Keycloak und Okta lassen eine leere Liste weg, statt eine zu senden, und wer aus seiner letzten Gruppe entfernt wurde, muss verlieren, was sie ihm gegeben hat. Fehlt der Claim im Token und ist UserInfo nicht erreichbar, wird die Anmeldung stattdessen abgewiesen: Das sagt nichts über die Gruppen der Person, und es als keine zu lesen, würde sie ihr entziehen. `OIDC_GROUPS_CLAIM` und `LDAP_URL` können nicht zusammen gesetzt werden. Jedes meldet Gruppen in eigenen Kennungen, und jede Anmeldung würde die Mitgliedschaften entfernen, die das andere angelegt hat, daher startet der Prozess mit beiden nicht. !!! warning "Ein Gruppen-Overage von Entra ID wird abgewiesen" Ab mehr als 200 Gruppen sendet Entra ID statt der Gruppen einen Verweis auf Microsoft Graph (`_claim_names`). Ein solches Token sagt nicht, in welchen Gruppen die Person ist, also wird die Anmeldung mit einem Satz abgewiesen, der um die Behebung bittet. Es als „keine Gruppen“ zu lesen, würde jede Mitgliedschaft entziehen, die das Verzeichnis gegeben hat, und es zu ignorieren, würde solche behalten, die es womöglich entzogen hat. Konfigurieren Sie die App-Registrierung so, dass sie **groups assigned to the application** sendet. ## Was dies noch nicht tut { #what-this-does-not-do-yet } - **Nichts prüft das Verzeichnis zwischen Anmeldungen erneut.** Ein Konto im Verzeichnis zu deaktivieren verhindert seine nächste Anmeldung, und die nächste Anmeldung ist der Zeitpunkt, zu dem Zuordnungen neu gelesen werden. Eine bereits offene Session hält, bis ihr Refresh Token abläuft, was `REFRESH_TOKEN_EXPIRE_MINUTES` ist und standardmäßig sieben Tage. Um eine früher zu beenden, deaktivieren Sie das Konto unter **Admin → Users**, was es überall sofort abweist. Es gibt kein SCIM und keine geplante Verzeichnissynchronisierung. - **Ein Verzeichniskonto kann weiterhin Passwort-Reset und Magic Link nutzen.** Sie gehen an die Adresse des Kontos, wie bei jedem anderen Konto. Soll das Verzeichnis der einzige Weg hinein sein, halten Sie diese Adressen unter der eigenen Kontrolle des Unternehmens, wie Sie es für OIDC-Konten täten. - **Kein SAML.** Ein Identitätsanbieter, der nur SAML spricht, kann AgenticOS über Keycloak erreichen, das mit ihm SAML und mit AgenticOS OIDC spricht.