In the end I settled on using the php-ssh2, php-mysqli and a combination of iptables and ipset under OpenWRT (packages ipset, iptables-mod-ipset and kmod-ipt-ipset).
I created a ipset to contain the ip addresses that are allowed full internet access:
ipset create ip_whitelist hash:ip
On OpenWRT I set up the iptables to redirect any traffic http traffic to my internet server if the source IP is not in ip_whitelist:
iptables --table nat --new prerouting_mychain
iptables --table nat --insert PREROUTING -j prerouting_mychain
iptables --table nat --append prerouting_mychain --match set --match-set ip_whitelist src -j RETURN
iptables --table nat --append prerouting_mychain --dport 80 -j DNAT --to-destination <internal http server>
Now this will handle the redirection for IPs not in the ipset, but we still need to block other unauthorized traffic. This is done in the filter table of the FORWARD chain:
iptables --table filter --new forward_mychain
iptables --table filter --insert FORWARD -j forward_mychain
iptables --table filter --insert forward_mychain -j DROP (this ends up being the last rule)
iptables --table filter --insert forward_mychain --match set --match-set ip_whitelist src -j ACCEPT
In my case I want to always allow DNS so the browser doesn't freeze on a lookup:
iptables --table filter --insert forward_mychain -p udp --dport 53 -j ACCEPT
iptables --table filter --insert forward_mychain -p udp --sport 53 -j ACCEPT
Now the FORWARD chain is setup to only allow traffic from IPs in the ipset and DNS traffic.
IPs that are authorized are added to the ipset:
ipset add ip_whitelist 10.10.10.10
and removed with:
ipset del ip_whitelist 10.10.10.10
In my case I simple set an expiry on the entry to force a redirect back to the internal site for a database check:
ipset add ip_whitelist 10.10.10.10 timeout 3600
(seconds)
This timeout can also be set as a default when you first create the ipset.
All that is left to do is create a php page for the redirected http traffic, query the database and send ipset commands to the gateway routers.
First my simple database:
CREATE DATABASE Testdb;
CREATE TABLE `Clients` (
`IP` varchar(15) DEFAULT NULL,
`Created` datetime DEFAULT NULL,
`Expiry` datetime DEFAULT NULL,
`LastAccess` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY `ipidx` (`IP`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
And the redirect.php:
<!DOCTYPE html >
<html>
<head>
<title></title>
</head>
<body>
<?php
$ClientIPAddr = filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP);
$sqlQuery = "";
if (($ClientIPAddr === FALSE) || ($ClientIPAddr === NULL)) {
echo "Failed to obtain client IP [" . $_SERVER["REMOTE_ADDR"] . "]";
exit;
}
Database query / insert:
$mysqli = new mysqli("<database ip>", "dbuser", "dbpass", "Testdb");
if ($mysqli->connect_errno) {
echo "Failed to connect to MySQL: " . $mysqli->connect_error;
exit;
} else {
echo "Connected (" . $mysqli->server_info . ")<br />";
}
echo "Now for a query on " . $ClientIPAddr . ":<br />";
// Check to see if IP is in database and has not expired
$sqlQuery = "SELECT IP, TIMEDIFF(Expiry, now()) from Clients where IP=\"" . $ClientIPAddr . "\";";
if (($result = $mysqli->query($sqlQuery)) === FALSE) {
echo "Query Error (" . $mysqli->error . ") on (" . $sqlQuery . ")<br />";
exit;
} else {
echo "<h2>Query Result(" . $result->num_rows . "):</h2>";
echo "<table>";
while (($row = $result->fetch_array(MYSQLI_NUM)) !== NULL) {
echo "<tr>";
foreach ($row as $value) {
echo "<td>" . $value . "</td>";
}
echo "</tr>";
}
echo "</table>";
echo "<h1>Welcome to my Test Page</h1>";
if ($result->num_rows === 0) {
echo "<p>New(" . $result->num_rows . "): " . $ClientIPAddr . "</p>";
echo "<p>You now have full Internet access.</p>";
} else {
echo "<p>Returning(" . $result->num_rows . "): " . $ClientIPAddr . "</p>";
echo "<p>Your Internet access has been extended.</p>";
}
$result->free();
$sqlQuery = "INSERT INTO Clients (IP, Created, Expiry) VALUES (\"" . $ClientIPAddr . "\", now(),
timestampadd(hour, 24, now())) ON DUPLICATE KEY UPDATE Expiry=timestampadd(hour, 24, now());";
if (($result = $mysqli->query($sqlQuery)) === FALSE) {
echo "Query Error (" . $mysqli->error . ") on (" . $sqlQuery . ")<br />";
exit;
} else {
Add IP to ipset to authorize Internet access:
// **************** UPDATE IPSET *****************
//ssh2_connect, ssh2_fingerprint, ssh2_auth_pubkey_file, ssh2_auth_password, ssh2_exec
if (($sshConnect = ssh2_connect("<GW IP>")) === FALSE) {
$err = error_get_last();
echo $err["message"];
exit;
}
if (ssh2_auth_pubkey_file($sshConnect, "root", "/usr/share/nginx/rsa.pub", "/usr/share/nginx/rsa") === FALSE) {
$err = error_get_last();
echo $err["message"];
exit;
}
if (($stream = ssh2_exec($sshConnect, "ipset add ip_whitelist " . $ClientIPAddr)) === FALSE) {
$err = error_get_last();
echo $err["message"];
exit;
}
$stderr_stream = ssh2_fetch_stream($stream, SSH2_STREAM_STDERR);
if ((stream_set_blocking($stream, true) === FALSE) ||
(stream_set_blocking($stderr_stream, true) === FALSE)) {
$err = error_get_last();
echo $err["message"];
exit;
}
if (($resultStr = stream_get_contents($stream)) === FALSE) {
$err = error_get_last();
echo $err["message"];
exit;
}
if ($resultStr === "") { // Likely an error
if (($resultStr = stream_get_contents($stderr_stream)) === FALSE) {
$err = error_get_last();
echo $err["message"];
exit;
}
}
echo "<pre>" . $resultStr . "</pre>";
}
}
$mysqli->close();
?>
</body>
</html>
As you can see this is very rough. However it does hit all the functions I need. I hope this proved useful to someone else.