Zaktualizowano 6 min czytania

Logowanie do narzędzi administracyjnych przez Active Directory: LDAPS czy OIDC

System CI, platforma GitOps, monitoring i baza wiedzy potrafią logować użytkowników kontem domenowym. Bezpośrednie LDAPS i OIDC przez pośrednika, takiego jak Dex lub Keycloak, dają ten sam efekt dla użytkownika i zupełnie inny dla bezpieczeństwa.

Rys. 1. Przy bezpośrednim LDAP hasło trafia do każdej aplikacji; przy federacji tylko do dostawcy tożsamości, który wymusza też drugi składnik.

Administrator odchodzi z zespołu. Jego konto w Active Directory zostaje wyłączone tego samego dnia i sprawa wydaje się zamknięta. Miesiąc później przegląd pokazuje, że konto lokalne w systemie CI, w monitoringu i w panelu platformy GitOps nadal działa, bo każde z tych narzędzi miało własną listę użytkowników.

Podpięcie narzędzi pod katalog rozwiązuje ten problem, ale sposób podpięcia ma większe znaczenie, niż sugeruje formularz konfiguracji, w którym oba warianty zajmują po jednej zakładce. Różnica dotyczy tego, kto widzi hasło, gdzie można wymusić drugi składnik i ile miejsc trzeba sprawdzić przy odbieraniu dostępu.

Wariant pierwszy: bezpośrednie LDAPS

Narzędzie przyjmuje login i hasło użytkownika, łączy się z kontrolerem domeny i sprawdza je operacją bind. Członkostwo w grupach odczytuje zapytaniem LDAP i na tej podstawie nadaje role. Zalety to prosta konfiguracja, brak dodatkowych komponentów i obsługa w niemal każdym narzędziu.

Wady wynikają z tego, że hasło użytkownika trafia do narzędzia:

  • każde narzędzie widzi hasła wszystkich logujących się osób, więc kompromitacja jednego z nich oznacza wyciek haseł domenowych,
  • uwierzytelniania wieloskładnikowego nie da się wymusić, bo bind LDAP przyjmuje tylko hasło,
  • każde narzędzie potrzebuje konta do zapytań o grupy, a wygaśnięcie jego hasła zatrzymuje logowanie wszystkim,
  • zasady sesji, blokady i wylogowania są w każdym narzędziu inne.

Wariant drugi: OIDC przez pośrednika

Narzędzie przekierowuje użytkownika do pośrednika tożsamości przez OpenID Connect. Pośrednik, połączony z Active Directory, sprawdza hasło i zwraca do narzędzia podpisany token z nazwą użytkownika i listą grup. Narzędzie nigdy nie widzi hasła.

Najczęściej spotykane są dwa rozwiązania open source. Dex jest lekkim pośrednikiem bez własnej bazy użytkowników: tłumaczy LDAP na OIDC i nie przechowuje kont. Keycloak jest pełnym dostawcą tożsamości z federacją użytkowników z LDAP, własnymi zasadami sesji i drugim składnikiem (kody jednorazowe, klucze WebAuthn). Trzecią drogą jest chmurowy dostawca tożsamości, do którego konta są synchronizowane z katalogu.

CechaBezpośrednie LDAPSOIDC przez DexOIDC przez Keycloak lub Entra ID
Kto widzi hasłokażde narzędzietylko Dextylko dostawca tożsamości
Drugi składniknienie, konektor LDAP sprawdza tylko hasłotak, wymuszany w jednym miejscu
Konta do wiązania z katalogiempo jednym na narzędziejednojedno lub żadne przy synchronizacji
Dodatkowe elementybrakjedna usługa bez bazy danychserwer z bazą danych albo usługa chmurowa
Kiedy wybraćjedno lub dwa narzędziakilka narzędzi, platforma Kuberneteswymagane MFA, wiele narzędzi

Dla jednego czy dwóch narzędzi w małym zespole bezpośrednie LDAPS jest akceptowalnym startem, pod warunkiem weryfikacji certyfikatu i osobnego konta do wiązania. Przy kilku narzędziach pośrednik zaczyna się opłacać, bo hasła trafiają do jednego miejsca, a nie do każdego narzędzia. Gdy wymagane jest MFA, potrzebny jest dostawca, który je obsługuje. Przejście z LDAPS na OIDC nie wymaga zmian w katalogu: grupy i konwencja nazw zostają te same, zmienia się tylko miejsce, w którym sprawdzane jest hasło.

Konektor LDAP w Dex

Poniższa konfiguracja łączy Dex z katalogiem przez LDAPS, wpuszcza tylko włączone konta użytkowników i przekazuje do narzędzi wyłącznie grupy z prefiksem GG-, żeby token nie zawierał setek nieistotnych grup.

connectors:
  - type: ldap
    id: ad
    name: Active Directory
    config:
      # Nazwa domeny zamiast adresu jednego kontrolera
      host: corp.example.com:636
      # Certyfikat wewnętrznego urzędu certyfikacji
      rootCA: /etc/dex/tls/corp-root-ca.pem
      # Konto do wiązania, hasło ze zmiennej środowiskowej
      bindDN: CN=svc-dex-bind,OU=Service Accounts,DC=corp,DC=example,DC=com
      bindPW: $LDAP_BIND_PASSWORD
      usernamePrompt: Login domenowy
      userSearch:
        baseDN: OU=Users,DC=corp,DC=example,DC=com
        # Tylko włączone konta użytkowników
        filter: "(&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))"
        username: sAMAccountName
        idAttr: DN
        emailAttr: userPrincipalName
        nameAttr: displayName
        preferredUsernameAttr: sAMAccountName
      groupSearch:
        baseDN: OU=Groups,DC=corp,DC=example,DC=com
        filter: "(&(objectClass=group)(cn=GG-*))"
        userMatchers:
          - userAttr: DN
            groupAttr: member
        nameAttr: cn

Niektóre platformy, na przykład Argo CD, mają Dex wbudowany. Konfiguracja konektora jest wtedy taka sama, tylko trafia do ich pliku ustawień, a hasło konta do wiązania do ich sekretu.

Uprawnienia z grup

Niezależnie od wariantu uprawnienia wynikają z członkostwa w grupach katalogu, a nie z ręcznych przypisań w narzędziu. Sprawdza się prosta konwencja: osobna grupa na każdą rolę w każdym narzędziu, z nazwą mówiącą, co daje, na przykład GG-GitOps-Admins, GG-GitOps-ReadOnly, GG-Grafana-Editors. Członkostwo w grupie jest jedynym sposobem uzyskania roli, a przegląd uprawnień sprowadza się do przeglądu grup.

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
  namespace: argocd
data:
  # Bez policy.default: kto nie należy do żadnej grupy, nie ma dostępu
  scopes: '[groups]'
  policy.csv: |
    g, GG-GitOps-Admins, role:admin
    g, GG-GitOps-ReadOnly, role:readonly

Konto do wiązania

Pośrednik potrzebuje konta, którym wyszukuje użytkowników i grupy. Wystarcza zwykłe konto domenowe, bo uwierzytelnieni użytkownicy domyślnie mogą czytać atrybuty potrzebne do logowania. Konto nie należy do żadnej grupy poza Domain Users, ma odmowę logowania lokalnego i przez pulpit zdalny, a w opisie właściciela i datę następnej zmiany hasła.

Hasło ma co najmniej 30 losowych znaków i jest zmieniane według planu, na przykład raz w roku, razem z sekretem w pośredniku. Konto z hasłem, które wygasa samo w nieprzewidzianym momencie, zatrzymuje logowanie do wszystkich narzędzi naraz.

Pułapki wdrożeniowe

  • Certyfikat kontrolera. LDAPS działa tylko wtedy, gdy kontroler domeny ma certyfikat do uwierzytelniania serwera. Nazwa użyta w konfiguracji musi znaleźć się w polu SAN certyfikatu, inaczej weryfikacja się nie powiedzie.
  • Adres kontrolera. Konfiguracja wskazuje nazwę domeny albo kilka kontrolerów, nigdy adres IP jednego z nich. Awaria tego kontrolera nie może zatrzymać logowania.
  • Opóźnienie odebrania dostępu. Token wydany przez pośrednika jest ważny do wygaśnięcia. W Dex czas życia tokenu ustawia expiry.idTokens (domyślnie 24 godziny). Dla narzędzi z dostępem do produkcji rozsądna wartość to godzina.
  • Konto lokalne. Lokalny administrator narzędzia zostaje, wyłączony z codziennej pracy i zabezpieczony jako dostęp awaryjny na wypadek niedostępności katalogu lub pośrednika.

Sprawdź u siebie

Dostępność portu 636 na każdym kontrolerze domeny. Dobry wynik to True przy każdym kontrolerze; False oznacza brak certyfikatu na kontrolerze albo zablokowany port.

foreach ($dc in Get-ADDomainController -Filter *) {
    [pscustomobject]@{
        DC    = $dc.HostName
        LDAPS = Test-NetConnection -ComputerName $dc.HostName -Port 636 -InformationLevel Quiet
    }
}

Certyfikat widziany przez narzędzie. W polu subjectAltName musi być nazwa z konfiguracji, wystawcą powinien być wewnętrzny urząd certyfikacji, a data notAfter odległa o co najmniej miesiąc. Drugie polecenie sprawdza weryfikację z certyfikatem urzędu; dobry wynik to Verify return code: 0 (ok).

openssl s_client -connect corp.example.com:636 -servername corp.example.com </dev/null 2>/dev/null |
  openssl x509 -noout -subject -issuer -enddate -ext subjectAltName

openssl s_client -connect corp.example.com:636 -CAfile corp-root-ca.pem </dev/null 2>/dev/null |
  grep 'Verify return code'

Zapytanie kontem do wiązania przez LDAPS, z uwzględnieniem grup zagnieżdżonych. Wynik powinien zawierać wszystkich członków grupy. Błąd Can't contact LDAP server przy otwartym porcie zwykle oznacza brak zaufania do certyfikatu.

LDAPTLS_CACERT=corp-root-ca.pem ldapsearch -x -H ldaps://corp.example.com:636 \
  -D 'svc-dex-bind@corp.example.com' -W \
  -b 'OU=Users,DC=corp,DC=example,DC=com' \
  '(memberOf:1.2.840.113556.1.4.1941:=CN=GG-GitOps-Admins,OU=Groups,DC=corp,DC=example,DC=com)' \
  sAMAccountName

Stan konta do wiązania. Dobry wynik to puste MemberOf (Domain Users jest grupą podstawową i się tu nie pokazuje) oraz PasswordLastSet w okresie zaplanowanej rotacji.

Get-ADUser -Identity svc-dex-bind -Properties PasswordLastSet, PasswordNeverExpires, MemberOf |
    Select-Object SamAccountName, Enabled, PasswordLastSet, PasswordNeverExpires, MemberOf