OpenVPN client-connect-hook: waar je adres echt naartoe gaat
Een client-connect-script kan geen adres retourneren door het af te drukken. Zie hoe het tijdelijke bestand van de hook, de exitstatus en de topologie het IP van de client bepalen.
Een adres retourneren vanuit een client-connect-script lijkt de IP van de client te moeten instellen. Dat doet het niet: het enige kanaal van de hook naar de server is een tijdelijk bestand waarvan het pad als laatste argument van het script binnenkomt, en het adres moet daar in ifconfig-push-syntaxis worden geschreven. Een script dat het adres afdrukt of met nul eindigt zonder iets te schrijven, levert een verbonden client met een adres uit de pool, en het serverlog ziet er volkomen gezond uit.
De verwarring is redelijk, omdat verschillende andere hooks wel degelijk antwoorden via exitcodes of omgevingsvariabelen. Deze heeft zijn eigen contract, en het contract begint met een bestandsnaam.
De hook schrijft een bestand, geen retourwaarde
De declaratie bestaat uit twee regels: script-security 2 om de aanroep toe te staan, daarna client-connect /etc/openvpn/server/assign-address.sh. Wanneer een client zich authenticeert, voert de server dat script uit met het gegenereerde bestandspad als laatste argument toegevoegd, dat alleen $1 is als de declaratie geen eigen extra argumenten doorgeeft. Alles wat op die client wordt toegepast, gaat als gewone richtlijnen in dat bestand, één per regel, precies zoals ze in een configuratie zouden staan. Een wrapper die zijn eigen parameters toevoegt, verschuift de positie, en het klassieke symptoom is een script dat naar een pad schrijft dat op --verbose lijkt.
De exitstatus is de andere helft van het contract. Nul betekent accepteren en het bestand lezen; al het andere weigert de verbinding, en zo wordt een niet-gemapte identiteit geweigerd in plaats van stil terug te vallen op de pool. Schrijf de volledige richtlijn voordat je succes retourneert, zodat een half geschreven bestand nooit de configuratie van een client wordt.
Twee richtlijnen verdienen aandacht omdat ze met al het andere samenspelen. ifconfig-push legt het adres vast, terwijl een gepushte route de client vertelt wat via de tunnel bereikbaar is; geef de eerste terug en vergeet de tweede en je krijgt een client met een adres en nergens om naartoe te gaan. De volgorde in het bestand maakt niet uit, de volledigheid wel. Een hook die een route pusht voor een prefix die geen enkele peer kan bereiken, is dezelfde fout gezien vanaf de andere kant.
Invoer komt via omgevingsvariabelen: common_name, trusted_ip, ifconfig_pool_remote_ip en de rest. Behandel ze allemaal als strings van buiten de server, nooit als shellfragmenten.
Adresmapping is beleid, geen rekenkunde
Een verleidelijke kortere weg is de certificaatnaam te parseren en de cijfers als laatste octet te gebruiken. Dat is geen toewijzer, en het faalt op gewone manieren: twee namen met hetzelfde achtervoegsel botsen, een naam die op 300 eindigt is geen adres, en een certificaat dat een beheerder hernoemt, verplaatst stil een apparaat. Valideer subnetlidmaatschap, het bruikbare hostbereik, gereserveerde adressen en uniciteit bij elke aanroep, en houd de mapping in een tabel die een mens kan lezen.
Wie de naam beheert, beheert het adres, dus bepaal wat common_name in deze implementatie werkelijk vertegenwoordigt. Met username-as-common-name ingeschakeld is de identiteit de login en niet het onderwerp van het certificaat. Geef die waarde nooit door aan shell-evaluatie, en laat de client nooit een bestandspad beïnvloeden.
Een op een register gebaseerde toewijzer moet tegen gelijktijdigheid kunnen. Reserveer het adres atomair, behandel een mislukte schrijfactie zonder een half geconfigureerde client toe te laten, en markeer elke toewijzing met de verbinding die er eigenaar van is, zodat een late verbreking van een oudere sessie geen adres kan vrijgeven dat een nieuwere al vasthoudt. Meerdere serverinstanties die één pool delen, maken die eis scherper in plaats van optioneel.
De topologie bepaalt wat ifconfig-push betekent
Met topology subnet op 10.77.0.0/24 is ifconfig-push 10.77.0.20 255.255.255.0 een adres plus het netmasker. Onder topology net30 is het tweede argument helemaal geen masker maar het peeradres, dus het kopiëren van de subnetvorm naar een net30-server geeft je een client die verbindt en geen verkeer kan doorlaten. Wijzig de topologie bewust, nooit om een voorbeeld consistent te laten lijken. Een server die al net30 draait, heeft live clients op de oude adressering, dus de overstap is een migratie met een onderhoudsvenster, geen regel bewerken.
Statische adressen moeten buiten de dynamische pool en buiten elke andere statische toewijzing liggen, inclusief die van gewone client-config-dir-bestanden. Als twee mechanismen hetzelfde adres voor verschillende clients kunnen produceren, zal een van beide dat uiteindelijk doen.
Test vier gevallen voordat je erop vertrouwt
Voer dezelfde controles uit tegen een gemapte identiteit, een niet-gemapte, een herverbinding en een gesimuleerde registerstoring. openvpn --config client.ovpn --verb 4 toont het toegewezen adres in het push-antwoord, en op de server bevestigt grep -A2 PUSH_REPLY /var/log/openvpn/openvpn.log wat er werkelijk is verzonden. Voor de niet-gemapte identiteit is het verwachte resultaat een geweigerde verbinding, geen adres uit de pool.
Inspecteer daarna de map waarin de daemon die tijdelijke bestanden schrijft. Het script draait in de verlaagde servicecontext, dus een pad dat root wel kan schrijven maar de daemon niet, verandert elke verbinding in een weigering met een rechtenfout die dieper in het log begraven ligt.
Toewijzing is geen autorisatie: een vastgezet adres geeft nog steeds geen toegang tot elke bestemming achter de server. Houd het routerings- en firewallbeleid in zijn eigen laag, en verifieer het tegen het adres dat je zojuist hebt vastgezet.