# SECURITY.md — bezpieczeństwo serwera Zasada porządkująca: **serwer jest tak bezpieczny, jak jego najsłabsza wystawiona usługa.** Utwardzony SSH nie ma znaczenia, jeśli obok stoi panel administracyjny bez hasła. --- ## 1. Warstwy | Warstwa | Czym chronimy | Gdzie skonfigurowane | |---|---|---| | Dostęp do maszyny | SSH tylko na klucz, root wyłączony | `/etc/ssh/sshd_config.d/99-hardening.conf` | | Sieć | ufw: deny incoming, allow 22/80/443 | `ufw status verbose` | | Automatyczne ataki | fail2ban na sshd | `/etc/fail2ban/jail.local` | | Aktualizacje | unattended-upgrades (security) | `/etc/apt/apt.conf.d/50unattended-upgrades` | | Izolacja aplikacji | osobne sieci Dockera, brak publikowanych portów | compose każdego stacka | | Transport | TLS z Let's Encrypt na wszystkim | Traefik | | Sekrety | pliki `.env`, `chmod 600`, poza repo | obok każdego stacka | --- ## 2. SSH Docelowa konfiguracja: ``` PermitRootLogin no PasswordAuthentication no KbdInteractiveAuthentication no PubkeyAuthentication yes AllowUsers ubuntu X11Forwarding no MaxAuthTries 3 ``` **Reguła bezwzględna przy każdej zmianie w sshd:** otwierasz drugą sesję SSH i sprawdzasz, że logowanie działa, **zanim** zamkniesz pierwszą. Błąd w `sshd_config` przy zamkniętej sesji oznacza odzyskiwanie dostępu przez konsolę KVM w panelu OVH. Port zostawiamy na 22. Przenoszenie na 2222 to kosmetyka — odsiewa hałas w logach, ale nie zatrzymuje nikogo, kto celuje w konkretną maszynę. fail2ban robi tu realną robotę. ### Klucze - osobny klucz na każde urządzenie, `ed25519`, z komentarzem identyfikującym sprzęt - klucz prywatny **nigdy** nie trafia na serwer ani do repo - rejestr kluczy: `SERVER.md`, sekcja 4 - przy zgubieniu urządzenia: usuwasz jego linię z `~/.ssh/authorized_keys`, koniec iOS: Termius lub Blink. Wchodzisz przez `tmux attach -t claude` do tej samej sesji, którą zostawiłeś na Macu. --- ## 3. Sekrety | Zasada | Dlaczego | |---|---| | `.env` obok stacka, `chmod 600`, właściciel `ubuntu` | inni użytkownicy systemu nie odczytają | | `.env` w `.gitignore`, w repo tylko `.env.example` | sekret w historii gita zostaje tam na zawsze | | Hasła generowane, nie wymyślane — `openssl rand -base64 32` | brak ponownego użycia | | Osobny sekret na usługę | wyciek jednego nie kompromituje reszty | Jeśli sekret **przypadkiem** trafi do repo: nie wystarczy commit usuwający go. Trzeba go **unieważnić i wygenerować nowy** — historia gita pamięta, a jeśli poszła na mirror GitHuba, to jest już poza Twoją kontrolą. --- ## 4. Usługi administracyjne Panel Traefika, bazy danych, narzędzia debugowe — **nie wystawiamy publicznie**. Dostęp przez tunel SSH: ```bash ssh -L 8080:127.0.0.1:8080 ubuntu@dfkk.cloud # potem localhost:8080 w przeglądarce ``` Jeśli coś musi być publiczne (np. `status.dfkk.cloud`, żeby działało z telefonu), to za basic auth w Traefiku, z hasłem z generatora. **Gitea: rejestracja nowych użytkowników wyłączona natychmiast po instalacji.** Otwarta rejestracja na publicznej instancji to zaproszenie dla botów, które w kilka dni zamienią ją w hosting spamu. --- ## 5. Aktualizacje | Co | Jak | Kiedy | |---|---|---| | Poprawki bezpieczeństwa Ubuntu | `unattended-upgrades`, automatycznie | codziennie | | Reszta pakietów systemu | ręcznie, `apt upgrade` | miesięczny przegląd | | Obrazy Dockera | ręcznie, podniesienie tagu wersji w compose | miesięczny przegląd | | Restart po aktualizacji kernela | ręcznie, po snapshocie | gdy `/var/run/reboot-required` | Automatyczny restart po aktualizacji jest **wyłączony**. Nie chcemy, żeby maszyna sama się przeładowała w środku pracy albo w trakcie działania czegoś produkcyjnego. Obrazy pinujemy do konkretnych wersji. `latest` oznacza, że najbliższy `docker compose pull` może podmienić działającą aplikację na wersję z breaking changes — bez ostrzeżenia. --- ## 6. Co monitorujemy | Sygnał | Próg | Reakcja | |---|---|---| | Zajętość dysku | 75% | sprzątanie wg `40-utrzymanie.md` | | Usługa nie odpowiada | 2 nieudane sprawdzenia | alert mailem | | Certyfikat wygasa | < 14 dni | sprawdzić DNS-01 i klucze OVH | | Nietypowe wpisy w logach SSH | — | przegląd przy miesięcznym audycie | Alerty idą na `k.kruszka@hotmail.com` przez zewnętrzny SMTP. Serwera pocztowego tu nie stawiamy — OVH blokuje port 25. --- ## 7. Podejrzenie włamania Kolejność ma znaczenie. **Nie restartuj i nie „posprzątaj" najpierw** — zniszczysz ślady i stracisz możliwość ustalenia, czym weszli. 1. Odetnij ruch: `ufw default deny incoming` i zatrzymaj wystawione kontenery 2. **Zrób snapshot OVH** — to Twój materiał dowodowy 3. Zbierz: `last`, `journalctl -u ssh`, `docker ps -a`, `crontab -l` dla wszystkich użytkowników, `ls -la /tmp /var/tmp`, nietypowe procesy i połączenia (`ss -tulpn`) 4. Unieważnij **wszystko**: klucze SSH, tokeny API OVH, token GitHub, hasła w `.env` 5. Oceń zakres — jeśli atakujący miał roota, maszyna jest spalona. Postawienie od zera z repo `infra` + backup danych jest szybsze i pewniejsze niż czyszczenie. Dlatego repo `infra` musi być kompletne. 6. Dopiero potem: odbudowa, i wpis do `SERVER.md` co się stało --- ## 8. Braki, które trzeba domknąć Uczciwa lista rzeczy, których jeszcze **nie ma**. Przeglądać przy miesięcznym audycie. - [ ] **Backup off-site (restic).** Snapshot OVH to rollback, nie backup — jeden slot, ręczny, nadpisywany, w tej samej infrastrukturze. Do domknięcia **zanim** na serwerze pojawią się pierwsze prawdziwe dane. - [ ] **Test odtworzenia z backupu.** Backup, którego nie odtworzyłeś, nie istnieje. - [ ] **Mirror repo `infra` na GitHub.** Bez tego awaria serwera zabiera kod, historię i konfigurację potrzebną do odtworzenia serwera — naraz. - [ ] **2FA na koncie OVH.** Panel OVH może zrobić z maszyną wszystko, łącznie z reinstalacją. Jest w praktyce ważniejszy niż dostęp SSH. - [ ] **2FA na koncie GitHub.**