Linux-PAM

W poprzednim artykule mogliśmy się zapoznać z zarządzaniem użytkownikami, którego zdecydowanymi zaletami są prostota i przejrzystość. Przez wiele lat uwierzytelnianie i zarządzanie hasłami było bezpośrednio oparte na umówionych plikach passwd, shadow i group, z czasem jednak ujawniły się ograniczenia tego rozwiązania i firma SunSoft opracowała nowy, kompatybilny z dotychczasowym, system uwierzytelnienia pod nazwą Linux-PAM, czyli Pluggable Authentication Modules for Linux. Żeby być precyzyjnym, firma SunSoft opracowała PAM dla systemu Solaris, późniejsza adaptacja dla systemu Linux to Linux-PAM. Oczywiście nadal możemy zarządzać użytkownikami w dotychczasowy, prosty sposób, jednak warto poznać, jak Linux-PAM działa i jakie daje nam możliwości.

Ograczenia tradycyjnego modelu uwierzytelniania

Dlaczego w ogóle pojawiło się coś takiego jak Linux-PAM? Model tradycyjny zakładał, że każda aplikacja, która potrzebuje uwierzytelnić użytkownika, robi to sama. Z czasem, gdy tych aplikacji (głównie usług, jak np. ftp czy ssh) pojawiło się więcej oraz pojawiło się więcej sposobów uwierzytelnienia (np. login i hasło, certyfikaty, karty inteligentne), pojawiły się problemy.

  • Pierwsze ograniczenie wynikało z faktu, że dodatkowym usługom (jak wspomniane ftp czy ssh) przestawała wystarczać funkcjonalność oferowana przez ten prosty system i żeby spełnić założenia, koniecznym stało się dodawanie np. dodatkowych plików z konfiguracją. A to z kolei prowadziło do niespójności w uwierzytelnianiu użytkownika (np. na usłudze ftp był zablokowany, ale nadal miał dostęp przez ssh).
  • Drugi problem polegał na trudnościach implementacyjnych: duża liczba sposobów uwierzytelnienia (jak z kolei wspomniane oprócz logina i hasła, certyfikaty czy karty inteligentne) wymagała sporej ilości kodu, a to pociągało za sobą błędy. Co więcej, dodanie nowego sposobu uwierzytelniania (np. z odcisku palca) wiązało się z aktualizacją wszystkich programów korzystających z uwierzytelnienia.

Naturalne było utworzenie architektury, która poprzez oddzielenie procesu uwierzytelnienia od programów, które chcą go przeprowadzić, rozwiązałaby powyższe dwa problemy. Linux-PAM jest właśnie takim rozwiązaniem.

Architektura Linux-PAM

Głównym celem Linux-PAM jest dostarczenie bibliotek pozwalających na uwierzytelnienie użytkownika programom (usługom), które tego potrzebują. Wynikają z tego co najmniej dwie korzyści. Jedna to taka, że programy nie muszą implementować uwierzytelnienia samodzielnie, a druga, wynikająca z pierwszej, że jeśli rozszerzymy Linux-PAM o nowy sposób uwierzytelnienia, programów nie trzeba zmieniać (w szczególności nie trzeba ich rekompilować).

Najprościej schemat PAM można przedstawić następująco:

Architektura PAM

Linux-PAM zajmuje się 4 rodzajami zadań.

  • Zarządzanie kontami. Sterowanie dostepem na podstawie czynników innych niż dane uwierzytelnienia, np. obsługa haseł, które utraciły ważność, obsługa zablokowanego konta, określonych godzin, lokalizacji użytkownika, np. określenie, że root może się zalogować tylko na konsoli.
  • Zarządzanie uwierzytelnieniem. Tutaj realizowane są dwie rzeczy:
    • potwierdzenie na podstawie loginu i hasła (lub innych danych, np. odcisku palca), że użytkownik jest tym za którego się podaje,
    • ewentualne przydzielenie do grupy lub nadanie innych uprawnień.
  • Zarządzanie hasłami. Aktualizacja danych uwierzytelniających użytkownika, czyli np. hasła.
  • Zarządzanie sesją. Określenie czynności, które należy wykonać przed tym, jak użytkownik dostaje dostęp do danej usługi. Przykładowo może to być logowanie faktu wymiany daych użytkownika z usługą lub określenie konieczności zamontowania pewnych katalogów.

Do realizacji tych zadań są odpowiednie moduły, które również dzielimy na 4 odpowiadające grupy (listę wybranych modułów obejrzymy później).

Konfiguracja Linux-PAM

Konfiguracja znajduje się w pliku /etc/pam.d/nazwa_programu (np. dla passwd jest to plik /etc/pam.d/passwd). Dodatkowo mamy plik /etc/pam.d/other zawierający domyślną konfiguracją dla programów, które takowej nie posiadają. Niezależnie mamy także konfigurację konkretnych modułów i odpowiednie pliki znajdują się w katalogu /etc/security/. Warto w tym miejscu zauważyć, że każdy plik w katalogu /etc/pam.d/ zawiera pewną liczbę wierszy, z których każdy odpowiada pewnej "akcji" uwierzytelnienia realizowanej przez wybrany moduł i określonej przez konfigurację zawartą w tym wierszu. Proces uwierzytelnienia można streścić następującą ilustracją:

Architektura PAM

Aplikacja X jest nieświadoma mechanizmów uwierzytelnienia, ale przygotowana do korzystania z Linux-PAM. Proces uwierzytelnienia przebiega następująco:

  • aplikacja X łączy się Linux-PAM,
  • Linux-PAM wczytuje plik konfiguracyjny odpowiedni dla tej aplikacji,
  • Linux-PAM wykonuje po kolei moduły ("akcje") zdefiniowane w kolejnych wierszach pliku konfiguracyjnego
    Uwaga: może się zdarzyć, że nie wszystkie zostaną wykonane (np. wybrany moduł może przerwać proces uwierzytlenienia),
  • aplikacja X dostaje zwrotną informacją o pomyślności przebiegu procesu uwierzytelnienia.

Konfiguracja pojedynczego wiersza z pliku z katalogu /etc/pam.d ma postać:

module-type    control-flag    module-path    arguments

Znaczenie kolejnych pól jest następujące.

  • module-type – jedna z wartości auth, accoung, password, session i odpowiada jednej z grup zadań określoncyh powyżej.
  • control-flag – określa zachowanie PAM na wynik działania modułu i pola ma składnię:
    [wartosc1=dzialanie1 wartosc2=dzialanie2 ...]
    gdzie przykładowe wartości to user unknown, acct expired czy try again, a działania to ignore, ok, done, bad, die, reset. W praktyce jednak stosuje się uproszczony sposób określania tego parametru i polega na podaniu jednej z następujących wartości:
    • required – moduł z tą flagą musi być wykonany pomyślnie, aby cały proces uwierzytelniania zakończył się pozytywnie. Niezależnie od powodzenia wykonania modułu, pozostałe moduły zawsze są wykonywane do końca – zapobiega to wykryciu miejsca potencjalnego niepowodzenia procesu uwierzytelnienia.
    • requisite – moduł z tą flagą musi być wykonany pomyślnie, aby cały proces uwierzytelniania zakończył się pozytywnie. W przypadku pozytywnego wykonania modułu, pozostałe moduły wykonywane są do końca, jednak w przypadku niepowodzenia, użytkownik od razu dostaje informację zwrotną, że proces uwierzytelnienia się nie powiódł.
    • optional – powodzenie wykonania modułu nie ma znaczenia dla pomyślności całego procesu uwierzytelnienia. Zwykle stosowany tylko do wyświetlania informacji np. że przyszła nowa poczta.
    • sufficient – pozytywne zakończenie modułu powoduje przekazanie informacji zwrotnej o pomyślnym przeprowadzeniu procesu uwierzytelnienia (pod warunkiem, że wcześniej nie było niepowodzenia przy module z flagą required). Negatywne zakończenie modułu nie ma wpływu na dalszy przebieg procesu uwierzytelnienia – pozostałe moduły są dalej wykonywane jakby nic się nie stało.
    • include – nie jest to flaga kontrolna, ale określa, że należy zaimportować i wykonać plik o nazwie określonej przez następny parametr, czyli module-path (zob. poniżej przykład).
  • module-path – moduł do wykonania, który domyślnie jest katalogu /lib/security. Każda nazwa modułu zaczyna się od pam_. Nie trzeba podawać ścieżki, chyba, że moduł znajduje się w innej lokalizacji. Warto w tym miejscu zaznaczyć, że niektóre moduły mogą realizować zadania z różnych grup, np. moduł pam_unix2.so może realizować zadanie typu auth oraz zadanie typu passwd.
  • arguments – dodatkowe parametry przekazywane jako parametry wiersza poleceń, zwykle zależne od wykonywanego modułu, jednak każdy moduł powinien akceptować zestaw następujących parametrów: debug, no warn, use first pass, try first pass, expose account.
Przykładowy plik konfiguracyjny /etc/pam.d/login oraz poniżej pliki "zaincludowane" (z plików zostaną usunięte komentarze w celu uzyskania większej przejrzystości):
#%PAM-1.0
auth     required       pam_securetty.so
auth     include        common-auth
auth     required       pam_nologin.so
account  include        common-account
password include        common-password
session  include        common-session
session  required       pam_lastlog.so nowtmp
session  required       pam_resmgr.so
session  optional       pam_mail.so standard
Plik /etc/pam.d/common-auth
auth    required        pam_env.so
auth    required        pam_unix2.so
Plik /etc/pam.d/common-account
account required        pam_unix2.so
Plik /etc/pam.d/common-password
password required       pam_pwcheck.so  nullok
password required       pam_unix2.so    nullok use_first_pass use_authtok
Plik /etc/pam.d/common-session
session required        pam_limits.so
session required        pam_unix2.so

Przykłady

Poznaliśmy trochę teorii jak to działa. Teraz zobaczymy jak można wiedzę o Linux-PAM wykorzystać w praktyce.

Przykład 1. Blokowanie dostępu do systemu przez utworzenie pliku /etc/nologin. Przed rozpoczęciem tworzymy użytkownika gucio.

  • Przełączamy się na drugą konsolę wirtualną (Ctrl+Alt+F2). Jeśli z uwagi na środowisko wirtualne się to nie powiedzie, logujemy się jako root i wykonujemy polecenie
    # init 3
    
    a następnie po kilku chwilach wprowadzamy kombinację (Alt+F2)
  • Logujemy się jako root.
  • Wykonujemy polecenie:
    # echo "Zalogowanie zablokowane" > /etc/nologin
    
  • Przełączamy się na trzecią konsolę (Alt+F3)
  • Próbujemy się zalogować jako gucio (zalogowanie się nie udaje, ponieważ moduł pam_nologin i otrzymujemy komunikat "Zalogowanie zablokowane").
  • Wracamy na drugą konsolę (Alt+F2).
  • Wydajemy polecenie
    # tail /var/log/messages
    
    i patrzymy, że pojawił się wpis FAILED LOGIN
  • Modyfikujemy plik /etc/pam.d/login:
    • Wprowadzamy polecenie (zakładamy, że dostępny jest edytor mcedit):
      # mcedit /etc/pam.d/login
      
    • Znajdujemy wiersz, który po dodaniu na jego początku znaku # powinien wyglądać następująco
      #auth     required       pam_nologin.so
      
  • Przełączamy się na czwartą konsolę (Alt+F4) i próbujemy się zalogować jako gucio (udało się, ponieważ PAM już nie sprawdza pliku /etc/nologin)
  • Wracamy na drugą konsolę (Alt+F2).
  • Przywracamy z powrotem plik /etc/pam.d/login do oryginalnej postaci, czyli ww. wiersz powinien teraz wyglądać tak:
    auth     required       pam_nologin.so
    
  • Przełączamy się na piątą konsolę (Alt+F5) i próbujemy się zalogować jako gucio (nie udało się, ponieważ ponownie sprawdzane jest istnienie pliku /etc/nologin)
  • Wracamy na drugą konsolę (Alt+F2).
  • Na końcu usuwamy plik /etc/nologin
    # rm /etc/nologin
    
    i teraz z powrotem wszyscy mogą się już normalnie logować.

Przykład 2. Ustawianie zasad dla nowych haseł.

  • Przełączamy się na drugą konsolę wirtualną (Ctrl+Alt+F2). Jeśli z uwagi na środowisko wirtualne się to nie powiedzie, logujemy się jako root i wykonujemy polecenie
    # init 3
    
    a następnie po kilku chwilach wprowadzamy kombinację (Alt+F2)
  • Logujemy się jako root.
  • Zasady dotyczące nowych haseł znajdują się pliku konfiguracyjnym modułu pam_pwcheck, czyli w plku /etc/security/pam_pwcheck.conf. Otwieramy ten plik do edycji:
    # mcedit /etc/security/pam_pwcheck.conf
    
    Domyślnie zawartość tego pliku wygląda następująco:
    password: remember=13 cracklib nullok
    
    Łatwo się domyślić znaczenia poszczególnych opcji (remember pamięta historię haseł, cracklib wymusza skomplikowane hasła, a nullok dopuszcza hasła puste). W dalszej politykę trywialnych haseł z krótką historią. Dodatkowo wykorzystamy opcje: minlen=X, no_obscure_checks, tries=X. Wyjaśnienia wymaga tylko no_obscure_checks: ta opcja wyłącza proste testy dla haseł, czyli np. możemy wtedy zmienić hasło na 123456 (oczywiście w produkcyjnych rozwiązaniach nie należy używać tej opcji). Warto zauważyć, że jest również dualna opcja maxlen, ale w przypadku użycia md5 lub blowfish, jest ona ignorowana.
  • Edytorem mcedit ustawiamy zawartość pliku /etc/security/pam_pwcheck.conf na następującą:
    password: no_obscure_checks remember=5 minlen=3 tries=20
    
  • Przełączamy się na trzecię konsolę (Alt+F3), logujemy jako gucio.
  • Próbujemy zmienić hasło dla gucio poleceniem
    $ passwd
    
    Podajemy biężace hasło, a następnie wprowadzamy kolejno:
    12
    123
    
    Dalej zmieniamy hasło na
    1234
    
  • Przełączamy się na drugą konsolę (Alt+F2) i wyświetlamy zawartość pliku /etc/security/opasswd
    # cat /etc/security/opasswd
    
    Zauważamy, że mamy co najmniej 2 szyfrogramy rozdzielone przecinkami – jest to historia haseł użytkownika gucio.
  • Przełączamy się na trzecią konsolę (Alt+F3) i zmieniamy kolejno hasła na następujące:
    12345
    123456
    1234567
    12345678
    
    Na końcu spróbujmy zmienić wprowadzając jako nowe
    123456
    123
    
    Zmiana na 123 się nie uda, ponieważ mamy historię 5 haseł. Uda się zmiana na 123.
  • Przełączamy się na drugą konsolę (Alt+F2) i jeszcze raz oglądamy plik opasswd:
    # cat /etc/security/opasswd
    
    Teraz powinno być rozdzielonych przecinkami 5 haseł. Za pomocą mcedit przywracamy też oryginalną zawartość pliku /etc/security/pam_pwcheck.conf, która powinna być następująca:
    password: remember=13 cracklib nullok
    
  • Wszystkie opcje pam_pwcheck są opisane w manualu:
    # man pam_pwcheck
    

Przykład 3. Blokowanie dostępu do SSH.

  • Przełączamy się na drugą konsolę wirtualną (Ctrl+Alt+F2). Jeśli z uwagi na środowisko wirtualne się to nie powiedzie, logujemy się jako root i wykonujemy polecenie
    # init 3
    
    a następnie po kilku chwilach wprowadzamy kombinację (Alt+F2)
  • Logujemy się jako root.
  • Do pliku /etc/pam.d/sshd dopisujemy wiersz:
    account required pam_listfile.so item=user sense=deny file=/etc/ssh/sshd.deny onerr=succeed
    
    a następnie tworzymy plik /etc/ssh/sshd.deny i w kolejnych wiersza wprowadzamy nazwy użytkowników, dla których ssh ma być niedostępne, np.
    gucio
    zenon
    
    No i sprawdzamy, że poleceniem
    $ ssh gucio@komputer
    
    obywatel gucio już się nie zaloguje. Łatwo też sprawdzić, że blokada działa również dla utwierzytelnienia opartego o klucze.
  • Łatwo utworzyć dualną sytuację, w której do ssh są dopuszczeni użytkownicy umieszczeni na liście. Wtedy w pliku /etc/pamd.d/sshd zamiast poprzedniego wprowadzamy wiersz
    account required pam_listfile.so item=user sense=allow file=/etc/ssh/sshd.allow onerr=fail
    
    a następnie tworzymy plik /etc/ssh/sshd.allow z listę użytkowników, którzy mają dostęp. Zachęcam potestować.

Literatura