--- source_sha: "c3245b98d9c8" --- # Logowanie katalogowe i grupy { #directory-sign-in-and-groups } Firma, która prowadzi Active Directory, OpenLDAP albo FreeIPA, już wie, kto w niej pracuje i do jakich zespołów należy. Ta strona opisuje trzy sposoby, na jakie AgenticOS może z tej wiedzy skorzystać. Ludzie mogą logować się kontem katalogowym. Przeglądarka na komputerze w domenie może zalogować ich biletem Kerberos. A grupy katalogowe mogą decydować, do których organizacji ludzie dołączają, z jaką rolą i w jakich grupach się znajdują. Składają się na to trzy elementy, działające niezależnie od siebie: | Element | Co robi | Kto konfiguruje | |---|---|---| | [Grupy](#groups) | Nazwane zbiory członków organizacji, którym można udostępnić zasób | Członkowie z `members:manage`, w konsoli | | [Mapowania grup katalogowych](#directory-group-mappings) | „Każdy w tej grupie katalogowej dołącza z tą rolą, do tej grupy” | Członkowie z `members:manage` i `roles:manage` | | [Logowanie katalogowe](#signing-in-with-a-directory-account) | Logowanie hasłem przez LDAP i logowanie Kerberos (SPNEGO) | Operator, w `backend/.env` | Mapowania dotyczą także ludzi, którzy logują się przez [OIDC](configuration.md#single-sign-on-generic-oidc), jeśli dostawca tożsamości podaje ich grupy. Jeśli twoja firma już ma Keycloak albo Entra ID przed swoim katalogiem, natywne logowanie LDAP może w ogóle nie być potrzebne. Zobacz [Zamiast tego przez dostawcę tożsamości](#through-an-identity-provider-instead). ## Grupy { #groups } Grupa to nazwany zbiór członków w obrębie jednej organizacji: *Finance*, *Support team*, *Platform admins*. Agenta, skill, kolekcję, plik kontekstu, sekret w vault albo aplikację udostępniasz grupie tak samo jak osobie, z panelu **Sharing** danego zasobu. Jeden grant sięga wtedy do każdego w grupie. Grant dla grupy sięga do tego, kto jest w grupie **w chwili sprawdzania dostępu**. Ktoś, kto dołączy do grupy w przyszłym miesiącu, dostaje dostęp do zasobu; ktoś, kto z niej odejdzie, go traci. Nie ma niczego do odbierania osobno każdej osobie. Grupa nie niesie żadnej roli. To, co członek może robić w organizacji, nadal wynika z roli jego członkostwa, a grupa dodaje jedynie granty, które jej nadano. Efektywny dostęp pozostaje regułą opisaną w [Uprawnieniach](permissions.md#how-the-layers-combine): ``` effective access to one row = max(role scope, the person's grant, their groups' grants) ``` Gdy osoba sięga do wiersza przez kilka grantów, wygrywa najwyższy poziom. Odczyt przez własny grant i edycja przez grant grupy daje edycję. | Czynność | Kto może | |---|---| | Listować grupy i widzieć, kto w nich jest | Każdy członek organizacji | | Utworzyć grupę, zmienić jej nazwę albo ją usunąć; dodawać i usuwać ludzi | `members:manage` | | Udostępnić zasób grupie | Każdy, kto może edytować ten zasób | Grupy znajdują się w **Organizations → Members → Groups**. Dodać można tylko członków organizacji, a usunięcie kogoś z organizacji usuwa go ze wszystkich jej grup. Usunięcie grupy usuwa każdy nadany jej grant i każde mapowanie katalogowe, które ją wskazuje, więc wymaga tych samych uprawnień co usunięcie tych mapowań: Admin nie usunie grupy, na którą wskazuje mapowanie na poziomie Admina. ## Mapowania grup katalogowych { #directory-group-mappings } Mapowanie mówi, co jedna grupa katalogowa znaczy w obrębie jednej organizacji: > Każdy w `CN=Finance,OU=Groups,DC=corp,DC=example,DC=com` jest tutaj > **członkiem** (member) i należy do grupy **Finance**. Mapowania znajdują się w **Organizations → Members → Directory**. Ich odczyt wymaga `members:manage`. Utworzenie albo usunięcie mapowania wymaga `members:manage` i `roles:manage`, bo mapowanie jednocześnie wpuszcza ludzi i nadaje im rolę. Rola jest ograniczona tak samo jak rola w zaproszeniu. Grupę katalogową możesz zmapować tylko na rolę, którą twoja własna rola ściśle przewyższa. Nikt nie może zmapować grupy na `owner`, a Admin nie może zmapować grupy na Admina. Usunięcie mapowania wymaga tej samej władzy, bo degraduje każdego, kogo ono umieściło. ### Co się dzieje przy logowaniu { #what-happens-at-sign-in } Za każdym razem, gdy ktoś loguje się przez LDAP, przez Kerberos albo przez OIDC z ustawionym [`OIDC_GROUPS_CLAIM`](#the-groups-claim-over-oidc), jego grupy katalogowe są porównywane z mapowaniami każdej organizacji: 1. **Do organizacji z pasującym mapowaniem** osoba dołącza, jeśli jeszcze nie jest członkiem, ze zmapowaną rolą. Członkostwo jest oznaczone jako **directory**. 2. **Członkostwo katalogowe** ma rolę aktualizowaną zgodnie z mapowaniami. 3. **Osoba dołącza do zmapowanych grup** i opuszcza te utworzone przez katalog członkostwa w grupach, których żadne mapowanie już nie wskazuje. 4. **Członkostwo katalogowe, do którego nie pasuje już żadne mapowanie**, jest usuwane razem z członkostwami tej osoby w grupach tej organizacji. Grupy są porównywane bez względu na wielkość liter i otaczające spacje, bo każdy katalog porównuje je w ten sposób. Gdy w jednej organizacji pasuje kilka mapowań, rolą jest pierwsza z poniższych, którą wskazuje którekolwiek z nich: **admin → builder → operator → member → viewer** Role nie układają się w jedną linię władzy. Builder i operator mają każdy coś, czego drugi nie ma, więc kolejność jest tu podana wprost, a nie wyliczana. ### Czego synchronizacja nigdy nie dotyka { #what-the-sync-never-touches } Automatyczna synchronizacja członkostwa przestrzega czterech reguł: - **Członkostwo utworzone przez administratora zachowuje swoją rolę.** Ktoś zaproszony ręcznie zachowuje nadaną mu rolę, nawet jeśli pasuje do niego mapowanie. Nadal dołącza do zmapowanych grup. - **Ręczna zmiana roli członka katalogowego przejmuje członkostwo.** Staje się ono członkostwem ręcznym i kolejne logowania nie ruszają już jego roli. - **Członkostwo w grupie dodane ręcznie zostaje**, nawet gdy katalog wyjąłby osobę z tej grupy. - **Owner nigdy nie jest degradowany ani usuwany** i żadne mapowanie nie może nikogo ownerem uczynić. Własność przechodzi wyłącznie przez **Transfer ownership**. Do organizacji osobistej nigdy nie dołącza się przez mapowanie i nie można dla niej utworzyć mapowania. Gdy mapowanie katalogu i [mapowanie roli Discorda](channels.md#discord-roles) jednocześnie dają dostęp, przy logowaniu pierwszeństwo ma katalog: przejmuje członkostwo zarządzane przez Discorda i nadaje rolę katalogu. Utrata roli Discorda nie usuwa wtedy osoby z organizacji. Gdy znika dostęp z katalogu, logowanie najpierw sprawdza, czy połączone konto Discorda nadal daje członkostwo. Jeśli tak, ten sam wiersz przechodzi pod kontrolę **Discorda**, bez cofania kluczy API, zgód aplikacji ani niezależnych członkostw w grupach. Brak obu uprawnień powoduje zwykłe usunięcie. Gdy Discord nie odpowiada lub wyczerpie się dziesięciosekundowy budżet sieciowy, zachowane członkostwo przechodzi pod kontrolę Discorda, aby zadanie okresowe ponowiło sprawdzenie bez kolejnego logowania. Rola zachowuje tylko uprawnienia uzasadnione poprzednią rolą i możliwym nieodczytanym nadaniem. Wszystkie organizacje przy jednym logowaniu dzielą ten sam budżet sieciowy. Członkostwa ręczne i właściciele nigdy nie są przejmowani. Audyt zapisuje `directory.member_taken_over` i `directory.member_handed_to_discord`. Członkostwa w grupach zachowują własne źródło i reguły synchronizacji. !!! warning "Usunięcie mapowania usuwa ludzi, których ono umieściło" Usunięcie następuje przy **następnym logowaniu** każdej osoby, a nie od razu. Żeby zatrzymać kogoś po usunięciu mapowania, które go wprowadziło, najpierw zmień jego rolę ręcznie. To przejmuje członkostwo. ### Mapowania wpuszczają nowe konta { #mappings-admit-new-accounts } Mapowanie liczy się jako zaproszenie. Na wdrożeniu `invite_only` albo takim z listą dozwolonych domen pierwsze logowanie, którego grupy pasują do mapowania dowolnej organizacji, tworzy konto. Ktoś, kto trzyma zarówno `members:manage`, jak i `roles:manage`, zdecydował, że każdy w tej grupie tu należy. Wdrożenie `closed` nadal takie logowanie odrzuca. Zobacz [Kto może się zarejestrować](deployment.md#who-may-register). ## Logowanie kontem katalogowym { #signing-in-with-a-directory-account } Ustaw `LDAP_URL`, a strona logowania pokaże formularz katalogowy. Ludzie wpisują nazwę użytkownika, której używają wszędzie indziej, i swoje hasło katalogowe. AgenticOS sprawdza hasło standardowym dwuetapowym bindem: 1. Przeszukuje `LDAP_USER_BASE_DN` filtrem `LDAP_USER_FILTER`, używając konta serwisowego (`LDAP_BIND_DN`), w poszukiwaniu dokładnie jednego konta pasującego do nazwy użytkownika. 2. Wykonuje bind **jako to konto** z wpisanym hasłem. Hasło sprawdza wyłącznie katalog. Nie jest tutaj przechowywane, hashowane ani logowane. Konto jest kluczowane po stabilnym identyfikatorze samego katalogu: `LDAP_ID_ATTRIBUTE`, czyli `objectGUID` w Active Directory i `entryUUID` w OpenLDAP i FreeIPA. Nie jest kluczowane po adresie, więc osoba, której zmieniono nazwę, zachowuje swoją historię. Przy pierwszym logowaniu konto jest tworzone zgodnie z [polityką rejestracji](deployment.md#who-may-register), a istniejące konto z tym samym adresem jest z nim łączone. ```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 ``` Każde ustawienie i jego wartość domyślna są w [Konfiguracji](configuration.md#directory-sign-in-ldap). ### Czego logowanie odmawia { #what-the-sign-in-refuses } | Odrzucane | Dlaczego | |---|---| | Puste hasło | Bind LDAP z nazwą i bez hasła to bind *nieuwierzytelniony*, który wiele serwerów akceptuje (RFC 4513). Nigdy nie dociera do katalogu | | Dwa konta pasujące do jednej nazwy użytkownika | Bind jako to, które wróciło pierwsze, mógłby zalogować jedną osobę jako inną | | Nazwa użytkownika zawierająca składnię filtra | Jest escapowana (RFC 4515), więc `*` nie znajduje nikogo zamiast wszystkich | | Konto bez adresu albo bez identyfikatora | Konto, zaproszenie i polityka rejestracji są kluczowane właśnie po nich | | `ldap://` bez StartTLS | Wysyła każde hasło otwartym tekstem. Proces odmawia startu, chyba że `LDAP_ALLOW_PLAINTEXT=true` mówi, że to celowe | Błędna nazwa użytkownika i błędne hasło dostają tę samą odpowiedź. Katalog, który nie działa, odrzucone konto serwisowe i wyszukiwanie, na które katalog nie umiał odpowiedzieć (na przykład błędny base DN), są zgłaszane jako niedostępność katalogu. Nie są zgłaszane jako błędne hasło, co wysłałoby każdą osobę w firmie do resetowania swojego. Certyfikat jest weryfikowany względem systemowego magazynu zaufanych certyfikatów albo względem `LDAP_CA_CERT_FILE` dla firmowego CA — i tak samo nazwa hosta. Logowanie ma limit żądań na adres i na nazwę użytkownika, tak jak logowanie hasłem. ### Grupy z katalogu { #groups-from-the-directory } Domyślnie grupami osoby są DN-y z jej atrybutu `memberOf`. Katalog bez `memberOf` można zamiast tego przeszukać: ustaw `LDAP_GROUP_BASE_DN` oraz `LDAP_GROUP_FILTER` z `{dn}` albo `{username}` w środku. Dla zagnieżdżonych grup Active Directory szukaj z regułą dopasowania, która podąża za łańcuchem: ```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}) ``` Żeby w ogóle logować się mogła tylko jedna grupa, umieść ją w `LDAP_USER_FILTER`: `(&(objectClass=user)(sAMAccountName={username})(memberOf=CN=AgenticOS Users,OU=Groups,DC=corp,DC=example,DC=com))`. ## Zintegrowane logowanie Windows (Kerberos) { #integrated-windows-sign-in-kerberos } Na komputerze w domenie przeglądarka skierowana na host, któremu ufa jej polityka, odpowiada na `WWW-Authenticate: Negotiate` biletem Kerberos. Przy `KERBEROS_ENABLED=true` strona logowania pokazuje przycisk, który loguje osobę tym biletem, i nikt nie wpisuje hasła. Bilet wskazuje principal, `jane@CORP.EXAMPLE.COM`. AgenticOS rozwiązuje go przez opisany wyżej katalog, filtrem `LDAP_KERBEROS_FILTER` (domyślnie `(userPrincipalName={principal})`), do **tego samego konta**, do którego prowadzi logowanie hasłem LDAP. Kerberos wymaga więc również `LDAP_URL`, a katalog decyduje o adresie i grupach w ten sam sposób dla obu. Czego to wymaga: 1. **Obrazu zbudowanego z extra `kerberos`.** `gssapi` kompiluje się względem systemowych bibliotek Kerberos, więc nie ma go w domyślnym obrazie: `docker build --build-arg EXTRAS="--extra kerberos" -f backend/Dockerfile .` 2. **Service principala i jego keytabu.** W Active Directory utwórz konto serwisowe, nadaj mu SPN `HTTP/agenticos.corp.example.com` i wyeksportuj keytab przez `ktpass`. Zamontuj go w kontenerze backendu. 3. **Ustawień backendu.** ```bash KERBEROS_ENABLED=true KERBEROS_KEYTAB=/etc/agenticos/http.keytab KERBEROS_SERVICE_PRINCIPAL=HTTP/agenticos.corp.example.com@CORP.EXAMPLE.COM ``` 4. **Przycisku** — z `kerberos` w `OAUTH_PROVIDERS` frontendu i `KERBEROS_DISPLAY_NAME=Windows sign-in`, jeśli ma się tak nazywać. 5. **Polityki przeglądarki**, która pozwala jej negocjować z hostem API. To strefa lokalnego intranetu w Windows albo `AuthServerAllowlist` dla Chrome i Edge, albo `network.negotiate-auth.trusted-uris` dla Firefoksa. Service principal musi wskazywać host, pod którym przeglądarka dociera do **API**. To `PUBLIC_API_URL`, bo negocjacja odbywa się tam, a nie na hoście konsoli. Przeglądarka, która nie umie odpowiedzieć na wyzwanie, jest odsyłana prosto z powrotem na stronę logowania ze zdaniem, które to mówi. Dotyczy to każdego komputera spoza domeny i każdej przeglądarki, której polityka nie wymienia hosta. Akceptowany jest wyłącznie bilet przyjęty w jednym kroku. NTLM nie jest oferowany. ## Zamiast tego przez dostawcę tożsamości { #through-an-identity-provider-instead } Jeśli twoja firma już stawia dostawcę tożsamości przed swoim katalogiem, skieruj AgenticOS na niego, przez [OIDC](configuration.md#single-sign-on-generic-oidc). Do mapowań użyj jego claimu grup. Natywne logowanie LDAP i Kerberos nie są wtedy potrzebne. To także odpowiedź w sprawie uwierzytelniania wieloskładnikowego, którego natywne logowanie nie zapewnia. **Keycloak** federuje oba. W **User federation** dodaj providera **LDAP** dla swojego katalogu i włącz tam **Allow Kerberos authentication**, jeśli chcesz zintegrowanego logowania. Jego **group-ldap-mapper** przenosi grupy z katalogu. Potem daj klientowi AgenticOS mapper **Group Membership**, którego claim w tokenie to `groups`, i ustaw: ```bash OIDC_ISSUER=https://id.corp.example.com/realms/staff OIDC_GROUPS_CLAIM=groups ``` Grupą katalogową mapowania jest wtedy to, co Keycloak umieszcza w claimie: ścieżka grupy, na przykład `/Finance`, albo sama nazwa, jeśli **Full group path** jest wyłączone. **Microsoft Entra ID** z grupami synchronizowanymi z Active Directory umieszcza w tokenie claim `groups`, gdy tylko rejestracja aplikacji ma go w **Token configuration**. Domyślnie niesie on object id każdej grupy i to ten identyfikator mapujesz. ### Claim grup przez OIDC { #the-groups-claim-over-oidc } `OIDC_GROUPS_CLAIM` nazywa claim, który listuje grupy osoby. Gdy jest nieustawiony, logowania OIDC zostawiają członkostwa w spokoju. Gdy jest ustawiony, każde logowanie przez własnego dostawcę wdrożenia stosuje mapowania dokładnie tak, jak robi to logowanie katalogowe. Logowanie Google nigdy tego nie robi, bo tylko własnemu dostawcy wdrożenia ufa się w kwestii tego, w których jego grupach ktoś jest. Claim jest czytany z ID tokena albo z endpointu UserInfo, gdy token go nie niesie. Skonfigurowany claim, którego brakuje, liczy się jako **brak grup**. Keycloak i Okta pomijają pustą listę zamiast ją wysłać, a ktoś usunięty ze swojej ostatniej grupy musi stracić to, co ona mu dawała. Jeśli tokenowi brakuje claimu, a UserInfo jest nieosiągalne, logowanie zostaje odrzucone: taki stan nic nie mówi o grupach osoby, a odczytanie go jako braku grup odebrałoby jej członkostwa. `OIDC_GROUPS_CLAIM` i `LDAP_URL` nie mogą być ustawione jednocześnie. Każde z nich zgłasza grupy we własnych identyfikatorach, a każde logowanie odbierałoby członkostwa nadane przez drugie, więc proces z oboma odmawia startu. !!! warning "Przekroczenie limitu grup w Entra ID jest odrzucane" Powyżej 200 grup Entra ID zamiast grup wysyła wskaźnik do Microsoft Graph (`_claim_names`). Taki token nie mówi, w których grupach jest osoba, więc logowanie jest odrzucane ze zdaniem proszącym o poprawkę. Odczytanie go jako „brak grup” odebrałoby każde członkostwo, które dał jej katalog, a zignorowanie go zachowałoby te, które katalog mógł już odebrać. Skonfiguruj rejestrację aplikacji tak, żeby wysyłała **groups assigned to the application**. ## Czego to jeszcze nie robi { #what-this-does-not-do-yet } - **Nic nie sprawdza katalogu ponownie między logowaniami.** Wyłączenie konta w katalogu zatrzymuje jego następne logowanie, a następne logowanie to moment, w którym mapowania są czytane od nowa. Sesja już otwarta trwa, dopóki nie wygaśnie jej refresh token, czyli `REFRESH_TOKEN_EXPIRE_MINUTES`, domyślnie siedem dni. Żeby ją zakończyć wcześniej, dezaktywuj konto w **Admin → Users**, co odrzuca je wszędzie naraz. Nie ma SCIM ani zaplanowanej synchronizacji katalogu. - **Konto katalogowe nadal może używać resetu hasła i magic linka.** Trafiają one na adres konta, jak w przypadku każdego innego konta. Jeśli katalog ma być jedyną drogą wejścia, trzymaj te adresy pod kontrolą samej firmy, tak jak zrobiłbyś to dla kont OIDC. - **Brak SAML.** Dostawca tożsamości obsługujący wyłącznie SAML może dotrzeć do AgenticOS przez Keycloak, który mówi do niego po SAML, a do AgenticOS po OIDC.