Przejdź do treści
Lekcja 76 z 117

Kurs › Moduł 8 › Lekcja 76

SSH od środka

⏱ ok. 11 min czytania· moduł 8: Sieć

Po tej lekcji: kursant loguje się kluczem ed25519 zamiast hasłem, rozumie authorized_keys i uprawnienia, skraca sobie życie plikiem ~/.ssh/config, bezpiecznie zmienia sshd_config, kopiuje pliki przez scp i sftp oraz buduje tunele -L, -R i skoki -J.

Wstęp

Masz czterdzieści serwerów. Do każdego inne hasło, bo przecież jesteś odpowiedzialny. Rano wpisujesz hasło dwadzieścia razy, po południu kolejne dwadzieścia, a wieczorem śni Ci się Permission denied, please try again.

Admini tak nie pracują. Wpisują ssh web i są w środku. Bez hasła, bez adresu, bez nazwy użytkownika. A do bazy, która stoi za dwiema zaporami, wchodzą jedną komendą. Dziś zrobimy z Ciebie takiego admina: klucze, plik konfiguracyjny klienta, bezpieczne ustawienia serwera i tunele, czyli prywatne korytarze w cudzej sieci.

Klucz zamiast hasła

Pamiętasz analogię z początku kursu? Klucz SSH to kłódka na serwerze i kluczyk w kieszeni. Robisz komplet: kłódkę, czyli klucz publiczny, i kluczyk, czyli klucz prywatny. Kłódkę możesz rozdać całemu światu, przypiąć na każdym serwerze, wywiesić na płocie. Kluczyka nie dajesz nikomu, nigdy. Serwer z Twoją kłódką wpuszcza każdego, kto ją otworzy, czyli tylko Ciebie.

Robimy komplet, w nowoczesnym typie ed25519:

ssh-keygen -t ed25519 -C "marek@laptop"
Generating public/private ed25519 key pair.
Your identification has been saved in /home/marek/.ssh/id_ed25519
Your public key has been saved in /home/marek/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:sAOgsDc1Sc3ox1fzQKd0IVkVNCQiHwFmKD4UFX840xY marek@laptop

Po drodze pyta o hasło do klucza, passphrase. Ustaw je. Bez niego ktoś, kto ukradnie Ci laptopa, ma kluczyki do wszystkich czterdziestu serwerów. Z nim ma bezużyteczny plik. -C to tylko komentarz, żeby za rok wiedzieć, czyj to klucz.

ls -l ~/.ssh
-rw------- 1 marek marek 399 Nov  3 15:31 id_ed25519
-rw-r--r-- 1 marek marek  94 Nov  3 15:31 id_ed25519.pub

.pub to kłódka, jedna linijka tekstu. Bez .pub to kluczyk, uprawnienia 600, tylko dla Ciebie.

Przypięcie kłódki: ssh-copy-id

Kłódkę trzeba zawiesić na serwerze. Ręcznie się da, ale po co:

ssh-copy-id kuba@10.0.0.10
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/marek/.ssh/id_ed25519.pub"
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'kuba@10.0.0.10'"
and check to make sure that only the key(s) you wanted were added.

Ostatni raz podajesz hasło. ssh-copy-id łączy się, dopisuje Twoją kłódkę na koniec pliku ~/.ssh/authorized_keys użytkownika kuba i ustawia uprawnienia. Sprawdzamy:

ssh kuba@10.0.0.10 "ls -la ~/.ssh; cat ~/.ssh/authorized_keys"
drwx------ 2 kuba kuba 4096 Nov  3 16:31 .
-rw------- 1 kuba kuba   94 Nov  3 16:31 authorized_keys
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDjeJSn9OBJZKqp9UUGFYUNzgsuROI6wFL7LuKwgk5SW marek@laptop

Bez hasła. authorized_keys to lista kłódek, które ten użytkownik akceptuje, jedna w linijce. Odebranie komuś dostępu? Usuwasz jego linijkę.

Przy pierwszym połączeniu z nowym serwerem zobaczysz jeszcze to:

The authenticity of host '10.0.0.10 (10.0.0.10)' can't be established.
ED25519 key fingerprint is SHA256:eSWaHBKjUKaSPQXK17i6p/rlkIhJA0ZyNnny93ktJTI.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

Teraz to serwer pokazuje Ci swój dowód osobisty. Porządny admin porównuje odcisk z tym, co mówi sam serwer, na przykład z konsoli:

ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
256 SHA256:eSWaHBKjUKaSPQXK17i6p/rlkIhJA0ZyNnny93ktJTI root@vm (ED25519)

Zgadza się, więc yes. Klient zapisze ten odcisk w ~/.ssh/known_hosts i przy każdym kolejnym połączeniu będzie sprawdzał, czy serwer to wciąż ten sam serwer.

Agent, czyli hasło do klucza raz na sesję

Hasło do klucza przy każdym ssh szybko znudzi. Od tego jest ssh-agent: pomocnik, który trzyma odblokowany klucz w pamięci. Na pulpicie zwykle działa sam, na serwerze uruchamiasz go ręcznie:

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/klucz_z_haslem
ssh-add -l
Agent pid 32084
Identity added: /home/marek/.ssh/klucz_z_haslem (test)
256 SHA256:njvnFuoz+mKyNneboFR0xnx+VziBn4DRXPwXIUooxtY test (ED25519)

Hasło wpisujesz raz, przy ssh-add. [macOS] Na Macu agent działa od razu i potrafi trzymać hasło w pęku kluczy systemu.

~/.ssh/config: książka adresowa

Zamiast ssh -i ~/.ssh/id_ed25519 -p 22 kuba@10.0.0.10 robimy wpis w ~/.ssh/config:

Host web
    HostName 10.0.0.10
    User kuba
    IdentityFile ~/.ssh/id_ed25519

Host baza
    HostName 10.1.0.2
    User kuba
    ProxyJump web
chmod 600 ~/.ssh/config
ssh web hostname -I
10.0.0.10 10.1.0.1

Host to Twoja ksywka dla serwera. HostName to prawdziwy adres. User, Port, IdentityFile mówią same za siebie. Ksywka działa wszędzie: w ssh, scp, sftp, rsync. Co klient naprawdę z tego zrozumiał, pokaże ssh -G:

ssh -G baza | grep -E '^(hostname|user|port|proxyjump) '
user kuba
hostname 10.1.0.2
port 22
proxyjump web

Skok przez serwer: -J

Wpis baza ma ProxyJump web. Baza stoi w sieci, do której laptop nie ma drogi:

ssh -o ConnectTimeout=3 kuba@10.1.0.2
ssh: connect to host 10.1.0.2 port 22: Network is unreachable

A przez skok:

ssh baza 'echo jestem na: $(hostname -I)'
ssh -J web kuba@10.1.0.2 'hostname -I'
jestem na: 10.1.0.2
10.1.0.2

-J, jump, to wejście do bazy przez serwer web, który robi za przedsionek. Wygodne, a przy tym bezpieczne: Twój kluczyk nigdy nie ląduje na web. Przedsionek tylko przekazuje zaszyfrowaną rozmowę dalej, jak kurier, który niesie zaklejoną kopertę.

scp i sftp: przenoszenie plików

scp raport.txt web:/tmp/              # z laptopa na serwer
scp -r projekt web:                   # cały folder do domowego na serwerze
scp web:/etc/hostname ./              # z serwera do laptopa

Dwukropek to granica: po lewej nazwa serwera, po prawej ścieżka na nim. Sam dwukropek bez ścieżki to folder domowy. Zapomnisz dwukropka, a scp grzecznie skopiuje plik lokalnie do pliku o nazwie web. Brawo, właśnie zrobiłeś kopię raportu obok raportu.

sftp to rozmowa z serwerem plików, z komendami jak w terminalu:

sftp web
sftp> pwd
Remote working directory: /home/kuba
sftp> ls
projekt
sftp> put raport.txt
sftp> get /etc/os-release
sftp> bye

put wysyła, get pobiera, ls i cd działają na serwerze, lls i lcd u Ciebie. Do dużych folderów lepszy jest rsync, o nim w następnej lekcji.

Tunele: -L i -R

Teraz magia. Na bazie danych działa panel, który słucha tylko na 127.0.0.1:5432. Z zewnątrz nie dojdziesz, i bardzo dobrze. Ale Ty musisz do niego zajrzeć.

Tunel lokalny, -L: „otwórz u mnie port 15432, a wszystko, co do niego wpadnie, przenieś przez SSH i wypuść na serwerze do localhost:5432”.

ssh -f -N -L 15432:localhost:5432 baza
curl http://localhost:15432/
panel bazy (tylko localhost)

To jak prywatny korytarz wykopany pod ulicą: wchodzisz drzwiami w swojej piwnicy, wychodzisz w piwnicy serwera. -N znaczy „nie otwieraj powłoki, tylko tunel”, -f znaczy „schowaj się w tło”. Ważne: localhost w tej komendzie oznacza localhost serwera, nie Twojego laptopa.

Tunel odwrotny, -R, działa w drugą stronę. Na laptopie masz aplikację na porcie 3000 i chcesz ją pokazać na serwerze:

ssh -f -N -R 8000:localhost:3000 web
# na serwerze:
curl http://localhost:8000/
aplikacja z laptopa

Serwer otworzył u siebie port 8000, który prowadzi do Twojego laptopa. Domyślnie tylko na 127.0.0.1 serwera, żeby nie wystawić laptopa całemu światu.

sshd_config: drzwi od strony serwera

Do tej pory był klient. Serwer, czyli usługa sshd, ma swój plik /etc/ssh/sshd_config, a na nowych systemach dodatkowo folder /etc/ssh/sshd_config.d/. Zamiast grzebać w głównym pliku, robisz własny:

# /etc/ssh/sshd_config.d/50-hardening.conf
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AllowUsers kuba
MaxAuthTries 3

Uwaga, teraz najważniejsza zasada lekcji. Zanim wyłączysz hasła, sprawdź, że logujesz się kluczem. Zanim zrestartujesz usługę, sprawdź składnię. I trzymaj drugą sesję SSH otwartą, dopóki nowa nie wejdzie.

sudo sshd -t

Cisza znaczy „w porządku”. Literówka wygląda tak:

/etc/ssh/sshd_config.d/50-hardening.conf: line 2: Bad configuration option: PasswordAuthentcation
/etc/ssh/sshd_config.d/50-hardening.conf: terminating, 1 bad configuration options

A sshd -T wypisze ustawienia, które naprawdę obowiązują:

sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|allowusers|maxauthtries) '
maxauthtries 3
permitrootlogin no
passwordauthentication no
allowusers kuba

Dopiero teraz przeładowanie:

sudo systemctl reload ssh     # [Ubuntu]
sudo systemctl reload sshd    # [RHEL]

Tak, usługa nazywa się inaczej. [Ubuntu] Na Ubuntu 24.04 SSH startuje przez gniazdo systemd (ssh.socket). Zmiana portu wymaga więc sudo systemctl daemon-reload i sudo systemctl restart ssh.socket. [RHEL] Na Red Hacie zmiana portu to jeszcze zgoda SELinuksa (semanage port -a -t ssh_port_t -p tcp 2222) i otwarcie portu w firewalld. Logi logowań: /var/log/auth.log [Ubuntu] i /var/log/secure [RHEL], albo journalctl -u ssh / journalctl -u sshd.

Jak to działa pod spodem

Połączenie SSH to trzy etapy. Najpierw klient i serwer uzgadniają wspólny tajny klucz sesji metodą wymiany kluczy, więc dalej wszystko leci zaszyfrowane, nawet hasło. Potem serwer udowadnia, kim jest, kluczem hosta, a klient porównuje go z known_hosts. Na końcu klient udowadnia, kim jest. Przy kluczu wygląda to tak:

ssh -v web true 2>&1 | grep -E 'Offering|accepts|Authenticated'
debug1: Offering public key: /home/marek/.ssh/id_ed25519 ED25519 SHA256:sAOgsDc1Sc3ox1fzQKd0IVkVNCQiHwFmKD4UFX840xY explicit
debug1: Server accepts key: /home/marek/.ssh/id_ed25519 ED25519 SHA256:sAOgsDc1Sc3ox1fzQKd0IVkVNCQiHwFmKD4UFX840xY explicit
Authenticated to 10.0.0.10 ([10.0.0.10]:22) using "publickey".

Klient mówi: „mam kłódkę taką i taką”. Serwer sprawdza authorized_keys: „znam, udowodnij”. Klient podpisuje kluczem prywatnym dane z tej konkretnej sesji, serwer sprawdza podpis kłódką. Kluczyk prywatny nigdy nie opuszcza laptopa. Wysłany zostaje tylko podpis, ważny jeden raz.

sshd jest przy tym podejrzliwy. Opcja StrictModes, domyślnie włączona, odrzuca klucze, jeśli authorized_keys, folder .ssh albo folder domowy mogą być zmieniane przez kogoś innego. Bo jak ktoś obcy może dopisać kłódkę, to cała zabawa nie ma sensu.

Ściąga flag

KomendaFlagaCo robi
ssh-keygen-t ed25519 -C "opis"tworzy parę kluczy
ssh-keygen-lf plik.pubpokazuje odcisk klucza
ssh-keygen-R hostusuwa host z known_hosts
ssh-keygen-p -f kluczzmienia hasło do klucza
ssh-copy-iduser@hostdopisuje kłódkę do authorized_keys
ssh-p PORT / -i KLUCZport / wybrany klucz
ssh-vgadatliwy tryb do diagnozy
ssh-J przedsionekskok przez inny serwer
ssh-L lok:cel:porttunel lokalny
ssh-R zdal:cel:porttunel odwrotny
ssh-N -fbez powłoki, w tle
ssh-G hostpokazuje gotową konfigurację klienta
ssh-addKLUCZ / -ldodaje klucz do agenta / wypisuje
scp-r / -P PORTfoldery / port (duże P!)
sftpput, get, ls, llswysyłanie, pobieranie, listy
sshd-t / -Ttest składni / gotowe ustawienia

⚠ Typowy błąd

Klucz jest, ssh-copy-id się udało, a serwer dalej mówi:

kuba@10.0.0.10: Permission denied (publickey,password).

Najczęściej winne są uprawnienia. Po stronie klienta ssh sam krzyczy:

@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
Permissions 0644 for '/home/marek/.ssh/id_ed25519' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.

Po stronie serwera jest ciszej. Klient widzi tylko Permission denied, a prawda leży w logu serwera:

Authentication refused: bad ownership or modes for file /home/kuba/.ssh/authorized_keys
Authentication refused: bad ownership or modes for directory /home/kuba

Pierwsza linijka: ktoś dał authorized_keys uprawnienia 666. Druga: ktoś „naprawił problem” przez chmod 777 na folderze domowym. Recepta:

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys ~/.ssh/id_ed25519
chmod go-w ~

I drugi klasyk, który wygląda jak koniec świata:

@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Offending ED25519 key in /home/marek/.ssh/known_hosts:4
  remove with:
  ssh-keygen -f '/home/marek/.ssh/known_hosts' -R '10.1.0.2'

Serwer pokazał inny dowód niż ostatnio. Najczęściej dlatego, że ktoś przeinstalował maszynę i dostała nowe klucze hosta. Czasem dlatego, że ktoś naprawdę się podszywa. Upewnij się u źródła (konsola, kolega, dokumentacja), a dopiero potem usuń stary wpis komendą, którą ssh sam podpowiada.

✎ Zadanie

Na dwóch maszynach wirtualnych: wygeneruj klucz ed25519 z hasłem, wgraj go ssh-copy-id na drugą maszynę i zrób wpis w ~/.ssh/config, żeby ssh vm2 wchodziło bez podawania adresu i użytkownika. Potem na serwerze w pliku /etc/ssh/sshd_config.d/50-hardening.conf wyłącz logowanie hasłem i logowanie roota, sprawdź sshd -t oraz sshd -T, przeładuj usługę, mając drugą sesję otwartą, i udowodnij, że ssh -o PubkeyAuthentication=no vm2 zostaje odrzucone. Na koniec zrób tunel -L, którym otworzysz u siebie stronę z serwera słuchającą tylko na 127.0.0.1.

Pokaż rozwiązanie

Klucz z hasłem i wgranie na drugą maszynę:

ssh-keygen -t ed25519 -C "eryk@laptop"
ssh-copy-id kuba@10.0.0.10

Wpis w ~/.ssh/config:

Host vm2
    HostName 10.0.0.10
    User kuba

Na serwerze, w drugim oknie, z pierwszą sesją otwartą:

printf 'PermitRootLogin no\nPasswordAuthentication no\n' | sudo tee /etc/ssh/sshd_config.d/50-hardening.conf
sudo sshd -t && sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication) '
sudo systemctl reload ssh      # na RHEL: sshd
permitrootlogin no
passwordauthentication no

I dowód, że bez klucza nie wejdziesz, a z kluczem tak:

ssh -o PubkeyAuthentication=no vm2
ssh vm2 echo kluczem-ok
kuba@10.0.0.10: Permission denied (publickey).
kluczem-ok

W nawiasie zostało już tylko publickey. Serwer nawet nie proponuje hasła, czyli drzwi na szyfr zamurowane. Na koniec tunel do strony, która słucha tylko lokalnie:

ssh -f -N -L 18080:localhost:80 vm2
curl -I http://localhost:18080/

Jeśli sshd -T pokazał u Ciebie passwordauthentication yes, mimo że wpisałeś no, to masz w sshd_config.d plik o niższym numerze, który wygrywa. Pamiętasz: pierwsza wartość wygrywa.

Co dalej

Kluczem już się logujesz, pliki pojedynczo kopiujesz. W następnej lekcji pakujemy całe foldery: tar, gzip, xz, zip, a potem rsync, który kopiuje przez SSH tylko to, co się zmieniło, i to on będzie Twoim narzędziem do kopii zapasowych.