Now that we have a list of whitelisted IPs we can use, it makes sense for us to create another end-point in order to return a list of all the whitelisted IPs. Any servers under our control can then send requests to this end-point to receive a list of the current IP whitelist and add them to iptables or other ACLs. This end-point will be created in the file whitelist-get.php.

The first thing I realised is that I want successful authentication to occur before returning a list of whitelisted IPs. Instead of copy/pasting the existing authentication code into the new file and duplicating code, it is better that we add the authentication code to its own file (auth.php) and then requiring auth.php in both whitelist-url.php and whitelist-get.php.

I also decided that the functionality for whitelist-url.php and whitelist-get.php are quite distinct with no real overlap, so it makes sense to have two user types. The first type would be end users who only need to whitelist their IPs and the second type would be servers which only need to grab a current list of whitelisted IPs. As such I have separated their authenticated file directory structure which allows slightly different permissions.

So here is the new auth.php file:

<?php

function login($type) {

	function auth_fail() {
		header('HTTP/1.1 403 Forbidden');
		echo "You are forbidden!\n";
		exit;
	}

	if ( !isset($_GET['user']) || !isset($_GET['auth']) ) 
	{ 
		auth_fail();
	}

	$user = $_GET['user'];
	$pass = $_GET['auth'];
	if ( $type == "user" )
    {
    	$auth_file = "data/auth/user/{$user}";
    }
    elseif ( $type == "infra" ) 
    {
    	$auth_file = "data/auth/infra/{$user}";
	} 
	else 
	{
		auth_fail();
	} 

	if (!file_exists("$auth_file")) {
		auth_fail();
	}

	$userauth = file_get_contents($auth_file);

	if ( $userauth != $pass ) {

		auth_fail();
	}

	return $user;
}

You can see above that I have placed all the authentication code into the login function which also requires a type to be passed to it. The type is either an end user who needs their IP whitelisted (user) or a server that needs a list of IPs to whitelist (infra). The type is then used to define where the authentication file resides.

I also used this chance to cleanup the duplicate code used to return the forbidden response in multiple locations. The 403 forbidden response was added as its own function auth_fail() and called as needed.

With the authentication now sorted out the rest is very simple in our whitelist-get.php file:

<?php 

# Include authentication code for login function
require 'auth.php';
$user = login("infra");

$path    = 'data/ip-data/';
$files = array_diff(scandir($path), array('.', '..'));

echo "---Whitelisted IPs---\n";

foreach($files as $file){
    echo file_get_contents("{$path}{$file}");;
}

echo "----End Whitelist----\n";

After including auth.php and setting the user type to infra all the code does is grab the list of saved user IP whitelist files from ‘data/ip-data/’ and echo their contents. The only additional thing I added was to add a ‘header’ and ‘footer’ to the IP list in order to allow some basic validation by the servers pulling this data.

And just for good measure I have included the updated version of whitelist-url.php after the authentication code was refactored:

<?php 

# Include authentication code for login function
require 'auth.php';
$user = login("user");

$whitelist_file = "data/ip-data/{$user}";
$current_ip = $_SERVER['REMOTE_ADDR'];
$whitelist_max = 5;

if (file_exists($whitelist_file)) {
	$user_whitelist = file($whitelist_file, FILE_IGNORE_NEW_LINES);
}
else {
	$user_whitelist = [];
}

if (in_array($current_ip, $user_whitelist)) {
    echo "$current_ip already whitelisted\n";
    exit;
}

elseif (count($user_whitelist) < $whitelist_max ) {
	array_unshift($user_whitelist , $current_ip);
}
elseif (count($user_whitelist) == $whitelist_max) {
	array_unshift($user_whitelist , $current_ip);
	array_pop($user_whitelist);
}

$i = 0;
while ($i < count($user_whitelist))
{
	if ($i == 0) {
		file_put_contents($whitelist_file, $user_whitelist[$i] . "\n");
	}
	else {
		file_put_contents($whitelist_file, $user_whitelist[$i] . "\n", FILE_APPEND);   
	}
    $i++;
}

echo "$current_ip added to whitelist\n";

?>

The web api for our whitelisting functionality is now complete. End users can whitelist their IPs as needed and our infrastructure servers can requests the list of IPs to whitelist. In the 4th and final part of this tutorial I will show how our infrastructure servers can pull this list of IPs and allow access using iptables and ipset.