Extended Slackware

Migracja slackware'owca na PAM

Data dodania: Sun, 16 Oct 2005 06:12:25 +0200

Autor: dozzie

Co to jest PAM? Czym PAM nie jest?

PAM to skrót od pluggable authentication module. PAM jest biblioteką służącą do uwierzytelniania użytkowników, zmiany haseł i tym podobnych rzeczy. Dzięki PAM możesz scentralizować zarządzanie użytkownikami, na przykład przenosząc konta do bazy MySQL albo LDAP, przy czym aplikacje wymagające uwierzytelnienia nie muszą być rekompilowane. Ba! Nie muszą być nawet przystosowane do pracy z MySQL czy LDAP! Aplikacja pyta się biblioteki PAM, czy dany użytkownik ma prawo się połączyć, a PAM odpowiada TAK lub NIE.

PAM jednak nie jest zamiennikiem dla prawa SUID. PAM nie przydziela dodatkowych praw. PAM służy jedynie do potwierdzenia, że użytkownik jest tym, za kogo się podaje i ma w danej chwili prawo połączyć się z daną usługą. Przyznanie uprawnień przyznanych użytkownikowi należy do aplikacji, co zwykle oznacza, że musi ona być uruchomiona z dodatkowymi prawami (na przykład prawami roota).

Kiedy warto używać PAM?

Wystarcza ci logowanie na podstawie haseł z /etc/passwd i /etc/shadow? Nie potrzebujesz wymuszać odpowiedniej siły hasła przy jego zmianie? Polityka pozwala użytkownikom logować się, kiedy tylko chcą? To nie potrzebujesz PAM. Moduł pam_cracklib automatycznie sprawdza przy zmianie hasła, czy podane hasło nie jest zbyt łatwe do złamania przy użyciu słownika, a pam_opie pozwoli na zalogowanie się za pomocą haseł jednorazowych. pam_ldap i pam_mysql z kolei pobiorą dane o użytkowniku ze stosownej bazy danych, niekoniecznie znajdującej się na tej samej maszynie, na której loguje się użytkownik.

Niestety, nic za darmo. PAM jest trudny w konfiguracji, jeśli zaczynasz od zera. Nawet gdy masz już wcześniej przygotowane pakiety, bardzo łatwo jest popełnić pomyłkę i wystawić jakieś konto bez hasła albo zablokować sobie dostęp do roota. Tak więc najpierw RTFM, potem dokładnie przemyśl, co robisz, przygotuj sobie możliwość logowania z pominięciem PAM (/bin/su z pakietu shadow ze standardowej dystrybucji Slackware jest całkiem dobry, o ile pamięta się o ustawieniu na nim prawa SUID), a dopiero na koniec zabieraj się do zmieniania czegokolwiek.

Instalacja

Najprościej jest zainstalować PAM z pakietu. Extended Slackware zawiera pakiet z PAM od dość dawna, podobnie jak pakiety openssh, shadow i sudo skompilowane z obsługą PAM. Jeśli masz ochotę na samodzielną kompilację, to ./configure && make && make install załatwia sprawę. Nie zalecam jednak tej metody instalacji z powodów, które zostały wymienione w slackbuild howto.

Sama instalacja PAM to nie wszystko. Daemony usług wymagających zalogowania się, takie jak SSH, FTP, shadow (/bin/login, /bin/su) i sudo, powinny jeszcze zostać skompilowane z obsługą PAM (zwykle jest to opcja --enable-pam lub --with-pam przy ./configure). Zwykle nie sprawia kłopotów dodanie odpowiedniej opcji do slackbuilda i przekompilowanie pakietu.

Konfiguracja

PAM jest konfigurowany osobno dla każdej aplikacji (a w zasadzie dla każdej usługi, bo aplikacja korzystająca z biblioteki PAM podaje nazwę usługi; zwykle jedna aplikacja nie używa więcej niż jednej nazwy usługi). Wybrane moduły dla programów znajdują się w plikach /etc/pam.d/<appname> (możliwe jest umieszczenie konfiguracji w jendym wspólnym pliku /etc/pam.conf, jednak takie rozwiązanie jest niewygodne z punktu widzenia systemu pakietów).

Każda usługa może korzystać z czterech kontekstów wywołania biblioteki: uwierzytelnienie użytkownika (authentication, w konfiguracji zwana auth), autoryzacja, zwana zarządzaniem kontem (account management, w konfiguracji określane jako account), otwarcie/zamknięcie sesji (session management, w konfiguracji session) i zmiana właściwości hasła (password management, w konfiguracji password). Podczas uwierzytelniania sprawdzane jest tylko to, czy użytkownik jest tym, za kogo się podaje (zwykle oznacza to sprawdzenie podanego hasła z zapisem w bazie użytkowników). Stan konta (na przykład termin ważności hasła lub konta) nie jest tutaj sprawdzany, podobnie jak to, czy użytkownik ma prawo zalogować się o tej porze czy skorzystać z tej właśnie usługi (przynajmniej bezpośrednio). Do tego wszystkiego został przeznaczony kontekst account. Kontekst session służy do zapisania w dzienniku logowania użytkownika, ustawienia zmiennych środowiskowych, nałożenia limitów na ilość czasu procesora i wypisania różnych komunikatów powitalnych, jak "You have new mail" czy MOTD. W password dzieje się trochę więcej, niż sama zmiana hasła. Ten kontekst dotyczy również zmiany daty ostatniej modyfikacji i terminu ważności hasła.

Nie wszystkie wymienione moduły muszą być wymagane do poprawnego zalogowania się. Niektóre mogą być opcjonalne, a niektóre mogą wystarczać bez pozostałych modułów z konfiguracji (taka konfiguracja w danym kontekście jest nazywana łańcuchem). PAM pozwala użycie czterech sposobów reakcji na porażkę modułu w danym łańcuchu: required, requisite, sufficient i optional.
required i requisite są bardzo podobne. Jeśli moduł zostanie wywołany i zwróci kod sukcesu, to sukces lub porażka całego łańcucha będzie zależeć od kodów zwróconych przez następne moduły. Jeśli moduł odniesie porażkę, to albo łańcuch jest natychmiast przerywany, a aplikacji zwrócony jest kod błędu (requisite), albo kolejne moduły są wywoływane, ale łańcuch zwraca błąd (required). Zwykle dla modułów używa się required, a nie requisite, ponieważ użytkownik nie dostaje informacji, na którym kroku uwierzytelnianie czy autoryzacja się nie powiodła (nie jest mu to potrzebne, a chroni przed łamaniem każdego modułu w łańcuchu z osobna). requisite jest używane do wczesnego odmówienia dostępu, na przykład gdy ktoś spoza grupy wheel próbuje wywołać su root.
optional, jak sama nazwa wskazuje, jest używane dla modułów, które nie są w żaden sposób istotne dla procesu logowania. Takie są na przykład moduły pam_mail i pam_motd.
sufficient oznacza, że jeśli do tej pory nie nastąpiła porażka, to sukces wywołania bieżącego modułu jest wystarczający, aby uznać, że cały łańcuch przebiegł poprawnie i nie trzeba wywoływać następnych modułów. Porażka bieżącego modułu nie ma specjalnego wpływu na kolejne moduły w łańcuchu.
W Linux PAM możliwe jest dostoswanie reakcji na kod zwrócony przez moduł w zależności od tego, co to był za kod. Po szczegóły odsyłam do dokumentacji administratora.

W konfiguracji PAM (a przynajmniej Linux PAM, który do tej pory miałem okazję dostosowywać) dzięki dyrektywie include daje się użyć wspólnych, globalnych ustawień. Zamiast wszędzie wpisywać auth required pam_unix.so wystarczy dla kontekstu auth wstawić auth include common-auth. To oczywiście oznacza, że w /etc/pam.d musi istnieć plik common-auth zawierający moduły niezbędne do uwierzytelnienia użytkownika, jak pam_unix, pam_unix2 czy pam_opie.

Jesli konfiguracja dla danej usługi nie zostanie znaleziona, wtedy brany pod uwagę jest łańcuch z pseudousługi other (/etc/pam.d/other).

Teorii chyba na razie już dość. Pewnie interesują cię przykłady konfiguracji.

#
# /etc/pam.d/common-account - authorization settings common to all services
#
# This file is included from other service-specific PAM config files,
# and should contain a list of the authorization modules that define
# the central access policy for use on the system.  The default is to
# only deny service to users whose accounts are expired in /etc/shadow.
#

account  required    pam_unix.so

To jest dość prosta konfiguracja sprawdzania stanu konta. pam_unix sprawdzi w /etc/shadow (albo w bazie NIS), czy konto użytkownika, na którego jest przeprowadzana próba logowania, nie jest przeterminowane. Ten plik jest zwykle włączany przez wszystkie pliki konfiguracyjne usług wymagających kontekstu account.

Teraz coś nieco bardziej skomplikowanego: konfiguracja dla su.

#
# The PAM configuration file for the Shadow `su' service
#

# Allow root to su to everybody without prompting for password
auth     sufficient  pam_rootok.so

# Disallow using su to any user not in wheel group
auth     requisite   pam_wheel.so use_uid group=wheel

auth     include     common-auth
account  include     common-account
session  include     common-session

W kontekście uwierzytelniania użytkownika wystarczający jest UID równy 0. Pytania o hasło nie zostaną zadane, ponieważ root ma prawo zmienić UID na dowolny, niezależnie od tego, czy zna hasło na to konto, czy nie. To zachowanie jest zapewniane przez moduł pam_rootok.
Żaden użytkownik nie ma prawa do użycia su, jeśli nie należy do grupy wheel (moduł pam_wheel); pytanie o hasło nawet się nie pojawi. Wszyscy pozostali użytkownicy muszą przejść poprawnie proces uwierzytelniania zdefiniowany w pliku common-auth, przy czym użytkownik docelowy musi mieć prawo zalogowania się (common-account). Zdarzenie zostanie zapisane w dzienniku systemowym (common-session). Wszystkie operacje z wyjątkiem specyficznych dla su (tu: dostęp dla roota bez hasła i odmowa dostępu dla użytkowników spoza grupy wheel) są zapisane w globalnej konfiguracji, co ułatwi ewentualne przejście na inny system uwierzytelniania/autoryzacji, jak na przykład dodanie opcjonalnych haseł jednorazowych czy przeniesienie użytkowników do bazy LDAP.

#
# /etc/pam.d/common-auth - authentication settings common to all services
#
# This file is included from other service-specific PAM config files,
# and should contain a list of the authentication modules that define
# the central authentication scheme for use on the system
# (e.g., /etc/shadow, LDAP, Kerberos, etc.).  The default is to use the
# traditional Unix authentication mechanisms.

# Last authentication method should be required, because in the other case if
# some config file which includes common-auth has 'auth required' or 'auth
# requisite', then even if all authentication methods fail, user could use
# program.

# Try unix password. It's enough for me if it passes.
auth     sufficient  pam_unix.so nullok

# If normal password fails, try asking user for one time password. Of course
# this line is useless unless you've installed OPIE PAM module.
auth     sufficient  pam_opie.so

auth     required    pam_deny.so

Tym razem oglądamy konfigurację procesu uwierzytelniania. Zgodnie z komentarzem, w tym pliku przynajmniej jeden moduł powinien być wymagany. Ja zdecydowałem się na pam_deny, który odrzuca wszelkie próby uwierzytelnienia. Wcześniej znajdują się dwa moduły, które są do uwierzytelnienia wystarczające. Jeśli pierwszy zawiedzie, nie stanie się nic strasznego i zostanie wywołany drugi. Jeśli i on zawiedzie, wywołany zostanie kolejny. Natomiast jeśli któryś zwróci kod sukcesu (użytkownik poda poprawne hasło wielokrotne albo jednorazowe), proces uwierzytelniania zostanie natychmiast zakończony sukcesem. To oznacza, że dołączenie tego pliku powinno się odbywać po uwierzytelnianiu specyficznym dla danej usługi.

Jeszcze słowo o konfiguracji domyślnej (usługa other). Moim zdaniem tutaj powinien się znaleźć bardzo restrykcyjny zestaw modułów.

#
# /etc/pam.d/other - fallback for non-configured services
#
# Appropriate section from this file is used if one service doesn't have
# configuration for it's PAM request type. By default we don't allow anything,
# but warn in syslog(8) that there is something to be configured.
#

auth     required    pam_warn.so
auth     required    pam_deny.so

password required    pam_warn.so
password required    pam_deny.so

session  required    pam_warn.so
session  required    pam_deny.so

account  required    pam_warn.so
account  required    pam_deny.so

pam_warn zapisze w syslogu informacje o próbie użycia biblioteki PAM przez nieskonfigurowaną usługę (jako typ zdarzenia auth; te zdarzenia w Slackware zostają zapisane w /var/log/secure, w innych dystrybucjach to może być /var/log/auth.log). pam_deny odmówi usługi użytkownikowi. Tak naprawdę tylko zwróci kod błędu, na co jednak aplikacje zwykle reagują odmową dostępu. Dlaczego akurat tak? Dzięki pam_deny brak konfiguracji usługi nie przejdzie niezauważony, a dzięki pam_warn będzie wiadomo, dla którego kontekstu konfiguracji brakuje. Wpisy w syslogu wyglądają następująco:

Oct 16 15:11:38 hans PAM-warn[2132]: function=[pam_sm_authenticate] service=[su]
 terminal=[pts/3] user=[dozzie] ruser=[dozzie] rhost=[<unknown>]
Oct 16 15:12:55 hans PAM-warn[2149]: function=[pam_sm_acct_mgmt] service=[su] te
rminal=[pts/3] user=[dozzie] ruser=[dozzie] rhost=[<unknown>]
Oct 16 15:13:27 hans PAM-warn[2152]: function=[pam_sm_open_session] service=[su]
 terminal=[pts/3] user=[dozzie] ruser=[dozzie] rhost=[<unknown>]
Oct 16 15:14:04 hans PAM-warn[2158]: function=[pam_sm_chauthtok] service=[passwd
] terminal=[<unknown>] user=[dozzie] ruser=[<unknown>] rhost=[<unknown>]

Wszystkie cztery wpisy pochodzą z modułu pam_warn w kontekstach auth (function=pam_sm_authenticate), account (function=pam_sm_acct_mgmt) i session (function=pam_sm_open_session) w konfiguracji usługi su (service=su). Ostatni jest wynikiem braku konfiguracji usługi passwd w kontekście password (function=pam_sm_chauthtok).

Jest jeszcze jedna rzecz, na którą warto zwrócić uwagę. Sam już dwukrotnie popełniłem ten błąd, przy czym w pierwszym przypadku wyśledzenie miejsca wystąpienia zajęło mi dobre dwa dni.

Oct 16 15:15:11 hans su[2168]: PAM pam_parse: expecting return value; [...requir
e]

Taki wpis pojawia się, gdy w pliku konfiguracyjnym zamiast słowa required zostanie użyte require. Bardziej obrazowo, zamiast

account  required    pam_unix.so

w pliku konfiguracyjnym pojawi się

account  require     pam_unix.so

Wpis w syslogu jest niezależny od kontekstu, w którego opisie błąd został popełniony.

Konfiguracja OpenSSH

OpenSSH domyślnie nie używa PAM, nawet mimo zlinkowania go z tą biblioteką. Chęć korzystania z PAM trzeba jeszcze zaznaczyć w konfiguracji. Na szczęście wystarczy dodać UsePAM yes w /etc/ssh/sshd_config i zrestartować daemona SSH. Uwaga: użyj polecenia /etc/rc.d/rc.sshd restart, a nie kolejno ... stop i ... start. stop odcina wszystkie połączenia SSH włącznie z bieżącą sesją, w przeciwieństwie do restart, więc nie będziesz mieć nawet szansy ponownego uruchomienia daemona SSH.

Uwagi końcowe

Wiem z doświadczenia, że łącza potrafią być zawodne. Staraj się nie pracować nad konfiguracją PAM zdalnie, bo to się może skończyć odcięciem dostępu. Jeśli naprawdę nie masz innego wyjścia, upewnij się, że logujesz się przez SSH za pomocą klucza publicznego (zwykle jest sprawdzany przed logowaniem interaktywnym, czyli przed użyciem PAM). Oprócz tego zapewnij sobie możliwość zdobycia uprawnień roota. Możesz do tego użyć /bin/su z pakietu shadow z oryginalnej dystrybucji Slackware (rozpakowujesz ten plik, zmieniasz właściciela na użytkownika root, po czym nadajesz prawa 4711). Ja sam mam zainstalowane i skonfigurowane sudo, przy czym sudo ma autonomiczną konfigurację PAM, bez żadnego włączania plików globalnych. Wprawdzie to nieco utrudnia zmianę metody uwierzytelniania (zmiany trzeba wprowadzić w dwóch miejscach, a nie w jednym), z drugiej jednak strony dzięki temu ewentualna pomyłka w konfiguracji globalnej nie odcina mi możliwości wykonania poleceń jako root. Rzecz jasna, sudo wymaga jeszcze odpowiedniej konfiguracji w /etc/sudoers.
Dodatkowo zmiany w konfiguracji PAM warto wprowadzać wewnątrz sesji screena. Oczywiście, sesja ta może stać się bezużyteczna, jeśli screen został uruchomiony przez roota, dlatego uruchamia się w nim powłokę na koncie zwykłego użytkownika, a dopiero potem loguje się na konto administracyjne. Upewnij się jeszcze, że zmienna TMOUT nie została ustawiona (shelle Bourne-compatible, czyli m.in. bash, ksh i zsh). Wszystkie przygotowania biorą w łeb, jeśli w okresie między utratą i odzyskaniem połączenia root zostanie wylogowany.
Staraj się też trzymać dla jednej usługi (na przykład dla sudo) jak najprostszą konfigurację, bez żadnych dodatkowych modułów. Zdarzyło mi się kiedyś usunąć moduł, który wydawał mi się niepotrzebny. Jak się szybko okazało, to był jedyny moduł używany do uwierzytelniania w systemie, przez co nie dało się zalogować na żadne konto.

Engine by Dozzie. Awful design by Dozzie.