Jeden serwer laboratoryjny, trzy role zaufania, jedna domena awarii
Wejście VPN, prywatne usługi i zadania CI potrzebują odrębnych uprawnień nawet na jednym serwerze. Podziel role, odizoluj sieci i zachowaj odzyskiwanie przez konsolę.
Wszystko działa, dopóki runner kompilacji nie potrzebuje tego samego gniazda hosta, którego używa gość VPN, a wtedy nikt nie potrafi wyjaśnić, dlaczego zadanie może czytać klucze bramy. Wszystko zainstalowano na jednej maszynie, bo tak było wygodnie, a każdy gość dostał szeroki dostęp, bo to była najszybsza droga do działania. Efektem jest host, którego granica zaufania to kabel zasilania. Podział ról wymaga mniej pracy, niż to brzmi, i zaczyna się od nazwania ich.
Trzy role, które obecnie wszystko dzielą
Wejście VPN, zestaw prywatnych usług i runner kompilacji chcą różnych rzeczy. Pierwsze potrzebuje publicznego portu i wąskiej sieci po stronie prywatnej. Drugie potrzebuje trwałego magazynu i sekretów aplikacji. Trzeci wykonuje kod, który napisał ktoś inny.
To, że wszystkie trzy mają ten sam dostęp, właśnie czyni objaw mylącym, bo wtedy każde z nich może wpływać na pozostałe. Zadanie kompilacji, które ma dostęp do gniazda hiperwizora, to zadanie, które może zatrzymać VPN. Gość usług z trasą do sieci zarządzania może zmienić konfigurację przełącznika. Problemem są uprawnienia, a lista procesów jest tylko miejscem, w którym to widać. Nazwanie ról najpierw daje każdemu uprawnieniu własne miejsce.
Szkic poniżej jest punktem wyjścia do takiego podziału, a nie obietnicą wysokiej dostępności. Traktuj go jako hipotezę do sprawdzenia, a nie jako gotowy projekt.
Podziel trzy role, a nie trzy procesy
Trzy goście na jednym hoście dają trzy zestawy uprawnień i trzy pozycje w sieci. Nie dają trzech domen awarii, a projekt powinien to powiedzieć wprost.
Rozdzielenie pomaga tylko wtedy, gdy przejęcie jednego gościa nie daje automatycznie dostępu pozostałym, a to ustawienie hiperwizora, nie konwencja nazewnicza. Nazwanie maszyny wirtualnej bramą nie powstrzyma innego gościa przed dotarciem do hosta. Czytaj konfigurację przełącznika wirtualnego zamiast nazwy hosta, a potem potwierdź, że każdy gość siedzi tam, gdzie myślisz.
host
├─ vpn-gw 10.60.10.2/24 public 51820/udp only
├─ services 10.60.0.10/24 no route to the management network
└─ ci-runner 10.60.20.2/24 outbound only, no host socket
Zapisz topologię, a potem zweryfikuj ją narzędziami, które nią zarządzają. Gość widoczny na niewłaściwym bridge'u to ustalenie, nie szczegół. Napraw sieć, zanim zmienisz konfigurację jakiejkolwiek usługi.
Pozwól gościowi VPN otwierać tylko własne drzwi
Opublikuj port tunelu na routerze i nic więcej. Porty administracyjne gościa należą do ścieżki zarządzania, a nie do internetu, i tam też należy interfejs samego hiperwizora.
Sieć po stronie prywatnej powinna sięgać usług, których potrzebują użytkownicy zdalni, i tam się kończyć. Uwierzytelnianie aplikacji pozostaje włączone, bo tunel to transport, a nie logowanie. Utrzymuj tablicę tras gościa VPN tak małą, by dało się ją opisać w jednym zdaniu. Jeśli nie potrafisz opisać jej tak prosto, granica już się rozmyła.
virsh net-list --all
virsh net-dumpxml lab-private
sudo nft list ruleset | grep -n -E '51820|10.60'
Wszystko, co nasłuchuje na hoście i nie jest potrzebne do zarządzania, nie powinno nasłuchiwać wcale. Dotyczy to API hiperwizora, które łatwo zostawić wystawione na publicznym interfejsie.
Zadania kompilacji potrzebują innego rodzaju granicy
Zadanie CI uruchamia polecenia kontrolowane przez repozytorium, więc jego zaufanie zależy od tego, kto może wypychać kod. Executor powłoki uruchamia te polecenia jako użytkownik runnera na hoście, czyli w najszerszej dostępnej granicy.
Executory kontenerowe są węższe, a uprzywilejowany kontener nie należy do tych węższych. Trzymaj runner we własnym gościu z własnym zakresem sieci i daj mu tylko dostęp do artefaktów i wdrożeń, którego potrzebują zadania. Jednorazowi pracownicy ograniczają zachowany stan; nie ograniczają osiągalności ani ekspozycji sekretów. Jedno i drugie nadal wymaga wyraźnych limitów.
Gość usług jest właścicielem danych, więc to jego plan kopii zapasowych musi działać. Trzymaj repozytoria, przesyłane pliki i bazę danych w tym samym harmonogramie. Przetestuj odtworzenie z kopii zamiast czytać status zadania.
Zaplanuj awarię, którą wszystkie trzy dzielą
Jeden host to jedna domena awarii. Restart usuwa tunel i każdą prywatną usługę w tej samej chwili, a właśnie wtedy droga do środka liczy się najbardziej.
Zachowaj dostęp do konsoli niezależny od gościa VPN, czy to konsola fizyczna, interfejs zarządzania poza pasmem, czy konsola u dostawcy. Migawka zapisana na tym samym dysku nie jest niezależną kopią, podobnie jak kopia zapasowa uruchamiana wewnątrz gościa, którego chroni. Potem przetestuj zimny start jako sekwencję, a nie listę kontrolną: host, sieci wirtualne, ścieżka tunelu i jedna nazwana aplikacja na zwykłym koncie.
virsh list --all
systemctl is-active libvirtd
curl -sS -m 5 -o /dev/null -w '%{http_code}\n' https://10.60.0.10/
ss -ltnp | grep -E ':51820|:443'
Kolejność uruchamiania procesów nie dowodzi niczego o trasach i DNS, więc testuj ścieżkę, a nie stany jednostek. To ścieżka jest tym, czego użytkownik zdalny naprawdę potrzebuje.