# SSH do hloubky

SSH je nejpoužívanější kryptografický protokol, který kdy budeš obsluhovat ručně. Zároveň je to jediná cesta na tvůj server, takže se vyplatí vědět, co se při připojení doopravdy děje — ne proto, aby to fungovalo, ale proto, abys věděl, co znamená, když to nefunguje nebo když se něco změní.

Základní zabezpečení je na [zabezpečení serveru](Zabezpeceni-serveru). Tahle stránka jde hlouběji: protokol, ověřování, certifikáty, tunely a věci, které dělá málokdo a měl by.

Předpokládá [veřejný a soukromý klíč](Verejny-a-soukromy-klic) a [Diffie-Hellmana](Diffie-Hellman).

## Co SSH doopravdy je

Není to „vzdálený terminál". Terminál je jen jedno z použití. SSH je **šifrovaný multiplexovaný transport**, do kterého se dá zabalit cokoliv:

- interaktivní shell
- jednorázový příkaz
- přenos souborů (SFTP, `rsync`, `scp`)
- přesměrované TCP porty oběma směry
- SOCKS proxy
- unixové sokety (třeba `docker.sock`)
- pomocné kanály — přeposlaný agent, X11

Všechno tohle jede **jedním TCP spojením** a rozlišuje se to čísly kanálů. Proto můžeš v jednom sezení mít shell a zároveň tunel na databázi.

Protokol je popsaný v RFC 4251 až 4254 z roku 2006 a od té doby se změnily hlavně algoritmy, ne struktura.

## Tři vrstvy

| Vrstva | Co dělá |
|---|---|
| **Transportní** | výměna klíčů, ověření serveru, šifrování, integrita, komprese |
| **Ověření uživatele** | dokazuje, kdo jsi — klíčem, heslem, tokenem |
| **Spojová** | kanály, tunely, shell, přenos souborů |

Pořadí je podstatné: **server se ověřuje první**. Než pošleš jediný znak hesla, musíš už vědět, s kým mluvíš. Kdyby to bylo obráceně, útočník uprostřed by hesla sbíral bez námahy.

## Průběh připojení

```mermaid
sequenceDiagram
    participant K as Klient
    participant S as Server
    Note over K,S: TCP spojení na port 22
    K->>S: SSH-2.0-OpenSSH_9.6
    S->>K: SSH-2.0-OpenSSH_9.6
    Note over K,S: nešifrovaně, oba znají verzi protistrany
    K->>S: KEXINIT — seznam algoritmů, které umím
    S->>K: KEXINIT — seznam algoritmů, které umím
    Note over K,S: z průniku se vybere první shoda podle pořadí klienta
    K->>S: veřejná část dočasného klíče
    S->>K: veřejná část + host key + podpis
    K->>K: ověří host key proti known_hosts
    K->>K: ověří podpis exchange hashe
    Note over K,S: NEWKEYS — dál je vše šifrované
    K->>S: chci se přihlásit jako martin, metodou publickey
    S->>K: dobře, zkus to
    K->>S: podpis session ID svým soukromým klíčem
    S->>K: SUCCESS
    Note over K,S: otevírají se kanály — shell, tunely
```

### Pozdrav

První dva řádky jdou **otevřeně** a jsou to jediná data, která si kdokoliv po cestě přečte:

```bash
nc server 22
# SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13.5
```

Odtud pochází návod „skryj verzi SSH". Nemá to smysl — verze je nutná pro obcházení historických chyb v protokolu a útočník tvou verzi stejně pozná podle nabídnutých algoritmů. Naopak: ten řetězec se **započítává do podpisu**, takže ho po cestě nikdo nemůže změnit, aniž by spojení selhalo.

### Vyjednání algoritmů

Obě strany pošlou seznamy toho, co umí, pro každou kategorii zvlášť: výměna klíčů, host key, šifra pro každý směr, MAC, komprese. **Vybere se první položka klientova seznamu, kterou umí i server.** Pořadí na straně klienta tedy rozhoduje.

Co umí tvůj klient:

```bash
ssh -Q kex        # výměna klíčů
ssh -Q key        # typy host keys a klíčů uživatele
ssh -Q cipher     # šifry
ssh -Q mac        # ověřování integrity
```

Co nabízí konkrétní server:

```bash
nmap --script ssh2-enum-algos -p 22 server
ssh-audit server          # čitelnější, s hodnocením; pip install ssh-audit
```

### Výměna klíčů

Proběhne [Diffie-Hellman](Diffie-Hellman), dnes typicky na křivce X25519 v hybridu s postkvantovým mechanismem.

```bash
ssh -vv server 2>&1 | grep -iE "kex:|cipher:"
# kex: algorithm: mlkem768x25519-sha256
# kex: host key algorithm: ssh-ed25519
# kex: server->client cipher: chacha20-poly1305@openssh.com
```

Historie výchozích voleb v OpenSSH, ať víš, čemu odpovídá tvoje verze:

| Verze | Výchozí výměna klíčů |
|---|---|
| 6.5 (2014) | `curve25519-sha256` |
| 9.0 (2022) | `sntrup761x25519-sha512` — první postkvantová |
| 10.0 (2025) | `mlkem768x25519-sha256` — standardizovaná postkvantová |

Vedlejším produktem výměny je **exchange hash** — otisk úplně všeho, co dosud proběhlo: obou pozdravů, obou seznamů algoritmů, host keye a veřejných částí. První takový hash se stane **session ID** a platí po celé spojení.

Server ho podepíše svým **host keyem** a tím se ověří. Kdo host key nemá, nemůže se za server vydávat — může leda přeposílat pakety, ale pak podpis neodpovídá jeho vlastní výměně.

Tohle je zároveň důvod, proč SSH nepotřebuje certifikáty: **důvěru neseš ty ve svém `known_hosts`.**

### Šifrování provozu

Po `NEWKEYS` je vše šifrované. Z výsledku výměny se [odvodí](Sifrovani-zaklady#kdf-jak-se-z-jednoho-tajemství-udělá-víc-klíčů) šest klíčů: pro každý směr šifrovací klíč, počáteční vektor a klíč pro integritu.

Moderní volba je AEAD šifra, kde se integrita řeší uvnitř:

- `chacha20-poly1305@openssh.com` — výchozí, rychlá i bez hardwarové akcelerace
- `aes256-gcm@openssh.com`, `aes128-gcm@openssh.com`

Kde AEAD není, používá se **encrypt-then-MAC** (`hmac-sha2-256-etm@openssh.com`). Varianty bez `-etm` počítají MAC z otevřeného textu a jsou horší; CBC režimy nepoužívej vůbec.

## Host keys a known_hosts

Nejzajímavější část SSH, protože tady se řeší důvěra bez jakékoliv autority.

Server má vlastní dvojici klíčů, obvykle několik podle typu:

```bash
ls -1 /etc/ssh/ssh_host_*
# ssh_host_ecdsa_key       ssh_host_ecdsa_key.pub
# ssh_host_ed25519_key     ssh_host_ed25519_key.pub
# ssh_host_rsa_key         ssh_host_rsa_key.pub
```

### First use a known_hosts

Při prvním připojení klient klíč nezná:

```
The authenticity of host 'server (192.168.1.10)' can't be established.
ED25519 key fingerprint is SHA256:5N3YbQm...
Are you sure you want to continue connecting (yes/no/[fingerprint])?
```

Tomu se říká **trust on first use**. Buď je to server, nebo je to útočník — protokol to nerozliší a **rozhodnutí přenáší na tebe**. Když odpovíš `yes`, klíč se uloží do `~/.ssh/known_hosts` a od té chvíle se každé připojení kontroluje.

Skoro nikdo to neověřuje a je to největší reálná mezera v běžném používání SSH.

Jak to udělat pořádně, seřazeno od nejlepšího:

**1. Certifikáty.** Podepiš host keys vlastní CA a klient je přijme bez ptaní. Řeší to i výměnu klíčů při reinstalaci. Viz [níže](#ssh-certifikáty).

**2. Otisk z jiného kanálu.** Přečti si ho na konzoli serveru přes web poskytovatele, z výstupu cloud-initu nebo z hypervizoru:

```bash
# na serveru, přes konzoli
for f in /etc/ssh/ssh_host_*_key.pub; do ssh-keygen -lf "$f"; done
```

**3. SSHFP v DNS.** Otisk klíče jako DNS záznam:

```bash
ssh-keygen -r server.example.cz          # vygeneruje záznamy k vložení do zóny
ssh -o VerifyHostKeyDNS=yes server.example.cz
```

Užitečné, ale **skutečnou bezpečnost dává jen s DNSSEC** — jinak jsi jen přesunul důvěru z první odpovědi SSH na první odpověď DNS.

**4. Rozdat `known_hosts` centrálně.** Ansible, `/etc/ssh/ssh_known_hosts`, správa konfigurací.

Co **není** ověření: `ssh-keyscan server >> ~/.ssh/known_hosts`. Tenhle příkaz se ptá po síti, tedy přesně toho, komu nevěříš. Je to zápis „důvěřuji tomu, kdo mi zrovna odpověděl". Použitelné jen tam, kde nemáš co ztratit.

### Hashované known_hosts

Debian a Ubuntu mají ve výchozím stavu `HashKnownHosts yes`, takže v souboru nejsou jména strojů, ale jejich otisky. Chrání to seznam tvých serverů, kdyby ti někdo prohledal notebook.

Nedá se v tom pak hledat očima, ale nástroje ano:

```bash
ssh-keygen -F server                  # najdi záznam
ssh-keygen -R server                  # smaž záznam
ssh-keygen -H -f ~/.ssh/known_hosts   # zahashuj existující soubor
```

### Když se otisk změní

```
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
```

**Neodstraňuj to reflexivně.** Legitimní důvody jsou reinstalace, obnova ze zálohy nebo výměna stroje. Nelegitimní důvod je útočník uprostřed. Rozdíl poznáš jen tak, že se zeptáš, jestli se se serverem něco dělo — a pokud ne, tak dokud to nevysvětlíš, se nepřipojuj.

Když je změna očekávaná:

```bash
ssh-keygen -R server
ssh server        # a otisk zkontroluj proti konzoli
```

### Automatická rotace

Od OpenSSH 8.5 je ve výchozím stavu `UpdateHostKeys yes`. Server po přihlášení pošle **všechny své host keys** a klient si je uloží. Umožňuje to bezbolestný přechod z RSA na Ed25519 nebo výměnu klíče bez varování u všech uživatelů.

Funguje to jen po úspěšném ověření, takže to nic neoslabuje.

## Ověření uživatele

### Klíčem

To, co se ve skutečnosti děje, je zajímavější než „server má můj veřejný klíč".

1. Klient ohlásí, kterým veřejným klíčem se chce prokázat.
2. Server se podívá do `~/.ssh/authorized_keys`, jestli tam je. Pokud ne, odmítne rovnou.
3. Klient **podepíše session ID** (plus jméno uživatele, služby a metody) svým soukromým klíčem.
4. Server podpis ověří veřejným klíčem.

Klíčový detail je ten podpis nad **session ID**. Session ID je unikátní pro každé spojení a je do něj zapleten host key serveru. Z toho plyne:

- Podpis nejde přehrát na jiné spojení.
- Podpis nejde přeposlat na **jiný server**, protože ten má jiný host key.
- Soukromý klíč **nikdy neopustí tvůj stroj** — server dostane jen podpis.

Tohle je zásadní rozdíl proti heslu. Když se přihlásíš heslem na podvržený server, útočník má tvoje heslo a přihlásí se s ním kamkoliv jinam. Když se přihlásíš klíčem, útočník má podpis, který jinde nefunguje. **Klíč tedy chrání i před špatným rozhodnutím u `known_hosts`.**

### Ostatní metody

| Metoda | Poznámka |
|---|---|
| `publickey` | tohle chceš |
| `password` | posílá heslo dovnitř šifrovaného kanálu; vypni |
| `keyboard-interactive` | dialog otázka–odpověď, tudy jde TOTP a PAM |
| `hostbased` | ověřuje stroj, ne uživatele; v praxi se nepoužívá |
| `gssapi-with-mic` | Kerberos, firemní prostředí |

### Dva faktory pořádně

Rozdíl, na kterém záleží: čárka znamená **a zároveň**, mezera znamená **nebo**.

```
AuthenticationMethods publickey,keyboard-interactive
```

Tohle vyžaduje klíč **a k němu** ještě kód. V PAM pak sedí `pam_google_authenticator` nebo Duo.

```
AuthenticationMethods publickey keyboard-interactive
```

Tohle znamená „stačí jedno z toho" a je to obvykle omyl.

Pro homelab je poctivější odpověď: **hardwarový klíč s vyžadovaným dotekem je 2FA sám o sobě** a je pohodlnější než opisování číslic.

## Volby v authorized_keys

Málo používaná a velmi užitečná věc. Před klíč se dá napsat seznam omezení, která platí **jen pro tento klíč**:

```
restrict,from="192.168.1.0/24",command="/usr/local/bin/zaloha.sh" ssh-ed25519 AAAAC3... zaloha
```

| Volba | Co dělá |
|---|---|
| `restrict` | zakáže **všechno** — tunely, agenta, X11, terminál. Základ, na kterém stavíš. |
| `pty` | povolí terminál zpátky |
| `port-forwarding`, `agent-forwarding` | povolí zpátky konkrétní věc |
| `command="..."` | při přihlášení se spustí **jen tohle**, ať uživatel žádá cokoliv |
| `from="1.2.3.4,10.0.0.0/8"` | klíč platí jen z těchto adres |
| `permitopen="db:5432"` | tunel jen sem a nikam jinam |
| `expiry-time="20271231"` | klíč po tomto datu přestane platit |
| `no-touch-required` | u FIDO klíčů zruší nutnost doteku |

`command=` je nejsilnější z nich. Původní příkaz uživatele najdeš v `$SSH_ORIGINAL_COMMAND`, takže se dá postavit rozcestník, který povolí jen konkrétní operace.

Typické použití: klíč pro zálohovací stroj, který smí spustit jen `rsync` v režimu čtení a odnikud jinud než z tvé sítě. Kdyby ten klíč unikl, útočník nedostane shell.

## ssh-agent

Klíč chráněný heslem je bezpečný a otravný. Agent je proces, který si klíč po jednom odemknutí drží v paměti a podepisuje jím za tebe.

```bash
ssh-add -l                    # co je načtené
ssh-add ~/.ssh/id_ed25519     # přidat
ssh-add -t 4h                 # přidat na omezenou dobu
ssh-add -c ~/.ssh/id_ed25519  # ptát se na potvrzení u každého použití
ssh-add -D                    # zapomenout všechno
```

Do `~/.ssh/config` patří:

```
AddKeysToAgent yes
```

Agent **nikdy nevydá soukromý klíč**. Umí jen podepsat, co mu pošleš. Kdo se dostane k jeho soketu (`$SSH_AUTH_SOCK`), tvůj klíč neukradne — ale může se s ním přihlašovat, dokud agent běží. Z toho plyne následující.

### Přeposílání agenta je nebezpečné

`ssh -A` zpřístupní tvého agenta na vzdáleném stroji, abys z něj mohl skočit dál.

**Kdo je na tom stroji root, může tvého agenta používat.** Ne někdy, vždycky — soket je běžný soubor a root ho přečte. Dokud jsi připojený, může se tvým klíčem přihlásit kamkoliv, kam ty. A ty se to nedozvíš.

Správné řešení je **ProxyJump**, které tenhle problém nemá vůbec:

```bash
ssh -J brana vnitrni-server
```

```
Host vnitrni-*
    ProxyJump brana
```

Rozdíl: klíč se použije **lokálně**, brána jen přeposílá zašifrovaný provoz. Nevidí do něj a nemá k čemu přistupovat. Skládá se to i do řetězu (`-J bastion1,bastion2`).

Když se přeposlání agenta opravdu nedá obejít, aspoň zapni `ssh-add -c`, ať každé použití vyžaduje tvé potvrzení, a nikdy ho nedávej na stroj, kterému nevěříš.

## SSH certifikáty

Tohle je funkce, kterou má OpenSSH od roku 2010, umí ji málokdo a řeší dva největší provozní problémy naráz.

**Problém 1:** máš deset serverů, každý s vlastním host keyem, a deset lidí, kteří na každý potřebují `known_hosts`.

**Problém 2:** máš `authorized_keys` na deseti serverech. Když někdo odejde, musíš jeho klíč odebrat na všech.

Certifikát je **podepsané tvrzení o klíči** — tentýž princip jako u [TLS](TLS-a-HTTPS), jen mnohem jednodušší formát bez X.509 a bez řetězů.

### Vytvoření CA

```bash
ssh-keygen -t ed25519 -f user_ca -C "SSH user CA"
ssh-keygen -t ed25519 -f host_ca -C "SSH host CA"
```

Dvě oddělené CA, ať klíč pro podpis serverů neumí vyrábět uživatele. Soukromé klíče patří mimo běžné servery.

### Certifikát pro uživatele

```bash
ssh-keygen -s user_ca -I "martin@notebook" -n martin,deploy \
           -V +52w -z 1 ~/.ssh/id_ed25519.pub
# vznikne ~/.ssh/id_ed25519-cert.pub
```

- `-I` — popisek, který se objeví v logu serveru
- `-n` — **principals**, tedy jako kteří uživatelé se smí přihlásit
- `-V +52w` — platnost; **tohle je ta hodnota, kvůli které to děláš**
- `-z` — sériové číslo, kvůli případnému odvolání

Na serveru stačí jediný řádek:

```
TrustedUserCAKeys /etc/ssh/user_ca.pub
```

A `authorized_keys` už nemusí existovat vůbec. Kdo má platný certifikát od tvé CA a správný principal, dostane se dovnitř. **Odchod člověka řešíš tím, že mu příště certifikát nevystavíš** — nikam se nepřihlašuješ, nic nemažeš.

### Certifikát pro server

```bash
ssh-keygen -s host_ca -I "web01" -h -n web01.example.cz,192.168.1.10 \
           -V +52w /etc/ssh/ssh_host_ed25519_key.pub
```

```
# /etc/ssh/sshd_config
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
```

U klienta stačí jeden řádek v `known_hosts` pro celou infrastrukturu:

```
@cert-authority *.example.cz ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
```

**A tím zmizí trust on first use.** Nový server naběhne a klienti se na něj připojí bez jediné otázky, protože jeho certifikát podepsala CA, které už věří. Reinstalace serveru přestane být událost.

### Kontrola a odvolání

```bash
ssh-keygen -L -f ~/.ssh/id_ed25519-cert.pub    # co v certifikátu je

# seznam odvolaných
ssh-keygen -k -f revoked.krl id_ed25519-cert.pub
# a v sshd_config:  RevokedKeys /etc/ssh/revoked.krl
```

V praxi ale odvolávání skoro nepotřebuješ — **stačí krátká platnost**. Certifikát na osm hodin se odvolávat nemusí, ráno prostě vyprší. To je stejná úvaha, kterou dělá `tls internal` v [Caddy](Vlastni-certifikacni-autorita#caddy-tls-internal).

## Hardwarové klíče

OpenSSH od verze 8.2 umí klíče uložené v FIDO2 tokenu:

```bash
ssh-keygen -t ed25519-sk -O resident -O verify-required -C "yubikey"
```

- **`ed25519-sk`** — soukromá část zůstane v tokenu, ven se nikdy nedostane
- **`-O resident`** — klíč jde z tokenu obnovit na novém počítači (`ssh-keygen -K`), takže nepotřebuješ zálohu souboru
- **`-O verify-required`** — kromě doteku ještě PIN

Vznikne dvojice souborů, ale ten „soukromý" je jen odkaz do tokenu. **Bez fyzického tokenu je k ničemu.** Zkopírovaný malwarem, uniklý ze zálohy, nalezený na ztraceném notebooku — pořád k ničemu.

Praktická poznámka: pořiď si **dva tokeny** a oba klíče vlož do `authorized_keys`. Ztráta jediného tokenu jinak znamená ztrátu přístupu.

## Tunely a přesměrování portů

Nejužitečnější a nejméně známá schopnost SSH. Ke všem třem variantám platí, že se nemusí zabírat terminál — `-N` znamená „žádný příkaz", `-f` pošle na pozadí.

### Místní: `-L`

Otevři port u sebe, provoz vylez na druhé straně.

```bash
ssh -L 8080:localhost:80 server
# http://localhost:8080 u tebe = http://localhost:80 na serveru
```

Kanonické použití: dostat se na administraci, která poslouchá jen na `127.0.0.1` a záměrně není vystavená. To je přesně ten správný způsob, jak zpřístupnit databázi nebo panel bez [reverse proxy](Reverse-proxy) a bez otevírání portu.

```bash
ssh -N -L 5432:db-server:5432 brana
# lokální psql se připojí na localhost:5432 a mluví s databází ve vnitřní síti
```

Ten prostřední údaj se rozlišuje **z pohledu serveru** — `localhost:80` je server sám, `db-server:5432` je stroj, na který server dosáhne.

### Vzdálený: `-R`

Opačný směr. Otevři port **na serveru**, provoz vyleze u tebe.

```bash
ssh -R 8080:localhost:3000 server
# kdo na serveru sáhne na localhost:8080, dostane tvou aplikaci na portu 3000
```

Tohle je takzvaný **reverzní tunel** a je to plnohodnotná náhrada za port forwarding na routeru, když jsi za [CGNATem](Poskytovatel-CGNAT-a-verejna-IP). Malá VPS s veřejnou adresou stačí.

Ve výchozím stavu se port na serveru otevře jen na `127.0.0.1`. Pro přístup zvenku je potřeba na serveru `GatewayPorts clientspecified` a u klienta uvést adresu:

```bash
ssh -R 0.0.0.0:8080:localhost:3000 server
```

**Přidej `-o ExitOnForwardFailure=yes`.** Bez toho se spojení naváže, i když se tunel nepovede otevřít, a ty se pak divíš, proč nic nefunguje.

Pro trvalý provoz použij `autossh` nebo systemd jednotku s `Restart=always`, `ServerAliveInterval 30` a `ServerAliveCountMax 3`.

### Dynamický: `-D`

SOCKS5 proxy — všechno, co do ní pošleš, vyleze na serveru.

```bash
ssh -N -D 1080 server
curl --socks5-hostname localhost:1080 http://vnitrni-server/
```

Nastav to v prohlížeči a máš „VPN" na jeden příkaz. Pro pohodlný trvalý přístup je [WireGuard](WireGuard) lepší, na jednorázovou potřebu je tohle rychlejší.

Použij `--socks5-hostname` (a v prohlížeči „proxy DNS"), jinak se jména překládají u tebe a vnitřní jména nefungují.

### Multiplexování

Druhé a další připojení na tentýž stroj může použít existující spojení a naváže se okamžitě, bez handshake:

```
Host *
    ControlMaster auto
    ControlPath ~/.ssh/cm-%C
    ControlPersist 10m
```

`%C` je hash celé kombinace, což řeší limit délky cesty u unixového soketu — s `%r@%h:%p` na to u dlouhých doménových jmen narazíš.

Kromě rychlosti to znamená jedno přihlášení místo deseti, což se hodí u hardwarového klíče s dotekem. Nevýhoda: `ssh -O exit server` musíš použít, když chceš spojení opravdu zavřít.

## Konfigurace klienta

`~/.ssh/config` je nejlepší investice do pohodlí. **Platí první nalezená hodnota**, takže obecné `Host *` patří na konec souboru, ne na začátek.

```
Host *
    AddKeysToAgent yes
    ServerAliveInterval 30
    ServerAliveCountMax 3
    HashKnownHosts yes

Host brana
    HostName 89.24.x.x
    User martin
    Port 2222
    IdentityFile ~/.ssh/id_ed25519_brana
    IdentitiesOnly yes

Host nas media pihole
    ProxyJump brana
    User martin

Host github.com
    IdentityFile ~/.ssh/id_ed25519_github
    IdentitiesOnly yes
```

**`IdentitiesOnly yes` je důležitější, než vypadá.** Bez něj klient nabízí serveru postupně všechny klíče z agenta. Jednak tím prozradí, které klíče máš, jednak po pár pokusech narazí na `MaxAuthTries` a dostane `Too many authentication failures` — přestože ten správný klíč má.

Ověření, co se na daný stroj skutečně použije:

```bash
ssh -G nas      # výsledná konfigurace po vyhodnocení všech pravidel
```

## Konfigurace serveru

Základ je na [zabezpečení serveru](Zabezpeceni-serveru#ssh). Tady to, co jde dál.

```
# /etc/ssh/sshd_config.d/99-vlastni.conf

PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin prohibit-password
AuthenticationMethods publickey

AllowGroups ssh-users

LoginGraceTime 20
MaxAuthTries 3
MaxSessions 5
MaxStartups 10:30:60

AllowAgentForwarding no
AllowTcpForwarding no
X11Forwarding no
PermitTunnel no

ClientAliveInterval 300
ClientAliveCountMax 2
```

`MaxStartups 10:30:60` znamená: nad 10 rozpracovaných přihlášení začni s pravděpodobností 30 % odmítat, nad 60 odmítej vše. Levná ochrana proti zahlcení.

`AllowTcpForwarding no` vypne tunely. Zapni je selektivně tam, kde jsou potřeba:

```
Match Group tunel
    AllowTcpForwarding yes
    PermitOpen 192.168.1.20:5432
    ForceCommand /usr/sbin/nologin
```

Uživatel v této skupině dostane tunel na databázi a **nic jiného** — ani shell. `Match` bloky musí být na konci souboru, protože platí až do dalšího `Match`.

Kontrola:

```bash
sshd -t                                    # syntaxe
sshd -T | sort                             # výsledná konfigurace
sshd -T -C user=martin,host=x,addr=10.0.0.5  # včetně vyhodnocení Match
systemctl reload ssh
```

### Zádrhel na Ubuntu 24.04

Ubuntu 24.04 spouští SSH přes **aktivaci soketem** (`ssh.socket`). Důsledek: **`Port 2222` v `sshd_config` se ignoruje**, protože port neotevírá sshd, ale systemd.

```bash
systemctl edit ssh.socket
```

```
[Socket]
ListenStream=
ListenStream=2222
```

Ten první prázdný řádek je nutný — vynuluje předchozí hodnotu, jinak se port jen přidá k dvaadvacítce.

Alternativně se dá socket vypnout a vrátit klasické chování:

```bash
systemctl disable --now ssh.socket
systemctl enable --now ssh.service
```

### Nechoď do internetu vůbec

Nejlepší nastavení SSH je to, na které se zvenku nikdo nedostane. Za [WireGuardem](WireGuard) je hrubá síla mrtvá bez jediného pravidla ve [firewallu](Linux-firewall), protože bez klíče WireGuard neodpoví ani jedním bajtem.

## Přenos souborů

```bash
scp soubor server:/tmp/           # jednoduché
rsync -avz --progress dir/ server:/dir/    # lepší, přenáší jen rozdíly
sftp server                       # interaktivní
sshfs server:/data /mnt/data      # připojení jako disk (FUSE)
```

Poznámka k `scp`: jeho původní protokol byl plný nepříjemných překvapení (vzdálený server mohl ovlivnit, které soubory u tebe vzniknou) a OpenSSH 9.0 ho **přepnul na SFTP**. Většina lidí si toho nevšimla, ale některé exotické možnosti zmizely. Na cokoliv většího používej `rsync` — umí pokračovat a nepřenáší, co už tam je.

## Diagnostika

```bash
ssh -v server        # co se děje
ssh -vvv server      # včetně vyjednávání algoritmů
ssh -G server        # výsledná konfigurace klienta

journalctl -u ssh -f            # log na serveru
/usr/sbin/sshd -d -p 2222       # sshd v ladicím režimu na jiném portu
```

Ten poslední příkaz stojí za zapamatování. Spustí druhého démona na jiném portu s výpisem všeho, obslouží **jedno** spojení a skončí. Ideální pro ladění bez rizika, že si zavřeš dveře.

| Chyba | Příčina |
|---|---|
| `Permission denied (publickey)` | klíč není v `authorized_keys`, nebo se nepoužil ten správný — ověř `ssh -v` |
| | častěji: **špatná práva**. `~` nebo `~/.ssh` zapisovatelné pro skupinu |
| `Too many authentication failures` | agent nabízí moc klíčů → `IdentitiesOnly yes` |
| `no matching host key type found` | starý server s `ssh-rsa` (SHA-1). Aktualizuj server. |
| `no matching key exchange method` | totéž, jen o kategorii vedle |
| `Connection reset by peer` hned | fail2ban, firewall, nebo špatná verze protokolu |
| `Connection timed out` | nedoletíš vůbec — [firewall](Firewall-a-zony) nebo směrování |
| `REMOTE HOST IDENTIFICATION HAS CHANGED` | host key se změnil — **zjisti proč** |
| `Broken pipe` po nečinnosti | NAT zahodil spojení → `ServerAliveInterval` |
| přihlášení trvá 10 s | server dělá zpětný DNS překlad → `UseDNS no` |

Nouzový přístup, když si zavřeš dveře: konzole u poskytovatele nebo hypervizoru, nebo připojení disku k jinému stroji a oprava `sshd_config`. Proto se při změnách vždycky drží **druhé otevřené spojení**, dokud si nové neověříš.

## Chyby, o kterých je dobré vědět

**Terrapin, CVE-2023-48795 (2023).** Útočník uprostřed dokázal na začátku spojení nepozorovaně **umazat pakety**, protože se pořadová čísla nezapočítávala do ověření. Šlo tak potlačit vyjednání rozšíření a snížit bezpečnost sezení. Týkalo se to `chacha20-poly1305` a CBC s EtM. Opravou je **strict KEX** od OpenSSH 9.6 — obě strany si zavedou pořadová čísla do výpočtu. `ssh -v` ti to potvrdí řádkem o `kex-strict`.

**regreSSHion, CVE-2024-6387 (2024).** Souběh v obsluze signálu při vypršení `LoginGraceTime` umožňoval **vzdálené spuštění kódu jako root, bez přihlášení**. Netriviálně zneužitelné, ale reálné. Ukázka toho, že drtivá většina zranitelností je v okolí, ne v kryptografii.

**xz / liblzma, CVE-2024-3094 (2024).** Do komprimační knihovny byl zaveden zadní vrátka, která přes distribuční záplatu pro systemd zasáhla `sshd`. Chytilo se to náhodou, protože si někdo všiml, že přihlášení trvá o půl vteřiny déle. Samotný OpenSSH `liblzma` nepoužívá — problém přinesla úprava distribuce.

Praktický závěr je nudný a pravdivý: **aktualizuj**. Všechny tři případy byly opravené dřív, než se objevily plošné útoky.

## Tahák

```bash
# klíče
ssh-keygen -t ed25519 -a 100 -C "martin@notebook"
ssh-keygen -t ed25519-sk -O resident -O verify-required
ssh-copy-id -i ~/.ssh/id_ed25519.pub server
ssh-keygen -lf klic.pub                    # otisk
ssh-keygen -y -f klic > klic.pub           # veřejný z privátního

# známé hostitele
ssh-keygen -F server / -R server
ssh -o VerifyHostKeyDNS=yes server

# agent
ssh-add -l / -t 4h / -c / -D

# tunely
ssh -N -L 8080:localhost:80 server         # k sobě
ssh -N -R 8080:localhost:3000 server       # od sebe
ssh -N -D 1080 server                      # SOCKS
ssh -J brana cil                           # skok přes bránu

# certifikáty
ssh-keygen -s user_ca -I martin -n martin -V +8h klic.pub
ssh-keygen -L -f klic-cert.pub

# diagnostika
ssh -vvv server ; ssh -G server ; ssh -Q kex
sshd -t ; sshd -T ; ssh-audit server
```

## Kam dál

- **[Zabezpečení serveru](Zabezpeceni-serveru)** — základní nastavení a co nepomáhá
- **[WireGuard](WireGuard)** — jak SSH nevystavovat vůbec
- **[Veřejný a soukromý klíč](Verejny-a-soukromy-klic)** — co jsou ty klíče zač
- **[Diffie-Hellman](Diffie-Hellman)** — jak se domluví klíče spojení
- **[Fail2ban a CrowdSec](Fail2ban-a-CrowdSec)** — když SSH vystavené být musí
- **[Vzdálený přístup](Vzdaleny-pristup)** — kdy tunel, kdy VPN, kdy proxy
