Totul funcționează până când executantul de build are nevoie de același socket de gazdă pe care îl folosește oaspetele VPN, iar atunci nimeni nu poate explica de ce un job poate citi cheile gateway-ului. Totul a fost instalat pe o singură mașină pentru că încăpea, iar fiecărui oaspete i s-a dat acces larg pentru că era cel mai rapid mod de a face lucrurile să meargă. Rezultatul este o gazdă a cărei graniță de încredere este cablul de alimentare. Împărțirea rolurilor cere mai puțin efort decât pare, și începe cu numirea lor.

Trei roluri care împart acum totul

O intrare VPN, un set de servicii private și un executant de build vor lucruri diferite. Prima are nevoie de un port public și de o rețea îngustă pe partea privată. Al doilea are nevoie de stocare persistentă și de secretele aplicației. Al treilea execută cod scris de altcineva.

Faptul că toate trei primesc același acces este ce face simptomul confuz, pentru că atunci oricare dintre ele poate afecta celelalte. Un job de build care poate ajunge la socketul hipervizorului este un job care poate opri VPN-ul. Un oaspete de servicii cu rută către rețeaua de administrare poate schimba configurația switch-ului. Problema sunt privilegiile, iar lista de procese este doar locul unde se vede. Numirea rolurilor mai întâi dă fiecărui privilegiu un loc al lui.

Schița de mai jos este un punct de plecare pentru această separare, nu o promisiune de disponibilitate ridicată. Trateaz-o ca pe o ipoteză de verificat, nu ca pe proiectul final.

Împarte trei roluri, nu trei procese

Trei oaspeți pe o gazdă dau trei seturi de privilegii și trei poziții de rețea. Ei nu dau trei domenii de eșec, iar proiectarea ar trebui să spună asta cu voce tare.

Separarea ajută doar dacă compromiterea unui oaspete nu le oferă automat acces celorlalți, iar asta este o setare a hipervizorului, nu o convenție de denumire. A numi o VM gateway nu împiedică un alt oaspete să ajungă la gazdă. Citește configurația switch-ului virtual în locul numelui de gazdă, apoi confirmă că fiecare oaspete stă acolo unde crezi.

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

Scrie topologia și apoi verific-o cu instrumentele care o dețin. Un oaspete care apare pe bridge-ul greșit este o constatare, nu un detaliu. Corectează rețeaua înainte de a schimba orice configurație de serviciu.

Lasă oaspetele VPN să deschidă doar ușile pe care le deține

Publică portul tunelului pe router și nimic altceva. Porturile de administrare ale oaspeților aparțin unei căi de administrare, nu internetului, iar interfața hipervizorului aparține și ea acolo.

Rețeaua din partea privată ar trebui să ajungă la serviciile de care au nevoie utilizatorii la distanță și să se oprească acolo. Autentificarea aplicației rămâne pornită, pentru că un tunel este transport, nu autentificare. Ține tabela de rutare a oaspetele VPN destul de mică încât să o descrii într-o propoziție. Dacă nu o poți descrie atât de simplu, granița s-a estompat deja.

virsh net-list --all
virsh net-dumpxml lab-private
sudo nft list ruleset | grep -n -E '51820|10.60'

Orice ascultă pe gazdă și nu este necesar pentru administrare nu ar trebui să asculte deloc. Asta include API-ul hipervizorului, care este ușor de lăsat expus pe o interfață publică.

Joburile de build au nevoie de alt tip de graniță

Un job CI rulează comenzi controlate de depozit, deci încrederea lui depinde de cine poate împinge cod. Un executant shell rulează acele comenzi ca utilizatorul runner-ului pe gazdă, care este cea mai largă graniță disponibilă.

Executanții de container sunt mai înguști, iar un container privilegiat nu este unul dintre cei înguști. Ține runner-ul în propriul oaspete cu propriul domeniu de rețea și dă-i doar accesul la artefacte și la implementare de care au nevoie joburile. Lucrătorii efemeri reduc starea păstrată; nu reduc accesibilitatea sau expunerea secretelor. Ambele au în continuare nevoie de limite explicite.

Oaspetele de servicii deține datele, deci planul lui de backup este cel care trebuie să funcționeze. Ține depozitele, fișierele încărcate și baza de date în același program. Testează o restaurare în loc să citești starea jobului.

Planifică eșecul pe care toate trei îl împart

O gazdă este un domeniu de eșec. Un reboot elimină tunelul și fiecare serviciu privat în același moment, exact când o cale de intrare contează cel mai mult.

Păstrează accesul la consolă care nu depinde de oaspetele VPN, fie că este o consolă fizică, o interfață de administrare în afara benzii sau o consolă a furnizorului. Un snapshot păstrat pe același disc nu este o copie independentă și nici un backup care rulează în interiorul oaspetele pe care îl protejează. Apoi testează pornirea la rece ca o secvență, nu ca o listă de verificare: gazdă, rețele virtuale, calea tunelului și o aplicație numită sub un cont obișnuit.

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'

Ordinea pornirii proceselor nu dovedește nimic despre rute și DNS, deci testează calea, nu stările unităților. Calea este ceea ce un utilizator la distanță are cu adevărat nevoie.

Articole similare