In onze publieke computerlokalen hebben we lang de toegang tot het draadloos netwerk gecontroleerd door het draadloos basisstation enkel verkeer te laten aanvaarden van netwerkkaarten met geregistreerde MAC-adressen. Naarmate zulke kaarten echter van een rariteit tot een deel van de standaarduitrusting van laptops zijn geworden, en niet slechts een paar lesgevende assistenten, maar ook veel studenten gebruik willen maken van het draadloos netwerk, begon het onderhouden van zulk een lijst opgeslagen in het basisstation een echt corvee te worden.
Ik ben dus op zoek gegaan naar een methode van toegangscontrole die kon gebruikmaken van de server die de geldigheid van paswoorden controleert voor o.a. een aantal webapplicaties, zodat alle studenten en personeelsleden toegang zouden krijgen op basis van hun account bij het rekencentrum. Inspiratie vond ik bij het Leuvense kotnet, waar een pas aangesloten PC door de firewall enkel toegelaten wordt http- en dns-connecties te maken met enkele lokale servers. Dit is net voldoende om naar een webpagina te gaan om je PC te registreren, en als je dat doet, krijg je meteen toegang tot het hele internet.
In 2003 was het draadloos netwerk al zo populair bij technisch minder onderlegde studenten, dat het nodig was geworden een firewall te plaatsen tussen het basisstation en het universitair netwerk, vooral om windowswormen van het netwerk te houden. Die firewall was een oude PC met een extra netwerkkaart, met het gratis besturingssysteem OpenBSD en de firewall-software die daar standaard bijzit.
OpenBSD is een BSD-variant waarvan de ontwerpers zich specialiseren in zeer flexibele en toch extreem beveiligde netwerkservices. Hun bekendste subproject blijft openssh, maar hun firewall-software, gewoon pf genoemd voor "packet filter", is ook de onbetwiste standaard geworden onder BSD-besturingssystemen. Een van de gemakken van pf is de mogelijkheid om tabellen van IP-adressen te definiëren in een configuratie-bestand, en dan regels te definiëren die enkel van toepassing zijn op adressen uit die tabellen. Die tabellen kunnen nog na de activatie van de firewall bekeken, aangevuld of weer uitgemest worden.
Het systeem dat ik ontworpen heb, draait helemaal om de manipulatie van zulk een tabel. Ik heb op de bestaande firewall de regel, die ervoor zorgde dat het dataverkeer van de ene netwerkkaart wordt doorgegeven naar de andere netwerkkaart, aangepast om enkel verkeer door te laten als het IP-adres van de communicatiepartner aan de kant van het basisstation voorkomt in een tabel genaamd <loggedin>.
De eerste component van het systeem is een website via dewelke de gebruikers kunnen inloggen. Die site wordt aangeboden door een webserver die draait op de firewall zelf: ze kan dus ook wel degelijk bereikt worden vanaf IP-adressen die nog niet ingelogd zijn. De website wordt gegenereerd door een cgi-script, dat het ingeven van een correct paswoord beloont door de tabel <loggedin> aan te vullen met het IP-adres van de laptop vanwaar de connectie met de webserver gemaakt wordt.
Een tweede aspect bestaat erin, dit inloggen zo handig mogelijk te maken. Ten eerste laat de firewall dns-verkeer naar de lokale dns-servers ook voor niet-ingelogde IP-adressen door, zodat men tenminste al geen numeriek adres moet gebruiken voor de login-pagina. Bovendien worden alle http-requests vanaf niet-ingelogde adressen afgeleid naar de inlog-website. Dit gebeurt deels door een firewall-regel die alle http-verkeer van niet-ingelogde IP's afleidt naar de firewall zelf, en deels door de configuratie van de webserver, die requests voor niet-bestaande pagina's accepteert en doorsluist naar de inlogpagina. Zo kom je ook op de inlogpagina terecht als je een andere website vraagt, zoals je gewone home page; en door de inlog-site word je na een geslaagde login meteen doorgestuurd naar de origineel gevraagde pagina.
De derde component is een script dat om de paar minuten de lijst van ingelogde IP-adressen vergelijkt met de actieve leases van de dhcp-server, en de adressen die geen lease meer hebben uit de tabel van ingelogde adressen verwijdert. We kunnen er immers niet op vertrouwen dat de gebruikers braaf zullen uitloggen als ze weggaan, dus loggen we ze zelf uit als we merken dat ze weg zijn. Dit is nodig omdat we controle doen op IP-adressen, die na een tijdje opnieuw gebruikt kunnen worden, en niet meer op unieke MAC-adressen. Als we het nalieten zou een gebruiker niet hoeven in te loggen als hij een ooit reeds gebruikt adres toegewezen kreeg.
Daarmee is het ganse idee uitgelegd. De rest van deze tekst is een praktische inleiding met code snippets om de lezer te helpen zelf zulk een systeem op te zetten. Vereist is voldoende voorkennis van UNIX om een correcte minimale installatie van OpenBSD te doen. Het systeem zou ook gemakkelijk overgebracht moeten kunnen worden naar andere BSD-varianten, maar voor Linux is een complete herimplementatie nodig, aangezien de firewall-software sterk met de kernel is verbonden en pf daarom niet beschikbaar voor Linux.
Al het onderstaande kan meteen uitgeprobeerd worden met een computer met twee netwerkkaarten en een minimale installatie van OpenBSD, zonder extra software. Geef tijdens de installatie de netwerkkaart die verbonden is met het basisstation het interne IP 192.168.0.1, en de andere kaart een bruikbaar extern adres.
De laptops moeten al een IP-adres krijgen om een connectie te kunnen maken en in te loggen. Ik gebruik daarvoor de dhcp-server op de firewall zelf; dit maakt het gemakkelijk voor het login-systeem om o.a. de einddata van dhcp-leases op te zoeken. Het opzetten van een dhcp-server valt ruim buiten het onderwerp van deze tekst, maar hier vind je mijn /etc/dhcpd.conf bij wijze van voorbeeld. Vul de naam van de netwerkkaart verbonden met het basisstation in als waarde van dhcpd_flags in /etc/rc.conf of /etc/rc.conf.local.
Je zult de firewall wellicht niet enkel willen gebruiken voor het login-systeem, maar ook voor packet filtering. Dat valt ook buiten het onderwerp, maar er zitten wel voorbeeldregels bij de code snippets. Voor OpenBSD-nieuwelingen vermeld ik nog dat je de firewall automatisch laat aanzetten na reboots met pf=YES in rc.conf of rc.conf.local. Bovendien moet je om aan firewalling te doen ook net.inet.ip.forwarding=1 activeren in /etc/sysctl.conf.
De firewall aanzetten zonder reboot doe je met pfctl -e, met pfctl -F all reset je ze, en met pfctl -f /etc/pf.conf herlaad je de configuratie. Definieer in /etc/pf.conf een tabel van ingelogde IP-adressen:
table <loggedin> persist
Dan kun je, als de variabele $ext_if staat voor de benaming van de netwerkkaart "naar buiten", als volgt in 1 regel uitdrukken dat enkel de adressen in deze tabel geforward mogen worden van het interne netwerk naar het internet:
nat on $ext_if from <loggedin> -> ($ext_if)
Dit kan al meteen uitgetest worden. Met pfctl -T add -t loggedin xx.xx.xx.xx voeg je het IP-adres van een laptop toe aan de tabel, en geef je hem toegang tot het internet; met pfctl -T delete -t loggedin xx.xx.xx.xx maak je dit weer ongedaan. Je kunt de tabel bekijken met pfctl -T show -t loggedin.
De volgende regels zorgen ervoor dat domain lookups doorgelaten worden voor iedereen:
table <dns> { 134.184.15.13, 164.15.59.200 }
nat on $ext_if to <dns> port domain -> ($ext_if)
En deze regels tenslotte leiden alle http- en https-verkeer van niet-ingelogde laptops af naar de website op de firewall:
rdr on $int_if proto tcp from ! <loggedin> to port http -> 127.0.0.1 port http rdr on $int_if proto tcp from ! <loggedin> to port https -> 127.0.0.1 port https
Hier vind je een simpele maar volledige pf.conf. Alle code snippets zullen komen uit een prototype: deze zijn simpel te begrijpen en algemener bruikbaar dan mijn definitieve implementatie.
De dhcp-leases worden in OpenBSD bijgehouden in /var/db/dhcpd.leases. Oude leases worden er niet altijd meteen uit gewist: de recentste lease voor een bepaald adres is dus de laatste die je ervoor vindt in het bestand. Een lease ziet er als volgt uit:
lease 192.168.0.10 {
starts 1 2004/11/15 21:37:49;
ends 1 2004/11/15 21:57:49;
hardware ethernet 00:01:03:fa:b9:17;
uid 01:00:01:03:fa:b9:17;
client-hostname "progpc25";
}
Een attribuut van een lease opzoeken zonder het bestand te parsen doe je dus door eerst vanaf het einde van het bestand achteruit te zoeken naar het IP-adres van de lease, en dan weer vooruit naar de naam van het attribuut. In de scripts die volgen wordt dit gedaan door een vieze maar korte hack met ed, tail, grep en cut. Als je een andere dhcp-server zou gebruiken dan degene die standaard bij OpenBSD geleverd wordt, kan het nodig zijn deze hack aan te passen.
Update: de dhcp-server van OpenBSD 3.7 schrijft zijn leases incorrect weg. De eerste lease is in orde maar bij het vernieuwen kloppen de tijdsaanduidingen niet meer. Ik gebruik voorlopig de /usr/sbin/dhcpd van OpenBSD 3.6 op mijn 3.7-systemen. Ik heb de maintainer op de hoogte gebracht, maar die lijkt dit probleem geen prioriteit te vinden, om het zacht uit te drukken. Update op de update: ik ben overgestapt op ISC DHCPD, het volstaat om het pad van de leases file aan te passen en twee op te tellen bij de offsets van cut-commando's op de leases file.
De login-pagina wordt gegenereerd door 1 enkel shell-script. De gebruiker krijgt een klein webformulier aangeboden dat een naam en een paswoord accepteert en dat ook in een verborgen veld de URL bewaart waarmee de login-site origineel opgeroepen is; na een correcte login zal een refresh uitgevoerd worden om de origineel gevraagde pagina te tonen.
Het script wordt uitgevoerd onder de identiteit www, en daarom bezit het zelf niet de privileges die nodig zijn om de firewall aan te passen. Een webscript root privileges geven is nogal riskant, dus heb ik een apart scriptje dologin voorzien dat wel als root runt, en dat niets anders doet dan:
pfctl -T add -t loggedin $1
Een eenvoudige versie van het login-script kun je hier bekijken; die kan natuurlijk nog uitgebreid en verfraaid worden. Het programma authenticate dat erin gebruikt wordt is enkel toepasselijk voor onze eigen site; plug daar uw eigen paswoordcontrole in. En het gebruikte script urldecode is te vinden op Shelldorado.
Ik merk verder enkel op dat ik in de action van het webformulier eender welke "root-URL" had kunnen gebruiken, aangezien alle verkeer van niet-ingelogde machines toch afgeleid wordt naar de webserver op de firewall. Een langere URL daarentegen zou niet onmiddellijk naar het login script leiden, maar een extra redirectie nodig hebben vanwege apache (zie hieronder), en daarbij gaat de inhoud van het formulier blijkbaar verloren.
Onze firewall-configuratie stuurt alle http-verkeer door naar de webserver op de firewall zelf, maar een URL bestaat uit een machinenaam en een pad, en de redirectie door de firewall manipuleert enkel het eerste deel. (Een URL kan nog verdere argumenten bevatten, maar deze zijn hier van minder belang.) Om er nu voor te zorgen dat niet enkel http://cnn.com/ naar de inlog-pagina wordt afgeleid, maar ook http://cnn.com/2004/TECH/science/10/27/dwarf.cavewoman.ap/, moeten we onze login-pagina in httpd.conf als standaard-repliek op elke vraag om een onbestaande pagina instellen:
ErrorDocument 404 /index.cgi
Het is belangrijk dat clients de login-pagina niet cachen, anders gaat de refresh na het inloggen mislopen, die ervoor zorgt dat de origineel gevraagde pagina getoond wordt. De login-pagina is als antwoord gekomen op de vraag om die andere pagina, en ze mag dus zeker niet onder de naam daarvan in de browser van de gebruiker gecached worden. Dit bereiken we als volgt:
LoadModule headers_module /usr/lib/apache/modules/mod_headers.so Header set Cache-Control "no-cache, no-store, private" Header set Pragma "no-cache"
Om eventuele heisenbugs te vermijden stel ik apache ook maar in om geen connecties open te houden:
KeepAlive Off
Hier is een bruikbare httpd.conf. Ik heb apache geconfigureerd met virtual hosts, zodanig dat de login-site enkel te zien is via het adres 127.0.0.1, terwijl er op het externe adres een saaie tekstpagina te zien is die uitlegt wat de machine doet en wie de administrator is. De login-site staat wel degelijk op het loopback-adres, niet op het interne adres 192.168.0.1: de rdr-regels in de firewall-configuratie verwijzen het http-verkeer van niet-ingelogde laptops door naar 127.0.0.1. Merk op dat het daardoor totaal onmogelijk is voor ingelogde laptops om terug op de login-pagina te geraken; want je kunt normaal het loopback-adres van een andere machine niet bereiken.
Machines waarvan de dhcp lease vervallen is moeten automatisch uitgelogd worden. Merk op dat leases niet meteen hergebruikt worden nadat ze vrijgegeven zijn; het uitloggen kan dus gerust om de paar minuten door een cron job gedaan worden in plaats van echt onmiddellijk.
De cron job doet een pfctl -T show -t loggedin en controleert voor elk IP-adres dat dit oplevert of er nog een geldige dhcp lease voor bestaat, dus of het eindmoment van de laatste lease voor dat adres nog niet verlopen is. De begin- en eindmomenten van de leases worden uitgedrukt in Universal Coordinated Time (dus Greenwich Mean Time). Dit is het formaat overeenkomend met:
date -u +%Y/%m/%d\ %H:%M:%S
Dit formaat, beginnend met het jaar en eindigend met de seconden, en netjes geformatteerd met voorlopende nullen, heeft als voordeel dat je tijdstippen kunt vergelijken door simpel alfabetisch sorteren. Dit laat toe om ook de cron job te schrijven als een behoorlijk kort scriptje.
De hier voorgestelde oplossing veronderstelt dat het basisstation of de basisstations fysiek van de rest van het netwerk gescheiden kunnen worden door een firewall. Ze veronderstelt van het basisstation dan ook niet meer, dan dat het functioneert als een hub. Als het daarentegen niet mogelijk is de firewall tussen het gewone en het draadloos netwerk te plaatsen, kan men eventuele extra mogelijkheden van het basisstation wensen te gebruiken om iets als een VPN op te zetten tussen basisstation en firewall. In een minimalistische oplossing zou het basisstation enkel interne adressen toelaten, zodat het dataverkeer van de laptops nooit voorbij de dichtstbijzijnde router kan geraken als de firewall het niet onderschept om address translation te doen. Een fysieke scheiding van draadloos en bedraad netwerk blijft echter natuurlijk veruit te verkiezen.
Het beschreven systeem dient om publieke toegang tot standaard-internet-diensten te reguleren, niet om toegang tot een vertrouwelijk netwerk te beperken tot een kleine groep. Het inloggen zelf kan goed beveiligd worden met SSL, maar het is niet uit te sluiten dat technisch onderlegde laptop-eigenaars er zonder zelf in te loggen in slagen te profiteren van een doorgang door de firewall die voor en door een legitieme gebruiker gecreëerd is. Dit zal behoorlijk moeilijk zijn, maar de mogelijkheid mag niet uitgesloten worden, zolang als alle netwerkverkeer niet geëncrypteerd wordt.
Encryptie van alle netwerkverkeer zou het voor profiteurs een stuk moeilijker maken, maar er bestaat bij mijn weten geen geschikte methode die werkt met elk wireless device en met elk besturingssysteem, wat een vereiste is voor publieke toegang. Gebruik maken van IPSec is aan de ingewikkelde kant maar wellicht de beste benadering; dat zou alleen nog handhelds en dergelijke mogen uitsluiten. WEP-encryptie is niet geschikt, aangezien dat enkel werkt met een secret key, en het ondoenbaar is een key onder duizenden studenten te verspreiden, maar voor de buitenwereld geheim te houden.
Een systeem voor publieke toegang moet tenslotte in ieder geval gecombineerd worden met packet filtering op de grens tussen het veilige en het onveilige netwerk.
Er is een printer voorzien in de publieke lokalen, waarvoor gebruikers print credits kunnen kopen, die geregistreerd worden op basis van hun login-naam. Voor de publieke computers die we zelf beheren werkt dat goed, maar op je eigen laptop kun je natuurlijk eender welk account bijmaken en gebruiken, en zou je dus op triviale wijze op andermans kosten kunnen printen. Daarom was wireless printen aanvankelijk niet toegestaan. Ondertussen hebben we echter bedacht dat we print jobs even goed kunnen onderscheppen als blokkeren, en als we ze onderscheppen kunnen we via een eigen print spooler op de firewall de login-naam in het controlebestand van de job vervangen door de login-naam waarmee de laptop waarvan de job komt ingelogd is op het wireless netwerk; dan sturen we de print job verder naar de echte print spooler, en zo betaalt steeds de juiste gebruiker.
Dit doen we niet met de gewone print spooler van OpenBSD, maar met lprng, wat als apart package toegevoegd moet worden. In de firewall leiden we alle verkeer naar de printer-poort af naar onze eigen print spooler:
rdr on $int_if proto tcp from <loggedin> to 134.184.49.0/24 port printer -> 127.0.0.1 port printer
De print spooler op de firewall geeft de print job door aan de echte print spooler, na bewerking van de control file van de job. Dat wordt voor onze print queue epcl als volgt genoteerd in /etc/printcap:
epcl
:rm=igwe.vub.ac.be
:rp=epcl
:lpd_bounce
:control_filter=/usr/local/sbin/ig-print-filter
:server
:sh
:direct_read
:ps=status
:mx#0
:sd=/var/spool/lpd/epcl
Het script ig-print-filter puzzelt uit wat de login-naam voor de job moet worden, en zorgt voor de vervanging van de string. Je kunt het hier bekijken: het is een vieze hack van onze lokale lprng-specialist.