Logowanie hasłem przez SSH to największa odsłonięta powierzchnia zwykłego serwera. Nie pomoże przeciw temu ani Fail2ban, ani żaden próg: tysiąc adresów po jednej próbie na godzinę nigdy nie osiągnie limitu, a zgadywać będą dokładnie tak długo, jak długo zgadywanie w ogóle jest możliwe. Klucze usuwają samą tę możliwość i z całej listy utwardzania jest to jedyne ustawienie, które zmienia sytuację jakościowo.
Straszy w tym tylko jedno: wyłączyć hasło i zostać na zewnątrz. Poniżej kolejność, przy której to niemożliwe.
Klucz
Generuje się na własnej maszynie, nie na serwerze:
ssh-keygen -t ed25519 -C "laptop roboczy"
ed25519 zamiast RSA: krótszy, szybszy i bez pytań o długość. RSA potrzebne jest tylko wtedy, gdy trzeba dostać się do czegoś bardzo starego; wtedy -t rsa -b 4096.
Hasło na klucz warto ustawić: plik klucza można ukraść z laptopa, a bez hasła działa od razu. Wpisywać go za każdym razem nie trzeba — zajmuje się tym ssh-agent, a w macOS i w większości systemów Linux z pulpitem jest już uruchomiony.
Skopiowanie na serwer to jedno polecenie, dopóki hasło jeszcze działa:
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@203.0.113.25
Jeśli ssh-copy-id jest niedostępne, zawartość pliku .pub dopisuje się jako wiersz do ~/.ssh/authorized_keys na serwerze. Prawa mają znaczenie: katalog .ssh 700, plik 600, właścicielem sam użytkownik. Przy innych prawach sshd po cichu odmawia czytania pliku — i to najczęstsza przyczyna „klucz nie działa”.
Sprawdzenie, które rozstrzyga
Zanim cokolwiek wyłączymy, otwórzmy drugie połączenie, nie zamykając pierwszego:
ssh -o PasswordAuthentication=no user@203.0.113.25
Opcja zabrania klientowi cofnąć się do hasła — jeśli logowanie się powiodło, powiodło się kluczem. Dopóki to polecenie nie przejdzie, nie ruszamy dalej. A pierwszego połączenia nie zamykamy do samego końca: przez nie naprawia się wszystko, co da się zepsuć.
Odcięcie i jedna nieoczywista pułapka
Ustawienia kładziemy w osobnym pliku, żeby aktualizacja pakietu ich nie zatarła. Ale nazwa pliku ma znaczenie:
sudo tee /etc/ssh/sshd_config.d/10-hardening.conf <<'EOF'
PasswordAuthentication no
PermitRootLogin no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
LogLevel VERBOSE
EOF
Rzecz w tym, że OpenSSH używa pierwszej napotkanej wartości parametru, a nie ostatniej, jak niemal wszędzie indziej. Pliki z sshd_config.d czytane są alfabetycznie, a obrazy chmurowe Ubuntu kładą tam 50-cloud-init.conf, w którym często stoi PasswordAuthentication yes. Nasz plik z numerem 90 w takiej sytuacji nie zrobi nic: ustawienie niby jest, a hasło dalej działa. Stąd numer 10 — czytany jest wcześniej.
Co wyszło naprawdę, widać bez zgadywania:
sudo sshd -T | grep -Ei 'passwordauth|permitrootlogin|pubkeyauth'
To polecenie wypisuje obowiązującą konfigurację po wszystkich włączeniach. Jemu należy wierzyć, a nie zawartości plików.
Dalej sprawdzenie składni i łagodne przeładowanie usługi:
sudo sshd -t && sudo systemctl reload ssh
sshd -t jest obowiązkowe: literówka w konfiguracji przy restart położy usługę, a nas zostawi na zewnątrz. reload do tego nie zrywa otwartych sesji.
Ubuntu 24.04: port jest nie tam, gdzie się wydaje
Osobna pułapka nowszych Ubuntu: sshd uruchamiany jest tam przez gniazdo systemd. W tym trybie wiersz Port w sshd_config nie robi nic — nasłuchuje gniazdo, a nie usługa. Jeśli zmieniamy port, poprawiać trzeba je:
sudo systemctl edit ssh.socket
i wskazać ListenStream= (pusta wartość zeruje poprzednią) z nowym portem. Objaw, po którym się to poznaje: konfiguracja zmieniona, usługa zrestartowana, a serwer nadal odpowiada na 22.
Przy okazji zmiany portu: jako ochrona to nie działa — skanery znajdują usługę na dowolnym porcie w kilka minut. Jedyny efekt to spokojniejsze dzienniki, bo masowe zgadywanie idzie tylko na 22. Wygodne, ale nie należy mylić tego z bezpieczeństwem.
Kto w ogóle może się logować
Pożyteczne uzupełnienie to jawna lista:
AllowUsers deploy admin
Wszystko, czego na niej nie ma, jest odrzucane jeszcze przed sprawdzeniem klucza. To zamyka też przypadek, w którym jakiś pakiet zakłada użytkownika systemowego z powłoką i katalogiem domowym.
Zapasowe wejście
Jeden klucz na jednym laptopie to pojedynczy punkt awarii. Dysk pada, laptop ginie i serwer staje się niedostępny na zawsze. Rozsądne minimum:
- drugi klucz z innego urządzenia w
authorized_keys; - kopia klucza prywatnego w menedżerze haseł albo na zaszyfrowanym nośniku;
- sprawdzony dostęp do konsoli u hostingodawcy — VNC albo szeregowy. Sprawdzony znaczy, że logowaliśmy się przez niego co najmniej raz, a nie że „gdzieś w panelu jest przycisk”.
Przy okazji warto zobaczyć, co już leży w authorized_keys — i u swojego użytkownika, i u roota. Obcy klucz przeżyje tam każdą zmianę hasła i pozostanie działającym dostępem.
Potem
Zgadywanie haseł nigdzie nie zniknie, po prostu staje się bezużyteczne: w dziennikach zostaną tysiące wierszy Failed password, z których żaden nie może zakończyć się powodzeniem. Warto teraz patrzeć na logowania udane: z jakiego adresu, na jakiego użytkownika, o której godzinie. Udane logowanie kluczem z nieznanego adresu to zdarzenie znacznie ważniejsze niż milion nieudanych prób i o wiele trudniejsze do zauważenia w ogólnym strumieniu. Właśnie ten obraz pokazuje strona demonstracyjna poniżej.