VPS-firewall: open alleen de VPN-poort en bewijs het daarna
Eén poort moet openbaar blijven, anders verbindt geen enkele client. Weiger de rest, houd doorsturen gescheiden van invoer, en laat een herstelpad achter dat je vooraf hebt getest.
Een firewall die alleen de VPN-poort toelaat is niet één regel. Het zijn drie afzonderlijke beslissingen die toevallig hetzelfde poortnummer raken: de invoerketen bepaalt wat een lokaal proces mag bereiken, de doorstuurketen bepaalt wat namens tunnelclients door de host mag, en de NAT-tabel herschrijft het bronadres op weg naar buiten. Zet je er één verkeerd, dan is het resultaat zelden een schone storing. Je krijgt een host die clients accepteert maar niets laadt, of een host die op slot lijkt tot iemand de volgorde van de ketens leest.
De enige poort die niet dicht kan
De VPN-listener moet verbindingen aannemen van adressen die je niet kunt voorspellen, dus is het per definitie een publieke dienst. Dat is de enige uitzondering waarop het ontwerp is gebouwd: één UDP-poort voor OpenVPN, één UDP-poort voor WireGuard, één TCP-poort voor een SSL VPN. Al het overige hoort achter de tunnel, wat betekent dat applicatiediensten aan het tunneladres of aan loopback moeten binden en niet aan elke interface.
Binden is echter geen handhaving. Een proces dat aan 0.0.0.0 is gebonden, is alleen bereikbaar als een regel het toelaat, en het standaardbeleid is de terugval voor verkeer dat geen enkele regel raakte, niet de regel die de poort bestuurt. Een accept boven je drop blijft een accept, en een dienst van het publieke adres af halen is een zelfstandige verbetering, geen vervanging van de keten.
Invoer, doorsturen en NAT beantwoorden verschillende vragen
Invoerverkeer is aan de host zelf gericht, daar wonen dus de VPN-listener en het beheerpad. Doorstuurverkeer gaat door de host, en tunnelclients hebben het nodig plus net.ipv4.ip_forward=1. De klassieke half kapotte configuratie staat de uitgaande stroom toe en vergeet de retourrichting, zodat de client zijn handshake afmaakt en er daarna niets laadt; een conntrack-regel voor gevestigd en gerelateerd verkeer dekt die richting.
NAT is de derde vraag. Zonder herschrijving in postrouting verlaten doorgestuurde pakketten de host met de privébron van de tunnel en worden ze hogerop weggegooid, omdat geen enkel netwerk voorbij de host weet hoe de pool teruggerouteerd moet worden. Het bronprefix in die regel moet bij de VPN-pool passen, niet bij het eigen adres van de host, en hetzelfde geldt voor de doorstuurregel die het clientsubnet noemt.
IPv6 is een tweede beleid, geen nagedachte. Een host met een strikte IPv4-keten en een open beleid op ip6tables heeft niets gesloten; de diensten zijn nog steeds bereikbaar via de andere familie. Ofwel spiegel je de regelset met ip6tables, ofwel schakel je IPv6 bewust uit op de publieke interface, en schrijf op welke je hebt gekozen.
Een basislijn die je in één pass leest
Begin met drop en voeg daarna alleen toe wat de host nodig heeft. Eerst loopback en antwoorden, dan het beheerpad, dan de tunnel-listener, dan het doorstuurbeleid voor clients:
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
iptables -A INPUT -p udp --dport 1194 -m conntrack --ctstate NEW -j ACCEPT
sysctl -w net.ipv4.ip_forward=1
iptables -A FORWARD -i tun0 -o eth0 -s 10.8.0.0/24 -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADEDe interfacenamen, de poort en de pool zijn plaatsaanduidingen; lees ze van de server in plaats van ze te kopiëren. Het resultaat opslaan is net zo belangrijk als het schrijven, want een regelset die alleen in het geheugen leeft, verdwijnt bij de volgende herstart en opent de host stilletjes opnieuw. Maak het permanent met het mechanisme van de distributie en bewaar een kopie buiten de machine voor de dag dat de machine niet opstart.
De laatste poort sluiten zonder de host te verliezen
Er zit een addertje in het ontwerp. Bestaat SSH alleen via de tunnel, dan neemt een uitgevallen VPN-dienst SSH mee, en de enige weg terug is misschien een console die je nooit hebt geopend. Bevestig voordat je de laatste publieke poort sluit dat die console bestaat en dat je hem echt kunt gebruiken, of houd één smal beheerpad aan dat niet van de tunnel afhangt: SSH vanaf een vast adres, alleen sleutels, wachtwoordauthenticatie uit.
Pas de wijziging toe vanuit een sessie die al is opgezet, niet via de verbinding die je op het punt staat te filteren. Plan een terugdraaiing zodat een fout verloopt in plaats van permanent te worden, bijvoorbeeld een systemd-run --on-active=10min-taak die de vorige regelset herstelt, en houd een tweede SSH-sessie open terwijl je de eerste test. Controleer daarna of de terugdraaitaak echt afgaat, want ongetest herstel is hetzelfde als geen herstel.
Verifieer het pad, niet het script
Lees de geladen regels, niet het bestand dat ze had moeten laden. Bevestig vanaf een geautoriseerde externe host dat alleen de bedoelde poorten antwoorden; bevestig van binnen de tunnel dat de diensten die je wilde publiceren bereikbaar zijn. Een standaard-deny-beleid dat ook tunnelverkeer blokkeert, ziet er precies zo uit als een werkende lockdown tot iemand het probeert te gebruiken.
iptables -S
iptables -t nat -S
ss -lntup
ip -4 addr showNoteer de toegestane poort, de clientpool, de tunnelinterface, de beheerbron en de consoleprocedure naast de configuratie. De volgende wijziging aan de pool of de interfacenaam moet zowel in het filter als in de NAT-regel worden doorgevoerd, anders keert de host terug naar een halfopen toestand zonder dat iemand het merkt.
Eén gewoonte is meer waard dan de regelset: bewijs na elke wijziging het doorstuurpad met een clientverzoek en het herstelpad met een tweede sessie. Een lockdown die je niet hebt gebruikt, is een hypothese.