Captive Portal Network Authentication

A 'captive portal' system uses a web browser to authenticate a user. This avoids the need for custom software or setups on client systems that want to access a network.

The gateway server used is running OpenBSD because it has all the neccessary components.

When a client connects to the network, they have access to the local LAN and can auto configure their computer using DHCP. This is the default with most laptops. Their access outside the LAN is blocked at the gateway. Any attempt to access outside the LAN using port 80 (HTTP) is redirected to the web server on the gateway. The gateway 'captures' attempts to access the Internet from the client.

The webserver on the gateway responds to all HTTP requests by putting up a login screen. When the client authenticates to the webserver, their IP address is added to the list of addresses that are not to be redirected. The client then has access to the Internet.

Routing setup

In this example, the reserved network 192.168.3.0/24 was chosen for the internal network. This is bound to interface 'dc1'.

/etc/hostname.dc1

inet 192.168.3.1 255.255.255.0 192.168.3.255 

The external network uses 10.0.0.136 and is bound to interface 'dc0'.

/etc/hostname.dc0

inet 10.0.0.136 255.0.0.0 10.255.255.255

In /etc/sysctl.conf uncomment the line:

net.inet.ip.forwarding=1        # 1=Permit forwarding (routing) of IPv4 packets

The default gateway for the gateway server is set to the ADSL router:

/etc/mygate

10.0.0.138

The ADSL router will need to be told to route your private network to the gateway server. You'll need to set up an entry in the routing table of the ADSL router.

The gateway server does not do NAT, so if you want NAT (you will if you're using reserved network addresses) you'll need to configure the ADSL router to do NAT. That's normally the default for ADSL routers.

PF setup

The gateway server blocks unathenticated traffic and redirects unathenticated http requests to the authentication server.

With pf this simply requires /etc/pf.conf:

ext_if="dc0"
int_if="dc1"
table <registered-host> persist
set skip on lo
set loginterface $int_if
scrub in all
rdr pass on $int_if proto tcp from ! <registered-host> to port www -> 127.0.0.1 port www
block in all
antispoof quick for $int_if inet
pass in proto icmp
pass proto {udp, tcp} to port domain keep state
pass in proto {udp, tcp} to port bootpc keep state
pass in proto {udp, tcp} to port bootps keep state
pass proto tcp from any to 192.168.3.1 port http keep state
pass proto tcp from any to 192.168.3.1 port https keep state
pass log (all) from <registered-host> keep state
pass out from $ext_if keep state

This uses a table name 'registered-host' to keep a list of authenticated clients.

All traffic from registered hosts is logged for accounting.

Security note: This setup doesn't prevent setting up a IP over DNS tunnel.

Access to the pf table that is used to control network access requires using pfctl, which needs root privilege. The way this is enabled for the CGI or whatever is used by the portal application is to have sudo allow the user that the webserver runs as to execute pfctl as root without a password.

www  localhost=NOPASSWD: /sbin/pfctl *

DNS setup

The gateway server runs a local nameserver to service the local network. This is configured to forward queries to the nameservers provided by the ISP. It also has entries for the local network, most critically for itself.

/var/named/etc/named.conf

acl clients {
        localnets;
        ::1;
};

options {
        version "";     // remove this to allow version queries
        listen-on    { any; };
        listen-on-v6 { any; };
        allow-recursion { clients; };
        forward first;
        forwarders { 202.27.158.40; 202.27.156.72; }; 
};

Also add entries for your local network hosts.

/var/named/etc/named.conf

zone "go.net" {
      type master;
      file "master/go.net";
      allow-transfer { localhost; 10.0.0.129; };
};

zone "3.168.192.in-addr.arpa" {
        type master;
        file "master/192.168.3.rev";
};

/var/named/master/go.net

$TTL 86400
go.net.      IN      SOA     wag.go.net.       philip.go.net. (
       12 
        7200
        3600
        604800
        86400 )
        IN      NS      wag.go.net.
        IN      MX      10 wag.go.net.

wag     IN      A       192.168.3.1

/var/named/master/192.168.3.rev

$ORIGIN 3.168.192.in-addr.arpa.
$TTL 6h

@       IN      SOA     wag.go.net. philip.go.net.  (
                                3       ; Serial
                                3600    ; Refresh
                                900     ; Retry
                                3600000 ; Expire
                                3600 )  ; Minimum
        IN      NS      wag.go.net.
1       IN      PTR     wag.go.net.

DHCP setup

option  domain-name "go.net";
option  domain-name-servers 192.168.3.1;
subnet 192.168.3.0 netmask 255.255.255.0 {
 option routers 192.168.3.1;
 range 192.168.3.129 192.168.3.254;
}

Apache Setup

The basic httpd.conf for the webserver is altered as follows:

ErrorDocument 404 /404.html
ErrorDocument 401 /401.html
ErrorDocument 500 /500.html

The error documents are links to the login screen, so requests for any document get served up the login screen.

RewriteEngine on
RewriteCond %{HTTP_HOST}   !^wag\.go\.net [NC]
RewriteRule ^.*$            https://wag.go.net/ [L,R]

The rewrite is the magic bit. It takes any request that is not for the gateway and rewrites it to go to the gateway. It also uses https so that the logins are served up over an encrypted link so that logins cannot be easily sniffed off the network. This is particularly useful for wireless networks.

The rest of the setup is left as an exercise for the student. It could be a simple HTTP basic auth and a CGI to update the access list, or it could be a full portal application.