Bezpieczne logowanie do serwera: praktyczny przewodnik po SSH, kluczach i twardym hardeningu Linuxa

0
48
Rate this post

Z tego tekstu dowiesz się...

Dlaczego logowanie do serwera jest krytycznym punktem bezpieczeństwa

Logowanie jako główna brama wejściowa do serwera

Moment logowania do serwera Linux przez SSH to punkt, w którym albo użytkownik otrzymuje legalny dostęp, albo atakujący przejmuje pełną kontrolę nad maszyną. Uzyskanie shella na serwerze to możliwość wykonywania dowolnych komend z uprawnieniami danego użytkownika. Jeśli to konto ma sudo albo jest rootem, serwer jest praktycznie przejęty.

Po uzyskaniu powłoki atakujący może:

  • doinstalować własne oprogramowanie (rootkity, koparki kryptowalut, botnety DDoS),
  • zmodyfikować lub podmienić działającą aplikację (np. wstrzyknąć własny kod do aplikacji webowej),
  • przeprowadzić pivot – wykorzystać serwer jako skocznię do innych hostów w tej samej sieci,
  • podmienić klucze SSH i konta użytkowników, utrzymując się w systemie nawet po zmianie haseł,
  • zaszyfrować dane i zażądać okupu (ransomware na serwerach to nic egzotycznego).

Bezpieczne logowanie SSH nie jest więc „ładnym dodatkiem”, ale fundamentem bezpieczeństwa całej infrastruktury. Nawet najlepiej zaktualizowany system i poprawnie napisane aplikacje nie pomogą, jeśli każdy może zgadnąć hasło do konta z sudo.

„Mam serwer działa” vs „mam serwer bezpieczny”

Świeży VPS od dostawcy cloudowego zazwyczaj działa od razu po włączeniu. Można się zalogować hasłem od dostawcy, doinstalować potrzebne pakiety, wrzucić aplikację i… uznać temat za zamknięty. W praktyce taki serwer to otwarte drzwi z napisem „proszę wchodzić”.

Różnica między serwerem „działa” a serwerem „bezpiecznym” to m.in.:

  • zmiana logowania z hasła na autoryzację kluczem publicznym,
  • wyłączenie bezpośredniego logowania na konto root,
  • ograniczenie, kto w ogóle może próbować logować się przez SSH,
  • konfiguracja firewalla i narzędzi takich jak Fail2ban,
  • logowanie, monitoring i cykliczny audyt bezpieczeństwa logowania.

Serwer „działa” po kilku minutach. Serwer „bezpieczny” wymaga paru kroków więcej, ale te kroki realnie zmieniają poziom trudności ataku z „dziecko z gotowym skryptem” do „poważny cel dla wyspecjalizowanego zespołu”.

Typowe scenariusze ataku na SSH

Najczęstszy scenariusz ataku na SSH to brute-force haseł. Boty skanują Internet w poszukiwaniu portu 22, a potem automatycznie próbują logować się jako: root, admin, test, ubuntu, user i kilkadziesiąt innych nazw, sprawdzając proste hasła. Jeśli serwer umożliwia logowanie hasłem, używa standardowego portu i ma słabe lub powtarzalne dane logowania – wcześniej czy później padnie.

Drugim częstym wektorem jest wyciek klucza prywatnego SSH z niepilnowanego laptopa lub z backupu trzymanego „gdzieś w chmurze”. Jeśli klucz nie jest zabezpieczony passphrase, przejęcie maszyny staje się trywialne. Do tego dochodzą różne błędy konfiguracyjne, jak ustawienie PermitRootLogin yes i pozostawienie logowania hasłem.

Zdarzają się też bardziej wyrafinowane ataki, np. próba podmiany host key serwera lub ataki MITM na niezabezpieczonej sieci Wi‑Fi, liczące na to, że użytkownik nie zwróci uwagi na ostrzeżenie o zmianie odcisku palca serwera.

Nowy VPS, 5 minut online i już lecą próby logowania

Typowa scenka: ktoś uruchamia świeżego VPS-a, instaluje system, zostawia SSH na porcie 22 i logowanie hasłem. Po kilku godzinach zagląda do /var/log/auth.log albo /var/log/secure i przeciera oczy – setki, czasem tysiące nieudanych prób logowania na root oraz popularne loginy. To nie znaczy, że ktoś „poluje” konkretnie na ten serwer. To po prostu automatyczne skanery chodzą po całym IPv4.

Wystarczy czasem poczekać dosłownie 5–10 minut od podniesienia VPS-a, by w logach zobaczyć pierwsze próby. To bardzo trzeźwiące doświadczenie dla każdego, kto uważa, że „mój serwer jest tak mało ważny, że nikt go nie będzie atakował”. Atakującego nie interesuje, czy host jest ważny – liczy się, czy da się go łatwo złamać i wykorzystać.

Konsekwencje udanego włamania przez SSH

Po udanym złamaniu SSH atakujący zazwyczaj robi kilka rzeczy w określonej kolejności:

  • zapewnia sobie trwały dostęp (dodaje własne klucze SSH, tworzy ukryte konto, modyfikuje crona),
  • usuwa lub zaciera logi, aby utrudnić dochodzenie przyczyn,
  • instaluje oprogramowanie do kopania kryptowalut lub dołączania serwera do botnetu,
  • sonduje sieć lokalną w poszukiwaniu innych hostów do ataku (pivot),
  • szuka wartościowych danych: backupów baz danych, plików konfiguracyjnych, kluczy API.

Jeśli na tej samej maszynie działa produkcyjna aplikacja (np. sklep internetowy, system księgowy czy panel klientów), skutki włamania mogą być bardzo bolesne: wyciek danych osobowych, naruszenie RODO, utrata reputacji. Bezpieczne logowanie SSH to jedna z najtańszych form prewencji przed takimi scenariuszami.

Podstawy SSH: jak działa bezpieczne połączenie

SSH kontra Telnet i FTP

SSH (Secure Shell) to protokół umożliwiający zdalne logowanie i wykonywanie poleceń na serwerze w bezpiecznym, szyfrowanym kanale. W odróżnieniu od archaicznych rozwiązań jak Telnet czy klasyczny FTP, SSH szyfruje zarówno dane, jak i same informacje uwierzytelniające.

Telnet i FTP wysyłają hasła oraz dane otwartym tekstem. Każdy, kto podsłucha ruch w sieci (np. w tej samej sieci Wi‑Fi), może przechwycić login i hasło. SSH eliminuje ten problem dzięki silnemu szyfrowaniu i mechanizmom uwierzytelniania, które zapewniają, że użytkownik łączy się z właściwym serwerem, a nie z podszytym hostem.

W praktyce oznacza to, że tam, gdzie kiedyś używano Telnetu, dziś niemal zawsze używa się SSH. Podobnie FTP do administracji systemem ustąpił miejsca połączeniom SCP/SFTP opartym o SSH.

Szyfrowany tunel i uwierzytelnianie serwera (host key)

Podczas nawiązywania połączenia SSH dzieje się kilka ważnych rzeczy:

  1. Klient łączy się z serwerem na wskazanym porcie (domyślnie 22).
  2. Serwer prezentuje swój klucz hosta (host key), czyli tożsamość kryptograficzną.
  3. Klient porównuje otrzymany odcisk palca (fingerprint) klucza hosta z tym zapisanym wcześniej w pliku known_hosts.
  4. Jeśli klucz pasuje, nawiązywany jest szyfrowany kanał, a dopiero potem następuje uwierzytelnienie użytkownika (hasłem lub kluczem).

Host key jest przechowywany po stronie serwera w katalogu /etc/ssh/ w plikach typu ssh_host_ed25519_key, ssh_host_rsa_key i podobnych. Nie należy ich mieszać z kluczami użytkownika – to osobna para butów.

Klucz publiczny i prywatny – lepszy niż hasło do domofonu

Mechanizm logowania kluczem SSH opiera się na kryptografii asymetrycznej. Użytkownik ma dwa powiązane ze sobą klucze:

  • klucz prywatny – trzymany lokalnie, chroniony i nigdy nieudostępniany,
  • klucz publiczny – bezpieczny do rozpowszechniania, wgrywany na serwer.

Można to porównać do sytuacji z zamkiem i kluczem. Zamek (klucz publiczny) można wkręcić w wiele drzwi – serwerów. Prawdziwą tajemnicą jest klucz (klucz prywatny), który pasuje tylko do tego zamka. Hasło to raczej „kod do domofonu”, który łatwo podsłuchać, zgadnąć lub komuś przekazać.

Klucz prywatny użytkownika w typowej konfiguracji znajduje się w katalogu ~/.ssh/ jako plik id_ed25519 albo id_rsa. Klucz publiczny ma tę samą nazwę z rozszerzeniem .pub. Po stronie serwera autoryzowane klucze użytkownika znajdują się w ~/.ssh/authorized_keys.

Plik known_hosts i ryzyko ataku MITM

Za pierwszym razem, gdy użytkownik łączy się z nowym serwerem SSH, klient wyświetla odcisk palca klucza hosta i prosi o potwierdzenie zaufania. Po akceptacji fingerprint jest zapisywany w pliku ~/.ssh/known_hosts. Przy kolejnych połączeniach klient używa tej informacji, aby zweryfikować, czy łączy się z tym samym serwerem.

Jeśli fingerprint nagle się zmieni, a użytkownik widzi ostrzeżenie typu „REMOTE HOST IDENTIFICATION HAS CHANGED!”, oznacza to jedną z dwóch rzeczy:

  • zmieniła się infrastruktura (np. reinstalacja serwera, zmiana kluczy hosta),
  • ktoś próbuje przeprowadzić atak Man-in-the-Middle (MITM), podszywając się pod serwer.

Intuicyjna reakcja „kliknę <yes>, żeby mieć spokój” bywa zgubna. Fingerprint warto zweryfikować innym kanałem (np. przez panel dostawcy, VPN, dokumentację) i dopiero wtedy aktualizować wpis w known_hosts.

Hasło czy klucz? Wybór metody logowania i ich konsekwencje

Logowanie hasłem – wygodne, ale niebezpieczne

Logowanie do SSH hasłem jest kusząco proste: nie trzeba nic generować, wystarczy znać login i hasło. Problem w tym, że taka konfiguracja wystawiona do Internetu generuje ogromną powierzchnię ataku. Hasła można:

  • zgadnąć metodą brute-force lub słownikowo,
  • wykraść przez phishing lub keyloggera na stacji roboczej,
  • ponownie wykorzystać, jeśli użytkownik używa tego samego hasła „wszędzie”.

Nawet bardzo silne hasło nie chroni przed wszystkimi ryzykami, a do tego wymaga pamiętania i ręcznego wpisywania. Dla serwera który stoi 24/7 w sieci publicznej, logowanie hasłem to zaproszenie dla botów i skanerów.

Logowanie kluczem SSH – dzisiejszy standard

Autoryzacja kluczem publicznym SSH eliminuje wiele problemów związanych z hasłami. Klient nie wysyła hasła do serwera – zamiast tego wykonuje operację kryptograficzną przy użyciu klucza prywatnego, a serwer weryfikuje wynik przy użyciu odpowiedniego klucza publicznego.

Zalety logowania kluczem SSH:

  • brak przesyłania hasła w sieci,
  • bardzo długi i silny materiał kryptograficzny (klucz ma znacznie większą „złożoność” niż typowe hasło),
  • możliwość stosowania passphrase do klucza prywatnego,
  • łatwe unieważnianie dostępu – wystarczy usunąć dany klucz publiczny z authorized_keys,
  • wygoda – pojedynczy klucz może dawać dostęp do wielu serwerów.

Na współczesnym, bezpiecznym serwerze VPS podstawowym założeniem jest: logowanie możliwe tylko kluczem, logowanie hasłem wyłączone. Wtedy nawet milion prób bruteforce na porcie 22 nie przyniesie efektu, bo serwer odrzuca uwierzytelnianie hasłowe z definicji.

Kiedy hasło ma jeszcze sens

Hasło nie jest całkowicie bez sensu, ale jego użycie trzeba ograniczyć. Kilka przykładów:

  • środowiska testowe / labowe odcięte od Internetu, dostępne tylko przez VPN lub z sieci wewnętrznej,
  • tymczasowe konta, np. dla audytora, gdy nie ma czasu na dystrybucję kluczy (i tak warto szybko przejść na klucze),
  • jednorazowe logowanie na świeży serwer w celu skonfigurowania autoryzacji kluczem.

W każdym z tych przypadków hasło musi być silne, unikalne, zmienione po użyciu, a docelową konfiguracją i tak pozostaje logowanie kluczem. Model „user + mocne hasło” przestaje być wystarczający w momencie, gdy serwer wystawiony jest na cały świat.

Kombinacje: klucz, hasło do klucza i 2FA

Silny model bezpieczeństwa logowania do serwera łączy kilka warstw:

  • klucz prywatny użytkownika,
  • passphrase chroniąca klucz prywatny przed użyciem po kradzieży,
  • dodatkowe 2FA, np. TOTP (Google Authenticator, FreeOTP) czy klucz sprzętowy U2F.

Taka kombinacja powoduje, że atakujący musi jednocześnie:

  • wejść w posiadanie klucza prywatnego,
  • znać lub złamać passphrase,
  • mieć dostęp do tokena 2FA na telefonie lub klucza sprzętowego.

Brzmi to jak przesada, ale w realnych incydentach bezpieczeństwa atakujący zwykle ma co najwyżej jeden z tych elementów – np. wyciekł backup z kluczem prywatnym, ale już bez telefonu z aplikacją TOTP czy fizycznego klucza U2F. Dopiero złożenie kilku niezależnych zabezpieczeń sprawia, że przejęcie pojedynczego komponentu nie kończy się od razu utratą serwera.

Druga strona medalu to używalność. Jeśli każdy login wymaga przepisywania kodu z telefonu, szybko pojawi się pokusa, żeby „na chwilę” wyłączyć 2FA. Dlatego w codziennej pracy dobrze sprawdzają się połączenia klucz SSH + passphrase + agent SSH, a 2FA służy jako dodatkowa bariera przy logowaniu do szczególnie wrażliwych hostów (np. produkcja, system backupu, host z kluczami CI/CD). Łatwiej wtedy utrzymać wysoki poziom bezpieczeństwa bez doprowadzania adminów do rozpaczy.

Jeżeli środowisko jest większe (kilku adminów, wiele serwerów), sensowne staje się centralne zarządzanie dostępem – np. z użyciem bastion hosta, gdzie 2FA jest wymagane przy wejściu do „strefy zaufanej”. Poszczególne serwery akceptują już tylko klucze, które bastion przepuszcza dalej. Taki model ogranicza rozprzestrzenianie się incydentu: nawet jeśli ktoś przejmie jedno konto, wciąż napotyka kolejne warstwy kontroli.

Nawet w małym projekcie można wprowadzić mini‑politykę: każda osoba ma własny klucz, klucze są opisane i przypisane do konkretnych użytkowników, a odejście z zespołu oznacza natychmiastowe usunięcie odpowiedniego wpisu z authorized_keys. To prosta organizacyjna zasada, która często znaczy dla bezpieczeństwa więcej niż kolejny „magiczny” skaner podatności.

Cały przepis na bezpieczne logowanie do serwera sprowadza się więc do kilku konkretnych ruchów: porządne klucze SSH zamiast haseł, odrobina kryptografii zamiast wiary w „mocne hasło” i twardy hardening SSH oraz systemu, żeby nawet ciekawski skaner z drugiego końca świata odbił się od drzwi, zanim zdąży je naprawdę nacisnąć.

Generowanie i zarządzanie kluczami SSH w praktyce

Wybór algorytmu i długości klucza

Na świeżym systemie pierwsza decyzja dotyczy typu klucza. Klasyczny RSA nadal działa, ale dziś rozsądniej postawić na ed25519 – jest krótszy, szybszy i uważany za bezpieczny na dłużej.

  • ed25519 – dobra, nowoczesna opcja domyślna, brak potrzeby „dobierania” długości,
  • RSA (3072/4096) – gdy trzeba zgodności ze starymi systemami, które nie obsługują ed25519.

Jeśli nie ma twardego wymagania „musi być RSA”, spokojnie można przyjąć prostą zasadę: ed25519 na stacjach roboczych i nowych serwerach, RSA 4096 tylko tam, gdzie ed25519 jest niedostępne.

Tworzenie pary kluczy na lokalnej maszynie

Klucze generuje się zawsze po stronie klienta, a nie na serwerze. W najprostszym wariancie:

ssh-keygen -t ed25519 -C "twoj.email@example.com"

Parametry wyjaśnione po ludzku:

  • -t ed25519 – wybór typu klucza,
  • -C – komentarz (np. e-mail, nazwa hosta, opis osoby), pomaga potem ogarnąć, co jest czyje.

Program zapyta o ścieżkę zapisu (domyślnie ~/.ssh/id_ed25519) oraz o passphrase. Pusta passphrase to proszenie się o kłopoty – jeden wyciek dysku lub laptopa i ktoś loguje się twoim „otwartym” kluczem.

Dla klucza RSA komenda będzie wyglądała podobnie:

ssh-keygen -t rsa -b 4096 -C "twoj.email@example.com"

Dobra passphrase – jak hasło, tylko sensowniejsze

Klucz prywatny chroni passphrase, ale nie musi ona być zlepkiem losowych znaków nie do wymówienia. Dużo wygodniej stosować dłuższą, ale zapamiętywalną frazę:

  • kilka słów po polsku/angielsku + cyfry,
  • bez danych osobowych, nazw projektów, oczywistych cytatów z piosenek,
  • inna niż wszędzie indziej – to nie jest hasło do Netflixa.

Jeśli trzeba: menedżer haseł wesprze w przechowywaniu passphrase, ale nadal lepiej umieć ją wpisać z głowy, gdy padnie dostęp do tego menedżera.

Podstawowa higiena katalogu ~/.ssh

Katalog z kluczami prywatnymi nie może leżeć otwarty dla całego systemu. Szybka kontrola i korekta praw:

chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519.pub

Podobne zasady będą obowiązywać po stronie serwera dla ~/.ssh i authorized_keys. Jeśli coś nie gra z uprawnieniami, OpenSSH często odmawia logowania, zamiast „udawać, że nic się nie stało”.

Kopiowanie klucza publicznego na serwer

Najszybsza bezpieczniejsza droga na pierwszy serwer to ssh-copy-id (o ile jest dostępny na kliencie):

ssh-copy-id -i ~/.ssh/id_ed25519.pub user@twoj-serwer

Ten prosty program:

  • łączy się hasłem do serwera,
  • tworzy katalog ~/.ssh po stronie serwera (jeśli trzeba),
  • dopisuje klucz do ~/.ssh/authorized_keys z właściwymi uprawnieniami.

Gdy ssh-copy-id nie jest dostępny, zostaje klasyczne kopiowanie:

cat ~/.ssh/id_ed25519.pub | ssh user@twoj-serwer "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

Polecenie wygląda na długie, ale robi dokładnie to, co ssh-copy-id, tylko „ręcznie”. Dzięki temu od razu unikamy typowego błędu z niepoprawnymi prawami do plików.

Organizacja wielu kluczy – nie wszystko do jednego worka

Przy jednym serwerze i jednym użytkowniku domyślna para id_ed25519 wystarczy. Przy większym środowisku bałagan robi się szybciej niż się wydaje. Rozsądniej od razu przyjąć prostą strukturę:

  • osobne klucze dla pracy, projektów prywatnych i testów, np.:
    • ~/.ssh/id_ed25519_praca
    • ~/.ssh/id_ed25519_dom
    • ~/.ssh/id_ed25519_lab
  • opisowe komentarze w kluczach publicznych,
  • spis „kto ma jaki klucz” w dokumentacji zespołu.

Dużo łatwiej wtedy cofnąć dostęp konkretnej osobie lub maszynie. Zamiast desperacko zgadywać, który klucz w authorized_keys należał do byłego podwykonawcy, usuwa się po prostu „jan.nowak@firmaX”.

Konfiguracja klienta SSH – plik ~/.ssh/config

Ręczne podawanie parametrów za każdym razem szybko przestaje mieć sens. Klient OpenSSH obsługuje lokalny plik konfiguracyjny:

nano ~/.ssh/config

Przykładowy fragment:

Host prod-app
    HostName 203.0.113.10
    User deploy
    IdentityFile ~/.ssh/id_ed25519_praca
    Port 22
    ForwardAgent no

Host lab-*
    User labuser
    IdentityFile ~/.ssh/id_ed25519_lab
    Port 2222

Dzięki temu wystarczy:

ssh prod-app
ssh lab-db

i klient sam wybierze właściwy klucz, użytkownika oraz port. Przy kilkunastu hostach oszczędność czasu jest konkretna, a ryzyko przypadkowego zalogowania się „złym” kluczem spada.

Agent SSH – logowanie bez ciągłego wpisywania passphrase

Passphrase zabezpiecza klucz, ale nikt nie chce klepać jej 40 razy dziennie. Tu wchodzi ssh-agent, który przechowuje klucze w pamięci i podpisuje żądania w naszym imieniu.

Podstawowy scenariusz:

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

Po podaniu passphrase agent trzyma odblokowany klucz do końca sesji (lub do restartu/wyczyszczenia):

ssh-add -l       # lista załadowanych kluczy
ssh-add -D       # usunięcie wszystkich z agenta

Na systemach desktopowych ssh-agent zwykle startuje automatycznie i integruje się z menedżerem kluczy (Gnome Keyring, Keychain). Dobrze sprawdza się to po połączeniu z 2FA – raz logujesz się do środowiska, a potem agent „odwala czarną robotę” w tle.

Pierwsze bezpieczne logowanie: od świeżego VPS-a do logowania kluczem

Logowanie na konto root – raz, świadomie i ostrożnie

U większości dostawców VPS start odbywa się od danych typu: IP, login root, losowe hasło. Celem jest użycie tego domyślnego dostępu tylko jeden raz, żeby go „ucywilizować”.

Pierwsze kroki po zalogowaniu:

  1. Sprawdzenie, czy naprawdę jesteśmy tam, gdzie myślimy:
    whoami
    hostname
    ip a
  2. Aktualizacja pakietów – zanim pojawi się jakakolwiek aplikacja:
    apt update && apt upgrade -y    # Debian/Ubuntu
    dnf update -y                    # Rocky/Alma/Fedora
    

Wiele włamań opiera się na gotowych exploitach dla starych wersji OpenSSH lub kernela. Aktualizacja „od progu” gasi część najprostszych scenariuszy.

Tworzenie nieuprzywilejowanego użytkownika z dostępem sudo

Z konta root wszystko się da – w tym przypadkowe usunięcie połowy systemu jedną pomyloną komendą. Bezpieczniej działać na zwykłym użytkowniku z prawami sudo:

Jeśli chcesz pójść krok dalej, pomocny może być też wpis: Jak uruchomić system z pendrive i odzyskać dane z uszkodzonego dysku.

adduser admin
usermod -aG sudo admin       # Debian/Ubuntu
# lub
usermod -aG wheel admin      # Rocky/Alma/CentOS/Fedora

Dobrze też ustawić sensowne hasło dla nowego użytkownika, nawet jeśli docelowo nie będzie używane do SSH (przyda się lokalnie, np. po wejściu przez konsolę w panelu VPS):

passwd admin

Dodanie klucza publicznego dla nowego użytkownika

Do świeżego konta przenosimy wcześniej wygenerowany klucz publiczny. Najpierw tworzymy katalog:

mkdir -p /home/admin/.ssh
chmod 700 /home/admin/.ssh
chown admin:admin /home/admin/.ssh

Następnie wklejamy zawartość id_ed25519.pub do pliku authorized_keys:

nano /home/admin/.ssh/authorized_keys

Po zapisaniu:

chmod 600 /home/admin/.ssh/authorized_keys
chown admin:admin /home/admin/.ssh/authorized_keys

Na tym etapie można już z lokalnej maszyny sprawdzić logowanie:

ssh admin@twoj-serwer

Jeżeli wszystko działa – dopiero wtedy przechodzimy do obcinania przywilejów konta root i twardszego hardeningu.

Wyłączenie bezpośredniego logowania rootem

Bezpośrednie zalogowanie się jako root przez SSH to prosty cel: wystarczy znać (lub zgadnąć) jedno hasło. Rozsądniej zmusić atakującego do przejścia przez co najmniej dwa etapy (konto użytkownika + sudo).

Edycja pliku /etc/ssh/sshd_config:

sudo nano /etc/ssh/sshd_config

Odszukujemy lub dodajemy linię:

PermitRootLogin no

Po zmianie wykonujemy łagodny restart demona SSH:

sudo systemctl reload sshd   # lub ssh, zależnie od dystrybucji

Zanim to jednak zrobimy, trzeba mieć pewność, że logowanie dla nowego użytkownika działa i że konto ma dostęp do sudo. Dobrą praktyką jest:

  1. pozostawienie otwartego działającego połączenia root,
  2. otwarcie drugiej sesji jako nowy użytkownik po zmianach,
  3. dopiero po udanym teście zamknięcie sesji root.

Wyłączenie logowania hasłem – tylko klucze

Następny krok to przejście na „świat bez haseł” po SSH. Znowu edycja sshd_config:

sudo nano /etc/ssh/sshd_config

I ustawienia:

PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM yes

UsePAM pozostaje zwykle włączone, ponieważ bywa potrzebne do integracji z 2FA lub innymi mechanizmami logowania. Krytyczna jest linia PasswordAuthentication no – po niej serwer przestaje nawet pytać o hasło przy połączeniu SSH.

Na końcu ponownie przeładowanie usługi i test: nowe połączenie z lokalnego hosta musi wejść kluczem; próba z nieistniejącym kluczem albo z innej maszyny bez klucza powinna zostać odrzucona bez pytania o hasło.

Hardening SSH: konfiguracja sshd_config krok po kroku

Minimalizacja obsługiwanych protokołów i algorytmów

Domyślnie OpenSSH wspiera sporo mechanizmów wstecznej zgodności. W nowym systemie można je spokojnie ograniczyć. W /etc/ssh/sshd_config dopisujemy (lub odkomentowujemy) m.in.:

Protocol 2

KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-512,hmac-sha2-256

Parametry zależą od wersji OpenSSH; w bardzo starej dystrybucji wymuszenie zbyt nowoczesnych algorytmów może odciąć starą infrastrukturę. Na świeżych LTS-ach zestaw powyżej jest sensowną bazą.

Ograniczenie liczby jednoczesnych prób i połączeń

Boty uwielbiają otwierać wiele sesji i zasypywać serwer logowaniami. OpenSSH udostępnia kilka bezbolesnych ograniczeń:

MaxAuthTries 3
MaxSessions 10
LoginGraceTime 30
  • MaxAuthTries 3 – po trzech nieudanych próbach klient jest rozłączany,
  • MaxSessions 10 – ile kanałów (sesji) może otworzyć jeden proces SSH,
  • LoginGraceTime 30 – czas na uwierzytelnienie, potem połączenie jest zamykane.

Dodatkowo można zastosować dynamikę: początkowo dość restrykcyjne wartości, a po obejrzeniu logów i realnego ruchu lekko je skorygować. Jeżeli administratorzy często logują się przez wolne łącza VPN, lepiej dać im nieco więcej czasu na uwierzytelnienie niż domyślne 30 sekund – ale jednocześnie nie zostawiać minutowego „wiecznego wiszenia” półotwartych sesji.

Grupy, AllowUsers i precyzyjne zasady dostępu

Na serwerach produkcyjnych przypadkowe konto z powłoką i dostępem do SSH to proszenie się o kłopoty. Prościej z góry określić, kto w ogóle może próbować się logować. Najwygodniej zrobić to grupą i prostą regułą:

groupadd sshusers
usermod -aG sshusers admin
usermod -aG sshusers devops

Następnie w sshd_config:

AllowGroups sshusers

Od tej chwili tylko użytkownicy w grupie sshusers mają prawo do logowania przez SSH. Alternatywą są dyrektywy AllowUsers / DenyUsers i ich odpowiedniki dla grup, wygodne przy małej liczbie kont lub gdy dostęp ma mieć dosłownie jedna osoba:

AllowUsers admin@192.0.2.* devops

Tu widać dodatkowy bonus: można wiązać użytkownika z konkretną podsiecią (np. tylko z biurowego VPN). Z praktyki: niewielkie firmy często zaczynają od „wszyscy do wszystkiego”, a kończą na precyzyjnych listach dostępu po pierwszym incydencie albo audycie. Lepiej wyprzedzić ten moment.

Match blocks – inne zasady dla wybranych

SSH pozwala dopasowywać konfigurację do użytkownika, grupy czy źródłowego IP. Służy do tego sekcja Match. Daje to sporą elastyczność, np. twardsze zasady dla kont administracyjnych:

Match User admin,devops
    AuthenticationMethods publickey
    X11Forwarding no
    AllowTcpForwarding no
    PermitTTY yes
Match all

W tym przykładzie konta administracyjne muszą używać kluczy, nie mogą tunelować portów ani przekazywać sesji X11. Dla pozostałych użytkowników (jeśli tacy istnieją) obowiązuje ogólna konfiguracja. W podobny sposób można wymusić np. 2FA tylko na grupie sshadmins, a reszcie zostawić zwykłe logowanie kluczem.

Przy blokach Match istotna jest kolejność – konfiguracja jest czytana od góry, a pierwsze pasujące dopasowanie „przepisuje” część parametrów dla danej sesji. Warto więc trzymać szczegółowe reguły bliżej końca pliku i jasno je komentować, inaczej po roku nikt nie będzie pamiętał, czemu konto backup zachowuje się inaczej niż reszta.

Nietypowy port, banery i minimalizacja hałasu w logach

Przestawienie SSH na inny port nie jest zabezpieczeniem kryptograficznym, ale świetnie czyści logi. Zamiast ton wpisów typu „login attempt for root from…”, zostaje w dużej mierze ruch, który naprawdę nas interesuje. Zmiana jest prosta:

Port 2222

Po tej operacji trzeba oczywiście skorygować reguły firewalla i konfiguracje klientów (np. w ~/.ssh/config). Nie ma sensu szaleć z losowym, pięciocyfrowym portem zmienianym co tydzień – stały, nieoczywisty numer w zupełności wystarczy, żeby zatrzymać większość „ślepych” skanów na 22/tcp.

Drobne usprawnienie to także baner logowania. Można w nim umieścić jasną informację o przeznaczeniu systemu i ostrzeżenie prawne, przydatne z punktu widzenia compliance i działu prawnego:

Tworzymy plik z treścią banera, np. /etc/issue.net:

sudo nano /etc/issue.net

Przykładowa zawartość:

Authorized access only. All activity may be monitored and reported.

i podpinamy go w sshd_config:

Banner /etc/issue.net

Po przeładowaniu SSH komunikat pojawi się przed logowaniem. Dla atakującego niczego to nie utrudnia, ale porządkuje kwestie formalne i jednoznacznie sygnalizuje, że to nie jest „publiczny piaskownicowy VPS dla wszystkich”.

Dodatkową korzyścią ubocznej „estetyki” logów jest prostsze wykrywanie anomalii. Gdy dzienniki nie są zasypane setkami tysięcy nieudanych prób na porcie 22, dużo łatwiej zauważyć pojedyncze, nietypowe podejście z dziwnego zakresu IP albo serię logowań o niestandardowej godzinie. Administrator wreszcie widzi las, a nie tylko drzewa.

W praktyce najlepiej traktować konfigurację SSH jak żywy organizm. Zmienił się zespół? Dochodzi VPN z nową podsiecią? Wchodzi 2FA z osobnego systemu? Konfiguracja sshd_config powinna za tym nadążać. Kilka minut poświęconych raz na kwartał na przegląd ustawień i logów potrafi oszczędzić kilka bezsennych nocy po incydencie.

Na końcu dobrze mieć z tyłu głowy prostą regułę: SSH ma być nudne. Bez niespodzianek, bez zgadywanek, bez kreatywnych obejść. Stabilne klucze, rozsądne uprawnienia, twardo przykręcony serwer i spokojne logi – to zazwyczaj oznaka, że wszystko jest tak nudno bezpieczne, jak być powinno.

Dobrym uzupełnieniem będzie też materiał: Monitoring serwera bez bólu: uptime, alerty i metryki w praktyce — warto go przejrzeć w kontekście powyższych wskazówek.

Białe klawisze z napisem PASSWORD na koralowym tle
Źródło: Pexels | Autor: Miguel Á. Padriñán

Dodatkowe warstwy obrony: firewall, Fail2ban i port knocking

Firewall jako pierwsza linia frontu

Skoro SSH jest już przykręcone od środka, pora na obudowanie go z zewnątrz. Firewall ogranicza liczbę drzwi, przez które można w ogóle zapukać do serwera. Zamiast klasycznego „wszystko otwarte, jakoś to będzie”, lepiej przyjąć podejście odwrotne: domyślnie odrzucaj, otwieraj tylko to, co jest absolutnie potrzebne.

Prosta konfiguracja ufw (Debian/Ubuntu)

Na dystrybucjach z ufw (Uncomplicated Firewall) podstawowa konfiguracja to kilka komend. Najpierw reguła dla SSH na nowym porcie:

sudo ufw default deny incoming
sudo ufw default allow outgoing

sudo ufw allow 2222/tcp comment 'SSH'
# np. jeśli serwer wystawia HTTP/HTTPS:
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'

sudo ufw enable

ufw enable potrafi przerwać istniejące połączenie, dlatego dobrze wcześniej upewnić się, że reguła dla aktualnego portu SSH jest aktywna. W praktyce najlepiej:

  1. dodać regułę dla 2222/tcp,
  2. sprawdzić ją: ufw status verbose,
  3. otworzyć nową sesję SSH na porcie 2222,
  4. dopiero wtedy włączyć lub zaostrzyć firewall.

ipTables/nftables – gdy trzeba mieć pełną kontrolę

Na serwerach produkcyjnych często korzysta się z nftables (nowy standard w wielu dystrybucjach) lub jeszcze z iptables. Przykładowa, minimalna konfiguracja w nftables mogłaby wyglądać tak:

sudo nano /etc/nftables.conf
table inet filter {
  chain input {
    type filter hook input priority 0;

    ct state established,related accept
    iif lo accept

    tcp dport { 2222, 80, 443 } accept

    ip protocol icmp accept
    drop
  }
}

Po zapisaniu wystarczy włączyć i uruchomić usługę:

sudo systemctl enable nftables
sudo systemctl restart nftables

Tu również ten sam rytuał: utrzymanie działającej sesji SSH, test drugą konsolą, dopiero po pozytywnym teście zamknięcie starego połączenia. „Zamknąłem się sam w serwerowni” to klasyka gatunku, lepiej jej nie powtarzać.

Fail2ban – automatyczne banowanie natrętów

Dobry firewall odcina cały niepotrzebny ruch, ale nie zatrzyma wszystkiego. Niekiedy port SSH musi być otwarty szerzej (np. dla zespołu rozsianego po świecie), a internet pełen jest botów, które będą próbowały logowania, nawet jeśli nie widziały twojego CV na LinkedIn. Tu wchodzi Fail2ban.

Instalacja i podstawowy jail dla SSH

Fail2ban monitoruje logi (np. /var/log/auth.log) i na podstawie wzorców blokuje IP w firewallu. Instalacja na Debian/Ubuntu:

sudo apt update
sudo apt install fail2ban

Po instalacji dobrym nawykiem jest nie dotykać pliku głównego /etc/fail2ban/jail.conf, tylko stworzyć własny override:

sudo nano /etc/fail2ban/jail.local

Minimalna konfiguracja dla SSH:

[sshd]
enabled  = true
port     = 2222
filter   = sshd
logpath  = /var/log/auth.log
maxretry = 5
findtime = 600
bantime  = 3600
  • maxretry – liczba nieudanych prób, po której IP zostanie zbanowane,
  • findtime – okno czasowe (w sekundach), w którym liczone są próby,
  • bantime – jak długo IP pozostanie zablokowane.

Po zapisaniu konfiguracji:

sudo systemctl restart fail2ban
sudo fail2ban-client status sshd

Druga komenda pokaże, czy jail działa i ilu „gości” aktualnie przebywa na liście nieproszonych.

Dostosowanie do realnego ruchu

Parametry maxretry i bantime bywają źródłem sporów. Admini logujący się z telefonu przez niestabilny internet potrafią sami „wyklikać” bana. Rozsądne podejście:

  • ustawić nieco wyższy maxretry (np. 7–8) dla standardowego SSH,
  • skrócić bantime na czas testów (np. 5–10 minut),
  • później, gdy konfiguracja okaże się stabilna, stopniowo go wydłużać.

Na kluczowych hostach spotyka się też podejście „progressive ban”: pierwsza wpadka krótki ban, kolejne – coraz dłuższe. Fail2ban ma wsparcie dla takich scenariuszy przez recidive jail i osobne filtry.

Przeczytaj także:  Jak zaplanować mały ogród przy domu jednorodzinnym: praktyczne rozwiązania, koszty i najczęstsze błędy

Integracja z ufw/nftables

Najczęściej Fail2ban sam wykryje używany backend (np. iptables lub nftables). Jeżeli korzystasz z ufw, zwykle nie trzeba nic zmieniać – Fail2ban tworzy własne łańcuchy, z którymi UFW się dogaduje. W sytuacjach bardziej zaawansowanych można wymusić backend:

sudo nano /etc/fail2ban/jail.local
[DEFAULT]
banaction = nftables-multiport

i po restarcie fail2ban logi pokażą, czy bany rzeczywiście trafiają do reguł firewalla.

Port knocking – ukryte drzwi do SSH

Port knocking to sprytny trik: port SSH jest zamknięty dla wszystkich, dopóki klient nie „zapuka” w odpowiednią sekwencję portów. Dla skanerów wygląda to tak, jakby serwer w ogóle nie miał SSH; dla świadomego użytkownika – po wykonaniu serii pakietów port nagle się „otwiera”.

Jak to działa w praktyce

Najpopularniejsze narzędzie to knockd. Na serwerze definiuje się sekwencje portów (np. 12345, 23456, 34567), po których otrzymaniu firewall dynamicznie dodaje regułę dopuszczającą ruch na porcie SSH z adresu IP klienta.

Instalacja knockd

sudo apt install knockd
sudo systemctl stop knockd
sudo nano /etc/knockd.conf

Przykładowa konfiguracja z iptables (dla portu SSH 2222):

[options]
        logfile = /var/log/knockd.log

[openSSH]
        sequence    = 12345,23456,34567
        seq_timeout = 15
        command     = /sbin/iptables -I INPUT -s %IP% -p tcp --dport 2222 -j ACCEPT
        tcpflags    = syn

[closeSSH]
        sequence    = 34567,23456,12345
        seq_timeout = 15
        command     = /sbin/iptables -D INPUT -s %IP% -p tcp --dport 2222 -j ACCEPT
        tcpflags    = syn

Po uruchomieniu knockd:

sudo systemctl enable knockd
sudo systemctl start knockd

należy upewnić się, że firewall domyślnie nie wpuszcza na port 2222, a dostęp pojawia się dopiero po „zapukaniu”.

Klient – jak „zapukać” do serwera

Na stacji roboczej wystarczy prosty skrypt lub program knock. Przykład (Linux/macOS):

knock example.com 12345 23456 34567
ssh -p 2222 admin@example.com

Wersja „leniwa” to funkcja w ~/.bashrc:

knssh() {
  knock "$1" 12345 23456 34567
  sleep 2
  ssh -p 2222 "$1"
}

i potem wywołanie:

knssh admin@example.com

Kiedy port knocking ma sens, a kiedy przeszkadza

Mechanizm bywa bardzo wygodny dla pojedynczych, prywatnych serwerów, gdzie loguje się kilka zaufanych osób i można im spokojnie wytłumaczyć dodatkowy krok. W środowiskach większych (kilkudziesięciu adminów, automaty, Ansible, CI/CD) port knocking szybko zamienia się w źródło frustracji i niepotrzebnej komplikacji.

Jeżeli infrastruktura stoi za VPN-em, a SSH dostępne jest tylko z wewnętrznej sieci, port knocking zwykle można sobie odpuścić. Znacznie większy zysk przyniesie szczelny VPN, dobre logowanie i monitoring.

Rate limiting i geo-filtry na firewallu

Oprócz Fail2ban można też zdusić nadmiarowy ruch już na poziomie samego firewalla. Klasyczny przykład to ograniczenie liczby nowych połączeń na sekundę z jednego adresu IP. W nftables wygląda to np. tak:

tcp dport 2222 limit rate 10/minute accept
tcp dport 2222 drop

Proste, ale skuteczne – powolny, normalny ruch przechodzi, gwałtowne skanowanie jest uciszane. Dodatkowo, w niektórych scenariuszach stosuje się geo-filtry (np. przepuszczanie SSH tylko z paru krajów lub konkretnych ASN-ów), jednak wymaga to aktualizowanych list IP i dobrej integracji z narzędziami typu ipset lub modułami geoip dla firewalla.

Łączenie warstw – jak to wszystko nie zepsuć

Gdy zaczynają się piętrzyć narzędzia – sshd, firewall, Fail2ban, port knocking, ewentualnie VPN na dokładkę – łatwo doprowadzić do sytuacji, w której sam właściciel serwera nie potrafi się w niego wbić o trzeciej w nocy podczas awarii. Żeby temu zapobiec, warto trzymać się kilku prostych zasad:

  • utrzymywać co najmniej jeden znany, przetestowany „awaryjny” sposób logowania (np. przez VPN z prostszą polityką),
  • zmiany wprowadzać etapami: najpierw modyfikacja i test na jednym komponencie, dopiero potem dokładanie kolejnego,
  • spisywać, choćby w prostym pliku README w repo z konfiguracją, jak działa logowanie i skąd wynika każda warstwa zabezpieczeń.

Dobrym ćwiczeniem jest też cykliczny „test paniki”: ktoś z zespołu wchodzi na serwer, wykonuje typowe zadanie administracyjne (np. restart usługi) w warunkach symulowanego incydentu. Jeżeli po drodze potyka się o zbyt agresywny ban, dziwne zasady firewalla albo enigmatyczne match-blocki w sshd_config, to znak, że konfiguracja stała się zbyt wyrafinowana jak na możliwości obsługi.

Logowanie awaryjne i „break-glass”: co zrobić, gdy wszystko się wysypie

Nawet przy najlepiej zaplanowanej konfiguracji przychodzi dzień, kiedy coś się rozjedzie: zbyt agresywny Fail2ban, błędny Match w sshd_config, reguła firewalla dodana „tylko na chwilę” pół roku temu. Wtedy przydaje się scenariusz „break-glass” – przewidziany z góry sposób dostania się do maszyny, gdy standardowe logowanie przestaje działać.

Konsola z panelu VPS / IPMI / KVM

Na większości VPS-ów dostępna jest konsola awaryjna w panelu dostawcy (czasem jako „Serial console”, „Emergency console”). W przypadku serwerów bare metal rolę tę pełni IPMI, iLO, DRAC lub wirtualny KVM. Dzięki temu można:

  • zalogować się lokalnie jako root (o ile nie jest to całkiem wyłączone),
  • tymczasowo wyłączyć firewall lub Fail2ban,
  • naprawić błędną konfigurację sshd_config i zrestartować sshd.

Dobrym nawykiem jest przetestowanie konsoli awaryjnej zanim będzie potrzebna. Czasem wymaga osobnego hasła, VPN-a do panelu lub specyficznej wtyczki przeglądarki; lepiej to ogarnąć w poniedziałek rano niż w niedzielę o 3:00.

Tryb single-user / recovery

Na maszynach, gdzie masz fizyczny lub wirtualny dostęp do GRUB-a, można skorzystać z trybu jednoużytkownikowego:

  1. Podczas startu systemu zatrzymać GRUB-a (klawisz Esc lub Shift w zależności od dystrybucji).
  2. Edytować wpis startowy (klawisz e).
  3. Do linii z parametrami jądra dopisać single lub systemd.unit=rescue.target.
  4. Uruchomić system z tej konfiguracji (Ctrl+x lub F10).

Po starcie zyskuje się powłokę root na lokalnej konsoli. To moment na:

  • przywrócenie kopii zapasowej /etc/ssh/sshd_config,
  • wyłączenie problematycznych usług: systemctl disable --now fail2ban,
  • tymczasowe rozluźnienie reguł firewalla.

Awaryjne konto administracyjne

W wielu zespołach przydaje się jedno dodatkowe konto adminsko-serwisowe, używane tylko w trudnych sytuacjach. Kilka praktycznych założeń:

  • logowanie wyłącznie kluczem, hasło wyłączone,
  • klucz przechowywany poza standardowym środowiskiem (np. osobny klucz na pendrivie, w sejfie, w menedżerze haseł zespołu),
  • konto objęte normalnym audytem (logi, sudo), ale bez zbyt wyrafinowanych ograniczeń typu Match blokujący logowanie z 90% świata.

Na części serwerów stosuje się też „awaryjny” port SSH (np. 22222) z dużo prostszymi regułami – ale tylko wtedy, gdy reszta mechanizmów (firewall, VPN) jest na tyle szczelna, że nie zamienia się to w boczne wejście dla każdego.

Bezpieczne użycie sudo i podział uprawnień

Bez względu na to, jak solidnie umocnisz SSH, jedno sudo su - z kompromitowanego konta potrafi zaorać cały host. Dlatego konfiguracja sudo jest równie istotna jak sam dostęp do serwera.

Oddzielenie konta użytkownika od konta admina

Jeżeli jeden użytkownik używa tego samego konta do wszystkiego (SSH, edycja kodu, sudo, czasem nawet do logowania się na stacje robocze), każdy wyciek hasła lub klucza otwiera prostą drogę do roota. Mniej bolesne jest rozdzielenie ról:

  • konto „zwykłe” – dostęp bezpośrednio do plików, logów, własnych procesów, bez sudo,
  • konto „admin” – używane tylko do zadań administracyjnych, z ograniczonym sudo.

Alternatywnie można przyjąć model: logowanie jako zwykły użytkownik, a dopiero potem sudo -i po podaniu hasła z mocnym 2FA do menedżera haseł. Ważne, żeby nie robić wszystkiego jako root.

Ograniczenie zakresu sudoers

Plik /etc/sudoers i katalog /etc/sudoers.d/ to miejsce, w którym bardzo łatwo o błąd „dajmy wszystko, będzie działać”. Zamiast klasycznego:

%admin ALL=(ALL) NOPASSWD: ALL

lepiej zdefiniować precyzyjniejsze reguły. Przykładowo, zespół zajmujący się aplikacją webową może potrzebować:

%webadmins ALL=(root) NOPASSWD: /bin/systemctl restart nginx, 
                                 /bin/systemctl restart php*-fpm, 
                                 /usr/bin/journalctl -u nginx -u php*-fpm

Każdy nowy przywilej powinien być dopisywany świadomie, a nie jako „otwieramy NOPASSWD: ALL, bo Ansible się krzywi”. Jeżeli automatyzacja wymaga szerokich uprawnień, lepiej stworzyć dedykowanego użytkownika tylko dla CI/CD niż rozdawać roota każdemu.

Logowanie i audyt poleceń sudo

Domyślnie sudo zapisuje informacje w /var/log/auth.log, ale poziom szczegółowości da się zwiększyć. W /etc/sudoers można włączyć m.in. log_input i log_output (wspierane w nowszych wersjach sudo):

Defaults log_output
Defaults!/usr/bin/journalctl !log_output

Pozwala to na rejestrowanie wyjścia z wybranych poleceń wykonywanych przez sudo. Pełne śledzenie wszystkiego bywa zbyt ciężkie i problematyczne prywatnościowo, ale dla krytycznych komend (np. narzędzia do zarządzania bazą danych na produkcji) może mieć sens.

Bezpieczeństwo kluczy SSH na stacjach roboczych

Wszystkie mechanizmy hardeningu serwera tracą urok, jeśli klucz prywatny leży w ~/Downloads i ma uprawnienia 0644. Bezpieczna konfiguracja klienta to druga połowa układanki.

Silne hasło do klucza i agent SSH

Klucz prywatny bez passphrase to prezent dla każdego, kto dorwie się do twojego laptopa. Sensowny kompromis to:

  • klucz chroniony długim hasłem (najlepiej generatorem z menedżera haseł),
  • użycie ssh-agent, który przechowuje odszyfrowany klucz w pamięci przez określony czas.

Na desktopowych systemach agent zwykle startuje automatycznie. W razie potrzeby można go uruchomić ręcznie:

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

Na maszynach współdzielonych (serwery developerskie, jump hosty) agent bywa wygodą, ale i ryzykiem – inni użytkownicy nie powinni mieć możliwości odpytywania twojego agenta. Tu z pomocą przychodzi odpowiednie ustawienie uprawnień katalogu ~/.ssh i ostrożne przekazywanie agentów (ForwardAgent tylko tam, gdzie ma to sens).

Przekazywanie agenta SSH a dostęp do dalszych serwerów

Opcja ForwardAgent (w ssh -A lub w ~/.ssh/config) pozwala wykorzystać klucz lokalny po zalogowaniu się na pośredni host. To wygodne przy skakaniu przez bastiony, ale jeśli taki pośredni host zostanie przejęty, atakujący może użyć twojego agenta, dopóki klucz jest w nim załadowany.

Bezpieczniejszy model to:

  • włączenie ForwardAgent tylko dla zaufanych bastionów:
Host bastion-prod
  HostName bastion.example.com
  User admin
  ForwardAgent yes
  • logowanie z bastionu na dalsze hosty już bez kolejnego przekazywania agenta,
  • okresowe czyszczenie agenta (ssh-add -D) po zakończeniu pracy.

Backup i rotacja kluczy

Utrata prywatnego klucza (awaria dysku, kradzież laptopa, usunięcie katalogu) potrafi być równie bolesna, co włamanie. Dobrze mieć plan:

  • zaszyfrowany backup klucza w menedżerze haseł lub sejfie organizacji,
  • procedurę szybkiego podmiany klucza na serwerach (np. skrypt / playbook, który dodaje nowy klucz i usuwa stary),
  • termin ważności kluczy – np. raz na rok generacja nowego i wycofywanie poprzedniego.

Jeżeli masz setki serwerów, ręczne dodawanie nowego klucza do ~/.ssh/authorized_keys szybko zniechęca. Tu wygrywają narzędzia do zarządzania konfiguracją (Ansible, Puppet, Salt), które trzymają definicję „ten użytkownik ma mieć te klucze” w jednym miejscu.

Segmentacja dostępu: jump hosty, bastiony i VPN

Jednym z największych grzechów jest wystawienie portu SSH każdego serwera wprost do internetu. Dużo bezpieczniej, gdy ruch administracyjny płynie przez wąskie gardło – pojedynczy, dobrze zabezpieczony punkt.

Bastion SSH jako jedyne wejście

Bastion (jump host) to serwer, który:

  • ma otwarty port SSH do internetu (często schowany za dodatkowymi warstwami typu VPN, 2FA),
  • ma bardzo ograniczony zestaw usług – w zasadzie tylko SSH i monitoring,
  • służy do logowania na dalsze serwery w sieci prywatnej.

Konfiguracja po stronie klienta jest wygodniejsza z wykorzystaniem ProxyJump:

Host bastion
  HostName bastion.example.com
  User admin

Host web-*.prod
  User admin
  ProxyJump bastion

Po takiej konfiguracji ssh web-01.prod sam przeskoczy przez bastion, bez konieczności wpisywania kaskady komend. Logi z bastionu stają się centralnym miejscem podglądu tego, kto gdzie się logował.

VPN jako tunel do całej strefy administracyjnej

Zamiast wystawiać port SSH bastiona na świat, można pójść krok dalej i udostępniać logowanie tylko przez VPN (WireGuard, OpenVPN, IPsec). Model wygląda wtedy tak:

  1. Klient nawiązuje połączenie VPN do sieci administracyjnej.
  2. Dopiero z tej sieci widać porty SSH serwerów (lub bastiona).
  3. Firewall odrzuca cały ruch SSH z internetu, poza interfejsem VPN.

Taki układ upraszcza konfigurację samego SSH (mniej kombinowania z port knockingiem i geo-filtrami), ale przenosi ciężar bezpieczeństwa na warstwę VPN. Trzeba dbać o klucze, certyfikaty, aktualizacje i sensowną politykę odcinania nieaktywnych kont.

Monitoring logowań i wykrywanie anomalii

Dobre zabezpieczenia to jedno, ale bez obserwacji trudno zauważyć, że ktoś próbuje je obchodzić albo właśnie mu się udało.

Przegląd logów SSH w praktyce

Podstawowe miejsce to /var/log/auth.log (Debian/Ubuntu) lub /var/log/secure (CentOS/RHEL). Kilka prostych komend bywa zaskakująco skutecznych:

# Ostatnie udane logowania
grep "Accepted" /var/log/auth.log | tail -50

# Próby logowania jako root
grep "Failed password for root" /var/log/auth.log | tail -50

# Logowania z konkretnego IP
grep "Accepted" /var/log/auth.log | grep "203.0.113.10"

W niewielkich środowiskach już takie pół-automatyczne oglądanie logów co jakiś czas potrafi wychwycić zdarzenia, które nie przebiły się do żadnego SIEM-a.

Centralizacja logów

Gdy serwerów jest więcej, przeglądanie każdego z osobna robi się nierealne. Rozwiązaniem jest wysyłanie logów do centralnego systemu:

  • rsyslog / syslog-ng na własny serwer logów,
  • stack typu ELK/Opensearch (Filebeat + Elasticsearch + Kibana),
  • promowane ostatnio rozwiązania SaaS (ale wtedy logi idą w chmurę kogoś innego).

W centralnym systemie można zbudować proste alerty: „powiadom, jeśli ktoś zaloguje się na jakikolwiek serwer z nowego kraju” albo „poinformuj, jeśli użytkownik X zaloguje się poza godzinami pracy, z innego kontynentu niż zwykle”. To nie jest kryptorakieta, a potrafi ocalić weekend.

Bezpieczna automatyzacja: Ansible, CI/CD i konta techniczne

Ręczne klepanie komend na serwerach długo nie wytrzymuje próby czasu. Pojawiają się Ansible, Puppet, skrypty z CI/CD – i nagle zamiast jednego klucza admina mamy dziesięć kluczy technicznych pływających po różnych repozytoriach.

Dedykowane konta dla automatyzacji

Zamiast używać tego samego konta, którym logujesz się interaktywnie, lepiej stworzyć osobne konto dla narzędzi:

Takie konto powinno mieć:

  • osobny klucz SSH, trzymany w bezpiecznym storage (np. Vault, zaszyfrowane secret-y CI/CD),
  • ściśle ograniczone uprawnienia w sudoers – tylko komendy potrzebne do deployu/konfiguracji,
  • zakaz interaktywnego logowania z zewnątrz (np. AllowUsers ansible@bastion i dostęp wyłącznie z konkretnego IP lub sieci VPN),
  • jasno opisanego właściciela biznesowego: „to konto należy do Ansible, za jego użycie odpowiada zespół X”.

Dobrze sprawdza się model: ludzie logują się na swój bastion, z bastionu odpalają Ansible z konta technicznego. Dzięki temu nie trzeba rozdawać klucza CI/CD po całej firmie, a jednocześnie w logach widać, kto faktycznie pociągnął za spust.

Ograniczanie kluczy w authorized_keys

Dla kont technicznych można wykorzystać zaawansowane opcje przy wpisach w authorized_keys. Jeden klucz nie musi dawać pełnego dostępu do powłoki. Można go przywiązać do konkretnego polecenia i IP:

from="10.0.0.0/24",command="/usr/local/bin/run-ansible.sh",
no-port-forwarding,no-pty,no-agent-forwarding ssh-ed25519 AAAA... ansible-ci

Tak skonfigurowany klucz nie pozwoli się zalogować „normalnie”. Umożliwi wyłącznie uruchomienie run-ansible.sh z określonej sieci. Nawet jeśli klucz wycieknie do jakiegoś publicznego repozytorium, atakujący będzie miał pod górkę.

Podobnie można podcinać możliwości innym automatom: klucz od backupu nie potrzebuje port forwardingów, a bot od deployu frontendu nie musi móc uruchamiać dowolnych binarek jako root. Im bardziej ciasna definicja tego, co dany klucz może zrobić, tym mniejszy zasięg szkód po jego kompromitacji.

Bezpieczne klucze w CI/CD i repozytoriach

Systemy CI/CD lubią trzymać sekrety „gdzieś tam w ustawieniach projektu” i wszyscy udają, że to bezpieczne. Kilka prostych zasad naprawdę robi różnicę:

  • klucze prywatne nigdy w repozytoriach – nawet prywatnych; od tego są mechanizmy secrets w CI/CD i menedżery kluczy,
  • osobny klucz dla każdego pipeline’u/projektu, zamiast jednego „super-klucza do wszystkiego”,
  • rotacja kluczy przy większych zmianach w zespole lub incydentach,
  • dostęp do podglądu sekretów ograniczony tylko do adminów platformy, nie całego zespołu developerskiego.

W praktyce dobrze się sprawdza model „skarbnika”: jedna mała grupa ludzi zarządza sekretną częścią CI/CD, reszta może triggerować pipeline’y, ale nie zobaczy samych kluczy. To mniej wygodne niż wrzucenie wszystkiego do .env w repo, ale też znacznie mniej stresujące, gdy developer przez pomyłkę upubliczni gałąź.

Ślad audytowy dla automatów

Automatyzacja bywa świetnym alibi dla zmian, których nikt nie chce podpisać własnym nazwiskiem. Żeby nie skończyło się to „magia Ansible coś popsuła”, przydaje się sensowny ślad audytowy:

  • logowanie działań narzędzia (np. ansible-playbook --verbose z logiem wysyłanym do centralnego systemu),
  • przechowywanie commitów z definicją infrastruktury (playbooki, role, manifesty) w repo z review i historią,
  • wiążenie deploymentów z konkretnymi osobami: merge request zajechał do main, pipeline wystartował, konto techniczne zrobiło swoje – w logach widać całą ścieżkę.

Gdy przyjdzie dzień, w którym ktoś zapyta „kto w nocy przełączył konfigurację produkcji i dlaczego?”, dobrze mieć odpowiedź inną niż wzruszenie ramionami. Kilka prostych mechanizmów logowania i review zwykle wystarczy, by ten etap przesłuchania przeszedł z godnością.

Dobrą praktyką jest też wersjonowanie samych konfiguracji dostępu: plików authorized_keys generowanych z szablonów, reguł sudoers, definicji ról w Ansible. Zmiana uprawnień wtedy nie dzieje się „na żywym organizmie” pojedynczego serwera, tylko przechodzi normalny proces: commit, review, pipeline, rollout. Gdy audyt pyta „od kiedy ten klucz miał dostęp do produkcji?”, nie trzeba wertować notatek w OneNote – widać to w historii git.

Przydatnym drobiazgiem bywa osobna przestrzeń logów dla kont technicznych. Jeśli system logowania centralnego pozwala na tagowanie zdarzeń, sensownie jest rozdzielić: jedno źródło dla ludzi, drugie dla automatów. Potem szybciej wychodzi na jaw, że coś jest nie tak, gdy nagle pipeline „nocny-deploy” zaczyna strzelać w SSH o 14:30 z innego IP niż dotychczas.

W większych organizacjach opłaca się też jasny podział ról: zespół, który może zmieniać playbooki, niekoniecznie musi mieć prawo do zmiany samej infrastruktury CI/CD i sekretnych kluczy. Zdejmuje to presję z pojedynczych adminów i ogranicza chaos, gdy kilku ludzi jednocześnie „naprawia” dostęp. Ustalenie, kto może dodać nowy klucz do konta technicznego i na jakich zasadach, jest równie ważne, jak sama techniczna konfiguracja SSH.

Całość sprowadza się do jednego: logowanie do serwera nie jest drobnym technicznym szczegółem, tylko newralgiczną częścią bezpieczeństwa. Kilka godzin poświęconych na klucze zamiast haseł, rozsądną konfigurację sshd, prosty firewall, Fail2ban i przejrzyste zasady dla ludzi oraz automatów, bardzo często zwraca się w najmniej oczekiwanym momencie – wtedy, gdy zamiast weekendowego incident response można spokojnie wyłączyć laptopa.

Segmentacja dostępu i architektura „bastion first”

Nawet najlepiej utwardzony serwer dostaje zadyszki, jeśli wystawia SSH wprost do całego internetu. Im mniej miejsc, z których można się wbić, tym lepiej. I im bardziej przewidywalne ścieżki dostępu, tym łatwiej je monitorować i uszczelniać.

Serwer bastion: jedna brama, wiele serwerów

Bastion to pojedynczy, mocno dopieszczony serwer SSH, przez który przechodzą wszystkie połączenia do środka sieci. Reszta maszyn przyjmuje wyłącznie ruch z bastiona (lub bardzo wąskiej puli IP). Dzięki temu:

  • zewnętrzni użytkownicy nie łączą się bezpośrednio z produkcją,
  • SSH na serwerach aplikacyjnych może słuchać tylko na prywatnym interfejsie,
  • cały monitoring i hardening logowania koncentruje się wokół jednego punktu.

Do tego dochodzi zwykła wygoda: wystarczy znać jeden adres, a reszta odbywa się przez ssh bastion i dalej skoki z wewnętrznego hosta.

Przykładowa polityka dostępu

Typowy, sensowny układ wygląda tak:

  • bastion ma publiczny adres IP, twarde reguły firewall i sensowne limity w SSH,
  • serwery w sieci prywatnej: port 22 otwarty tylko na IP bastiona (i np. system backupowy),
  • wszystkie logowania z zewnątrz: najpierw na bastion, potem dopiero do wewnętrznych hostów.

Jeżeli trzeba awaryjnego dostępu „z domu admina”, lepiej dodać go jako wyjątkowy adres do firewall-a bastiona niż rozwiercać SSH na każdym serwerze produkcyjnym.

ProxyJump i konfiguracja ~/.ssh/config

Żeby bastion nie stał się przekleństwem użytkowników, dobrze wykorzystać wbudowane funkcje SSH, które eliminują konieczność ręcznego robienia hopów.

Host bastion
  HostName bastion.example.com
  User admin
  IdentityFile ~/.ssh/id_ed25519_admin

Host app-*
  User deploy
  ProxyJump bastion
  IdentityFile ~/.ssh/id_ed25519_deploy

Po takiej konfiguracji:

  • ssh app-01 automatycznie przechodzi przez bastion,
  • można włączyć agent forwarding do krótkotrwałych skoków (choć nie jako panaceum na wszystko),
  • łatwo dodać kolejne środowiska (np. app-*-stage) bez rzeźbienia w komendach.

ProxyJump (czyli -J) gdzie tylko się da zastępuje łańcuchy ProxyCommand nc, mniej się to psuje i jest czytelniejsze dla kogoś, kto przejmie konfigurację po tobie.

Ograniczenie zaufania do stacji roboczych

Najtwardszy serwer nic nie poradzi, jeśli ktoś zaloguje się z laptopa pełnego malware. Dlatego przydają się dodatkowe bezpieczniki po stronie klienta:

  • zasada „brak kluczy prywatnych na losowych maszynach” – jedyny wyjątek to stacje robocze adminów, i to sensownie zarządzane,
  • brak agent forwarding na wszystko jak leci – tylko tam, gdzie jest faktycznie potrzebny,
  • logowanie z domowych maszyn przez VPN, a nie „prosto z internetu”,
  • ochrona kluczy przez ssh-agent z krótką ważnością i, tam gdzie to ma sens, z użyciem kluczy sprzętowych.

Jeśli organizacja dopuszcza BYOD wśród adminów, trzeba założyć, że kompromitacja takiego sprzętu prędzej czy później nastąpi. Klucz sprzętowy i wymuszone 2FA na bastionie przynajmniej utrudnią wrogowi drogę.

SSH a kontenery, chmura i „nowoczesna” infrastruktura

Gdy infrastruktura zaczyna żyć w Kubernetesie, serverlessie i całej reszcie skrótów, pokusa rezygnacji z klasycznego SSH jest duża. W praktyce często wraca ono tylnymi drzwiami – jako awaryjny dostęp do nodów czy bastionów w VPC.

Dostęp do nodów Kubernetes

Klastry K8s zwykle mają dwa poziomy dostępu:

  • logiczny – przez kubectl,
  • systemowy – SSH na node’y, gdy dzieje się coś niskopoziomowego.

Bezpieczny model to:

  • SSH tylko przez bastion w tej samej sieci/VPC,
  • brak bezpośredniego SSH z internetu na node’y, nawet „na chwilę do debugowania”,
  • standaryzacja kont: jeden systemowy użytkownik (np. ops) z kluczami adminów w authorized_keys,
  • reszta uprawnień przez sudo i systemd, nie przez tajemnicze skrypty na /root/.

Kiedy ktoś musi wejść na node’a, lepiej, żeby ten incydent był wyjątkiem, dobrze udokumentowanym w logach, a nie standardową ścieżką pracy.

Warto też podejrzeć, jak ten temat rozwija praktyczne wskazówki: informatyka — znajdziesz tam więcej inspiracji i praktycznych wskazówek.

Serwery w chmurze publicznej

Dostawcy chmurowi dorzucają swoje mechanizmy dostępu: Session Manager w AWS, IAP w GCP czy Azure Bastion. Dobrze z nich korzystać, ale warto zrozumieć, jak składają się z klasycznego SSH pod spodem:

  • często zastępują otwieranie portu 22 z internetu połączeniem przez agentów/konsole webowe,
  • integrują się z IAM/AD – zamiast kluczy per user mamy logowanie oparte o tożsamość korporacyjną,
  • pozwalają nadawać dostęp „na chwilę” bez rozdawania klucza prywatnego.

Jeśli takie mechanizmy są wdrożone, tradycyjny SSH z kluczem bywa zarezerwowany wyłącznie dla planu awaryjnego, a codzienne logowania idą przez kontrolowaną warstwę pośrednią.

Instancje efemeryczne i krótkotrwałe

W środowiskach z autoscalingiem serwery żyją czasem godziny, nie lata. Kilka praktycznych zasad:

  • klucze użytkowników lub kont technicznych nie są wgrywane ręcznie, tylko przez cloud-init/konfigurację narzędzia IaaC,
  • zmiana klucza nie wymaga logowania na każdą maszynę – dzieje się w kodzie (Terraform, Ansible, itp.),
  • wzorce AMI/obrazów są aktualizowane regularnie, żeby nie powoływać do życia serwerów z dziurawym OpenSSH sprzed kilku lat.

Jeżeli jeden serwer znika i pojawia się z innym kluczem hosta co trzy godziny, użytkownicy zaczną omijać ostrzeżenia SSH o zmianie host key jak reklamy w przeglądarce. To z kolei otwiera drogę pod ataki MITM. Dlatego generowanie i rotacja host keys powinna być zaplanowana, a nie „jakoś to będzie”.

Zaawansowane mechanizmy SSH: skanery, tunelowanie i kontrola ryzyka

SSH to nie tylko powłoka na porcie 22. To także tunelowanie TCP, SOCKS proxy i całkiem zwinny „VPN dla ubogich”. Świetne narzędzia, o ile nie przerodzą się w niekontrolowane przejścia między sieciami.

Port forwarding – użyteczny, ale niebezpieczny

Przekierowania portów to codzienność przy pracy z bazami danych, panelami administracyjnymi czy debugowaniem serwisów:

  • lokalne (-L) – np. ssh -L 5432:localhost:5432 db@db-server i mamy bazę lokalnie,
  • zdalne (-R) – np. wystawianie lokalnej usługi na zdalnym serwerze,
  • dynamiczne (-D) – SOCKS proxy, często używane do „wejścia” w sieć zdalną.

Bez ograniczeń przekierowania potrafią stworzyć piękne boczne ścieżki między segmentami sieci, których nikt nie monitoruje. Z punktu widzenia atakującego – prezent.

Bezpieczniejsze używanie tuneli

Kilka prostych reguł porządkuje sytuację:

  • domyślnie wyłączone przekierowania w sshd_config:
    AllowTcpForwarding no
    PermitTunnel no
    GatewayPorts no
  • włączanie AllowTcpForwarding tylko tam, gdzie to jest realnie potrzebne (np. na bastionie dla konkretnych kont),
  • dla kont technicznych – ograniczanie port forwardingów przez opcje w authorized_keys (no-port-forwarding, albo odwrotnie – przyzwolenia tylko na konkretny przypadek),
  • jasne zasady: kto może robić tunele i do czego. „Bo wygodniej” nie jest wystarczającym uzasadnieniem dla stałego -D 1080 na produkcję.

Jeśli przekierowania są dopuszczone, dobrze jest mieć chociaż minimalny monitoring ruchu: nagły wysyp tuneli SSH z IP developera w sobotę wieczorem to sygnał, że coś dzieje się poza zwykłym rytmem pracy.

SSH jako „narzędzie do wszystkiego”

Przy skomplikowanych środowiskach pojawiają się mniej oczywiste zastosowania:

  • tunnele pod backup i replikację baz,
  • przepychanie snmp/sysloga przez zaszyfrowane kanały,
  • dostęp do zamkniętych paneli administracyjnych (np. IPMI za NAT-em).

Technicznie działa, ale każdy taki przypadek to kolejny fragment architektury bezpieczeństwa „ukryty” w czyimś ~/.ssh/config. Lepiej je udokumentować i, jeśli się da, przenieść na bardziej przejrzyste mechanizmy (VPN, dedykowane tunele, rozwiązania warstwy sieciowej), zamiast budować całą infrastrukturę na zasadzie „bo Janek ma fajny skrypt z ssh -L”.

Polityka kluczy SSH i zarządzanie ich cyklem życia

Sam mechanizm kluczy jest świetny. Problemy zaczynają się, gdy organizacja ma już kilkadziesiąt osób, kilkaset serwerów, a nikt nie wie, które klucze są jeszcze używane, a które dawno należało wyrzucić.

Standard nazewnictwa i metadane

Chaos zaczyna się od nazw plików typu id_rsa, id_rsa2, klucz_stary. Dużo lepiej od początku przyjąć prosty wzorzec:

id_ed25519_{imię}.{nazwisko}_{rok}{miesiąc}

W komentarzu klucza publicznego warto zawrzeć coś, co pomoże w przyszłości:

ssh-ed25519 AAAAC3... admin.nowak@firma.local 2024-03 laptop-służbowy

Po kilku latach różnica między „klucz_1.pub” a powyższą notacją jest mniej więcej jak między plikiem „final_v3_ostateczne.docx” a dobrze opisanym repozytorium Git.

Rotacja i wygaszanie kluczy

Większość ludzi generuje klucz raz na dekadę i liczy, że dożyje z nim emerytury. Tymczasem sensowna polityka rotacji może wyglądać tak:

  • klucze osobiste – rotacja co 1–2 lata lub przy zmianie stanowiska,
  • klucze techniczne (CI/CD, automaty) – krótszy cykl: np. raz do roku lub przy każdej większej zmianie zespołu,
  • natychmiastowa wymiana po incydentach: zgubiony laptop, podejrzane logowanie, wyciek repozytorium z kluczami.

Rotację da się robić bez niedzielnych maratonów na produkcji. Klucz jest dodawany do authorized_keys równolegle ze starym, testowany, a dopiero potem stary wpis jest usuwany w kolejnym rollout-cie konfiguracji.

Inwentaryzacja kluczy na serwerach

Prędzej czy później ktoś zapyta: „ile kluczy ma dostęp do produkcji i do kogo należą?”. Gdy authorized_keys są zarządzane kodem, odpowiedź leży w repozytorium. W innych przypadkach trzeba robić inwentaryzację na żywym organizmie.

# Prosty przegląd kluczy na wszystkich kontach użytkowników
find /home /root -name "authorized_keys" -print -exec cat {} ;

To oczywiście dopiero początek. Klucze dobrze jest wrzucić do jakiejś bazy (choćby arkusza), dopisać przypisanie do osoby/procesu i status („aktywny”, „do usunięcia przy najbliższym deployu”). Raz zrobiona inwentaryzacja, jeśli połączona z prostym procesem dodawania/usuwania kluczy, przestaje być bolesnym corocznym rytuałem audytowym.

SSH i compliance: jak nie zwariować przy audytach

Bez względu na to, czy na drzwiach wisi ISO, PCI, SOC czy inny skrót, logowanie do serwerów będzie jedną z pierwszych rzeczy, które interesują audytorów. Dobrze ułożony SSH jest tu atutem, a nie źródłem wstydu.

Rozdzielenie ról i obowiązków

Model typu „wszyscy są rootem” może i jest wygodny na trzech serwerach, ale przy pierwszym audycie przeradza się w długie milczenie na pytanie „kto to zmienił i dlaczego?”. Bezpieczniej jest:

  • mieć osobne konta użytkowników, nawet jeśli wszyscy używają sudo do roota,
  • ograniczyć możliwość bezpośredniego logowania na konto root (klasyczne PermitRootLogin no),
  • zadbać o czytelne granice: kto ma dostęp na produkcję, kto tylko na staging, a kto wyłącznie do środowisk testowych,
  • opisać te zasady w jednym miejscu (runbook, wiki, repo z infrastrukturą), zamiast przechowywać je w pamięci najstarszego admina.

Dla audytora rozdzielenie ról to nie akademicka teoria, tylko odpowiedź na pytanie: „czy jedna osoba może sama wprowadzić krytyczną zmianę i od razu ją wdrożyć na produkcję, bez żadnej kontroli?”. Jeśli tak – prędzej czy później ktoś z tego „skorzysta”, choćby w dobrej wierze, gasząc pożar o 2:00 w nocy.

Ścieżka audytu i logi

Drugi ulubiony temat audytów to możliwość odtworzenia, kto co zrobił. Przy SSH najprościej osiągnąć to przez kombinację:

  • indywidualnych kont użytkowników (brak wspólnych loginów typu admin),
  • sudo z logowaniem poleceń (np. do sysloga lub osobnych plików),
  • centralnej zbiórki logów (rsyslog, journald remote, SIEM).

Do tego dochodzą logi samego SSH. Wystarczy podnieść poziom szczegółowości w konfiguracji:

LogLevel VERBOSE

Dzięki temu w logach pojawią się informacje o użytych kluczach, kanałach, tunelach. Przy incydencie to często jedyna droga, żeby zrozumieć, jak atakujący poruszał się po infrastrukturze. W normalnej pracy te logi są trochę jak czujniki dymu – przez większość czasu nikt na nie patrzy, ale gdy zacznie się palić, nagle wszyscy są zadowoleni, że jednak wisiały na ścianie.

Dostęp tymczasowy i wyjątki od reguł

Żadna polityka nie wytrzyma zderzenia z rzeczywistością w 100%. Zdarzają się sytuacje, gdy trzeba kogoś wpuścić „na chwilę” na serwer produkcyjny: konsultanta, dostawcę, inny zespół. Kluczowe jest, żeby ta „chwila” nie trwała potem trzech lat.

Dobrze działa prosta zasada: każdy dostęp tymczasowy ma datę końca i właściciela. W praktyce oznacza to chociażby adnotację przy kluczu w authorized_keys albo w systemie zarządzania zmianą: kto poprosił, kto zatwierdził, do kiedy dostęp ma działać. W środowiskach z IaaC można to dodatkowo wymusić, trzymając takie klucze w osobnych plikach/rolach, które łatwo „wyciąć” przy najbliższym deployu.

Wyjątki od polityki są nieuniknione, ale każdy wyjątek, który nie jest nigdzie zapisany, automatycznie zamienia się w stałe wejście awaryjne. Po roku nikt już nie pamięta, skąd się wziął klucz podpisany „test_temp_01” – a jednak dalej działa na produkcji.

Łączenie bezpieczeństwa z wygodą pracy

Najbardziej odporne na obalenie są te zasady, które ludziom nie przeszkadzają w codziennej robocie. SSH daje sporo narzędzi, żeby to połączyć: od ~/.ssh/config u użytkowników, przez bastiony i Single Sign-On, po centralne systemy zarządzania dostępem. Im mniej „ręcznej gimnastyki” przy logowaniu, tym mniejsza pokusa, żeby politykę obchodzić skrótami i „tym jednym dodatkowym kluczem na wszelki wypadek”.

Dobrze przygotowane logowanie do serwera przestaje być wtedy magicznym rytuałem adminów, a staje się zwykłym elementem infrastruktury – jak sieć czy backupy. Działa przewidywalnie, nie zaskakuje w kryzysie, a gdy przychodzi nowa osoba do zespołu, procedura dodania jej klucza jest tak nudna i oczywista, jak powinna być każda solidna procedura bezpieczeństwa.

Najczęściej zadawane pytania (FAQ)

Jak najlepiej zabezpieczyć logowanie do serwera SSH zaraz po uruchomieniu VPS?

Na świeżym VPS pierwsze minuty to wyścig: Ty konfigurujesz, boty już próbują się włamać. Minimalny zestaw kroków na start to:

  • wygenerowanie klucza SSH na swoim komputerze i wgranie klucza publicznego na serwer,
  • wyłączenie logowania hasłem w /etc/ssh/sshd_config (PasswordAuthentication no),
  • zablokowanie bezpośredniego logowania na root (np. PermitRootLogin no),
  • dodanie firewall (ufw, nftables, iptables) i ograniczenie dostępu do portu SSH,
  • instalacja narzędzia typu Fail2ban do blokowania bruteforce.

Takie minimum zmienia Twoją maszynę z „łatwego łupu” w cel, którego większość automatycznych skryptów po prostu odpuści.

Czy samo silne hasło do SSH wystarczy, czy muszę używać kluczy?

Silne hasło jest lepsze niż słabe, ale klucz SSH z passphrase i tak wygrywa. Hasło można zgadnąć, przechwycić w phishingu albo wtłuc brute-force’em. Klucz prywatny jest dużo trudniejszy do złamania, a dodatkowa passphrase dokłada kolejną warstwę ochrony.

Przy logowaniu kluczem serwer nie dostaje żadnego hasła – weryfikuje tylko, czy naprawdę posiadasz właściwy klucz prywatny odpowiadający kluczowi publicznemu z ~/.ssh/authorized_keys. Z punktu widzenia bezpieczeństwa przejście z hasła na klucz to największy „skok jakościowy”, jaki możesz zrobić w kilka minut.

Jak wyłączyć logowanie na root przez SSH i nadal mieć pełne uprawnienia?

Bezpośrednie logowanie na root jest wygodne, ale daje atakującemu pół roboty z głowy – zna już login, zostaje mu tylko hasło lub klucz. Lepszy schemat to zwykły użytkownik + sudo.

W praktyce wygląda to tak: tworzysz użytkownika, dodajesz go do grupy sudo, testujesz logowanie, a potem w /etc/ssh/sshd_config ustawiasz PermitRootLogin no. Od tej pory logujesz się jako zwykły user i w razie potrzeby podnosisz uprawnienia komendą sudo. Funkcjonalnie nie tracisz prawie nic, a zyskujesz sporo na bezpieczeństwie i czytelności logów.

Czy zmiana domyślnego portu SSH z 22 na inny ma sens?

Zmiana portu nie jest „pancernym” zabezpieczeniem, ale działa jak filtr na najprostsze boty. Większość skanerów wali po porcie 22 i kilkunastu popularnych; jeśli SSH słucha np. na 2222, logi z prób ataku zwykle mocno chudną.

Nadal trzeba jednak:

  • użyć kluczy zamiast haseł,
  • ustawić firewall,
  • ograniczyć, kto może się logować (np. dyrektywą AllowUsers lub AllowGroups).

Zmiana portu to raczej „zamurowanie oczywistych drzwi i wstawienie wejścia z boku”, a nie cały system alarmowy.

Co to jest plik known_hosts i czy mogę go bezpiecznie skasować?

~/.ssh/known_hosts to Twoja „książka adresowa” z odciskami palca serwerów SSH, z którymi się łączyłeś. Dzięki temu klient SSH przy kolejnym połączeniu sprawdza, czy host key serwera jest ten sam; jeśli się zmieni, dostajesz ostrzeżenie, które może oznaczać np. atak MITM albo reinstalację systemu.

Można usunąć pojedynczy wpis (np. ssh-keygen -R nazwa_hosta), gdy serwer faktycznie zmienił klucz. Skasowanie całego pliku jest możliwe, ale tracisz wtedy ochronę przed podszywaniem się pod znane hosty – SSH potraktuje każdy serwer jak „nowy” i nie wykryje, że coś jest nie tak.

Jak rozpoznać w logach, że ktoś próbuje się włamać przez SSH?

Na typowym serwerze Linuxa logi SSH znajdziesz w /var/log/auth.log (Debian/Ubuntu) lub /var/log/secure (CentOS/RHEL). O atakach brute-force świadczą serie wpisów w stylu:

  • Failed password for invalid user admin from ...
  • Failed password for root from ...
  • Invalid user test from ...

Jeżeli widzisz setki takich prób z różnych adresów IP, nie jesteś „szczególnie interesujący” – po prostu trafiłeś na globalny szum skanerów.

Zdrowa reakcja to nie panika, tylko: włączenie logowania kluczem, firewall, Fail2ban i ograniczenie listy dozwolonych użytkowników. Po wdrożeniu tych kroków taki szum staje się głównie ciekawostką, a nie realnym zagrożeniem.

Co zrobić, gdy podejrzewam, że klucz prywatny SSH wyciekł?

Jeśli jest choć cień podejrzenia, że ktoś mógł skopiować Twój klucz prywatny (zgubiony laptop, przejęty backup, zainfekowana stacja robocza), trzeba działać jak przy zgubionym fizycznym kluczu do mieszkania.

  • Natychmiast usuń ten klucz z ~/.ssh/authorized_keys na wszystkich serwerach.
  • Wygeneruj nową parę kluczy z mocną passphrase.
  • Przejrzyj logi SSH pod kątem nietypowych logowań (godziny, IP, użytkownicy).
  • Jeśli to maszyna produkcyjna – rozważ dodatkowy przegląd systemu: procesy, crony, nowe konta użytkowników.

Klucz prywatny z passphrase jest trudniejszy do wykorzystania przez atakującego, ale nie traktuj tego jako wymówki, żeby „nic nie robić i liczyć na szczęście”.

Najważniejsze wnioski

  • Logowanie SSH jest główną bramą do serwera – kto zdobędzie shella (zwłaszcza z sudo/root), ten de facto przejmuje maszynę i może robić z nią, co chce.
  • Różnica między serwerem „działa” a „bezpiecznym” to m.in. logowanie kluczem zamiast hasła, wyłączony root przez SSH, ograniczenie listy dozwolonych użytkowników, firewall, Fail2ban oraz stały monitoring logów.
  • Ataki na SSH są masowe i automatyczne – świeży VPS z otwartym portem 22 i logowaniem hasłem po kilku minutach ma w logach setki prób bruteforce, nawet jeśli nikogo „nie interesuje” konkretny serwer.
  • Najczęstsze wektory włamań to słabe/zgadywalne hasła, logowanie na standardowym porcie 22, włączony PermitRootLogin oraz wyciek niezabezpieczonego klucza prywatnego z laptopa lub backupu w chmurze.
  • Po udanym włamaniu atakujący najpierw utrwala dostęp (klucze, ukryte konta, cron), zaciera ślady, a potem wykorzystuje serwer jako koparkę, element botnetu lub trampolinę do dalszych ataków w sieci.
  • Skutki włamania na serwer z aplikacją produkcyjną to nie tylko „spowolniony VPS”, ale realne ryzyko wycieku danych, problemów z RODO i mocno nadszarpniętej reputacji firmy.
  • SSH jest dziś standardem, bo szyfruje zarówno dane, jak i dane logowania, w przeciwieństwie do Telnetu i klasycznego FTP – używanie tych starych protokołów to jak zostawienie hasła na kartce przyklejonej do monitora.
Poprzedni artykułGadżety, które poprawiają postawę i zdrowie podczas pracy siedzącej
Następny artykułJak wyznaczać sobie cele zawodowe w IT i je osiągać
Kazimierz Kaźmierczak

Kazimierz Kaźmierczak to doświadczony webmaster i programista PHP, który pomaga przekuwać pomysły na stabilne, szybkie i bezpieczne rozwiązania. Na porady-it.pl publikuje praktyczne materiały o tworzeniu skryptów, pracy z bazami danych, automatyzacji zadań (cron, importy, integracje API) oraz poprawie jakości kodu w codziennych projektach. Szczególnie ceni porządek: czytelną architekturę, sensowną obsługę błędów i zabezpieczenia, które chronią stronę przed najczęstszymi atakami. Jego poradniki są nastawione na wdrożenie — krótkie kroki, konkretne przykłady i wskazówki „co zrobić, gdy coś nie działa”.

Kontakt: kazimierz_kazmierczak@porady-it.pl