2011. május 20., péntek

Átlátszó Squid + jelszavas védelem

Adott egy cég ahol a munkatársak jelentős része laptoppal rendelkezik. A cégvezető nem szeretné, hogy a dolgozók nap közben különféle közösségi/ torrent / pornó oldalakon mászkáljanak. Erre a feladatra egy szabályokkal teletűzdelt proxy is elég lenne. A csavar az egészben, hogy a marketing osztály viszont ki szeretné használni a Facebook erejét a vállalat népszerűsítésére. Mivel a notebook-ok mozgásban vannak  a fix proxy beállítás és jelszavas bejelentkezés nem túl kényelmes megoldás. Nosza rakjunk össze egy átlátszó proxy-t és tekerjük meg web alapú bejelentkezéssel, hogy az arra jogosult kollégák hozzáférjenek a Facebook-hoz (korlátozott ideig!), viszont a torrent oldalak nekik is tiltva legyenek.

Tisztában vagyok vele, hogy a leírás alapján létrehozott szerver hemzseg a biztonsági hibáktól! (plaintext jelszavak ... stb.) Ezen okok miatt jelen formájában éles környezetben nem ajánlott használni a lent leírtakat!

A vas egy P4 Celeron 2GHz -es 3GB RAM-mal rendelkező "erőgép", két hálókártyával. Az eth0 a WAN felőli oldal, az eth1 a belső hálózat
Az alap rendszer egy Debian 5.0, alap telepítés.

Első lépésben állítsuk be a WAN oldalt, hogy szegény proxynak legyen mit szűrnie illetve szétosztania.
Ezt követően konfiguráljuk a LAN oldalt.

/etc/network/interfaces
auto eth1
allow-hotplug eth1
iface eth1 inet static
address 10.10.10.1
netmask 255.255.255.0
network 10.10.10.0
broadcast 10.10.10.255


A proxynak DHCP szerver szerep is jut. A pehelysúlyú dnsmasq a megfelelő megoldás erre a feladatra.
$ apt-get install dnsmasq

/etc/dnsmasq.conf
interface=eth1
dhcp-range=10.10.10.10,10.10.10.150,255.255.255.0,12h


A gép tűzfalát megfelelően konfigurálva érjük el, hogy a proxy átlátszónak tűnjön a kliensek felé. Erre a célra gyártunk egy scriptet, ami a gép indulásakor automatikusan beállítja a tűzfalat.

/etc/init.d/firewall.sh
#!/bin/bash
echo 1 >> /proc/sys/net/ipv4/ip_forward
iptables -F
iptables -X
iptables -P INPUT DROP
iptables -P OUTPUT DROP
iptables -P FORWARD DROP
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -i eth1 -j ACCEPT
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -j DROP
iptables -A OUTPUT -o lo -j ACCEPT
iptables -A OUTPUT -o eth1 -j ACCEPT
iptables -A OUTPUT -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -j DROP
iptables -A FORWARD -m state --state ESTABLISHED,RELATED,NEW -j ACCEPT
iptables -A FORWARD -j DROP
iptables -t nat -A PREROUTING -i eth1 -p tcp --dport 80 -j REDIRECT --to-port 3128
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

Beállítjuk, hogy minden indulásnál automatikusan fusson.

$ update-rc.d firewall.sh defaults


Szükségünk lesz valamilyen adatbázisra, hogy nyilvántartsuk a jogosult felhasználókat és a bejelentkezéseket. A több megoldási lehetőség közül válasszuk a mysql-t.

$ apt-get install mysql-server-5

A lementett adatbázis szerkezet:

auth.sql
--
-- Table structure for table `loggedin_users`
--
DROP TABLE IF EXISTS `loggedin_users`;
SET @saved_cs_client = @@character_set_client;
SET character_set_client = utf8;
CREATE TABLE `loggedin_users` (
`ip_address` varchar(100) NOT NULL,
`logintime` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
SET character_set_client = @saved_cs_client;

--
-- Table structure for table `users`
--

DROP TABLE IF EXISTS `users`;
SET @saved_cs_client = @@character_set_client;
SET character_set_client = utf8;
CREATE TABLE `users` (
`name` varchar(100) NOT NULL,
`password` varchar(100) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
SET character_set_client = @saved_cs_client;

Visszatöltjük az adatbázist:
mysql> CREATE DATABASE auth_db;
mysql> USE auth_db;
mysql> source auth.sql

Töltünk néhány tesztadatot az users táblába és továbblépünk a webszerver telepítéséhez.

$ apt-get install apache2

Létrehozzuk a jelszó bekérő oldalt:

/var/www/index.php
<?php
session_start();
$sql_username = "root"; // a MySQL felhasználó
$sql_password = "password"; // jelszó
$sql_host = "localhost"; // a kiszolgáló
$sql_db_name = "auth_db"; // adatbáis neve

$sql_connection = mysql_connect($sql_host,$sql_username, $sql_password)or die("Sikertelen kapcsolódás: " . mysql_error());
mysql_select_db($sql_db_name, $sql_connection) or die("Sikertelen adatbáis kijelölés [$sql_db_name]: " . mysql_error());
mysql_query("SET NAMES 'utf8'");
mysql_query("SET CHARACTER SET 'utf8'");
mysql_query("SET COLLATION_CONNECTION='utf8_unicode_ci'");

function addTime($_timestamp,$_amount)
{
$time= strtotime($_timestamp) + $_amount;
return date('Y-m-d H:i:s', $time);
}

if ($_POST["user_name"] && $_POST["password"])
{
$user_query = mysql_query("SELECT password FROM users WHERE name='".$_POST['user_name']."' && password='".$_POST['password']."'") or die(mysql_error());
$rows = mysql_num_rows($user_query);
if ($rows == 1)
{
mysql_query("INSERT INTO loggedin_users (ip_address,logintime) VALUES
('".$_SERVER['HTTP_X_FORWARDED_FOR']."','".addTime(date('Y-m-d H:i:s'),3)."')")or die(mysql_error());
$url=$_GET['url'];
$url=ltrim($url,"\x5c,\x27");
$url=rtrim($url,"\x27,\x5c");
header("Location:".$url);
}
else
{
$error = "<font color=red>A felhasználónév / jelszó hibás!</font>";
}
}
else
{
$error = "<font color=red>Nem adtál meg adatokat!</font>";
}

print "<html><head><script type=\"text/javascript\">
function setfocus()
{
document.getElementById(\"usern\").focus();
}</script><meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-2\"><title>Login</title>\n
</head>";
print "<body onLoad=\"setfocus();\">\n
<BR><center><font face=\"Verdana, Arial, Helvetica, sans-serif\" size=\"-2\">
<form name=\"login\" action=\"index.php?url='".$_GET['url']."'\" method=\"post\">\n
<table width=\"400\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\"><tr><td colspan=\"2\">\n
<br><br><br><center>$error</center><br></b></div></td></tr>\n
<tr><td><div align=\"right\">Felhasználó:</div></td><td>\n
<input type=\"text\" id=usern name=\"user_name\" size=\"20\" value=\"$user_name\"></td></tr><tr>\n
<td><div align=\"right\">Jelszó:</div></td><td><input type=\"password\" name=\"password\" size=\"20\"></td>\n
</tr><tr><td colspan=\"2\"><div align=\"center\"><input type=\"submit\" name=\"Submit\" value=\"Belép\"></div></td>\n
</tr></form></table></center></font></body></html>";
?>

A Squid használhat külső bejelentkezési modulokat. Mi pont ezt a tulajdonságát használjuk ki, mégpedig egy PHP nyelvű script-el. Ehhez telepíteni kell a PHP környezetet.

$ apt-get install php5
$ apt-get install php5-cli
$ apt-get install php5-mysql

A script dolga csak annyi, hogy ránézzen az adatbázisra, és egy ERR vagy OK üzenettel térjen vissza, a neki beadott IP cím alapján. Tehát a Squid küld egy kérést a script-nek, a script megnézi, hogy a kért IP bejelentkezett e már. Ha igen a válasz OK ha nem akkor ERR.

/etc/squid/auth.php
<?
if (! defined (STDIN))
{
define ("STDIN", fopen("php://stdin","r"));
}
mysql_connect("localhost","root","password") or die (mysql_error());
mysql_select_db("auth_db") or die (mysql_error());

while (!feof(STDIN))
{
$passed=0;
$line = trim(fgets(STDIN));
$ip_address = rawurldecode($line);
mysql_query("DELETE FROM loggedin_users WHERE logintime < NOW()") or die (mysql_error());
$result = mysql_query("SELECT * FROM loggedin_users") or die (mysql_error()) ;
while($row = mysql_fetch_array($result))
{
if($ip_address == $row['ip_address'])
{
$passed=$row['ip_address'];
}
else
{
$passed=0;
}
}
if($passed==$ip_address)
{
fwrite(STDOUT,"OK\n");
}
else
{
fwrite(STDOUT,"ERR\n");
}
}
?>

Végül pedig jöjjön a Squid.

$ apt-get install squid

Az első dolgunk ezután létrehozni a két listát. A bad_domains tartalma a teljesen tiltott oldalak, a grey_domains a jelszóval elérhető oldalak.

/etc/squid/bad_domains
porn
xxx
torrent

/etc/squid/grey_domains
facebook

A bejelentkezési oldal működéséhez a Squid egyik hibaoldalát alakítjuk át. Mikor az ember egy tiltott oldalra téved nem a megszokott ACCESS DENIED fogadja, hanem egy némileg módosított változata, ami tartalmazza a hivatkozást a korábban létrehozott bejelentkező oldalunkra.

/usr/share/squid/errors/English/ERR_ACCESS_DENIED
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=utf-8">
<TITLE>Bejelentkezés</TITLE>
<STYLE type="text/css"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}PRE{font-family:sans-serif}--></STYLE>
</HEAD><BODY>
<CENTER>
<H1>ACCESS DENIED</H1>
<H2>Az oldal tiltott!!</H2>
<HR noshade size="1px">
<P>
<A HREF="%U">%U</A>
<P>
<STRONG>
<A HREF='http://10.10.10.1/index.php?url=%U'>Ha van jogosultságod jelentkezz be!</A>
</STRONG><BR>
<P>
</UL>
</CENTER>

Végül a Squid konfigurációs beállításaival működésbe hozzuk a proxy-t.

/etc/squid/squid.conf
...
# INSERT YOUR OWN RULE(S) HERE TO ALLOW ACCESS FROM YOUR CLIENTS
# Example rule allowing access from your local networks.
# Adapt localnet in the ACL section to list your (internal) IP networks
# from where browsing should be allowed
#http_access allow localnet
http_access allow localhost

external_acl_type time_auth ttl=5 %SRC /usr/bin/php /etc/squid/auth.php
acl interval_auth external time_auth
acl BAD_DOMAINS dstdom_regex -i "/etc/squid/bad_domains";
acl GREY_DOMAINS dstdom_regex -i "/etc/squid/grey_domains";
http_access deny BAD_DOMAINS
http_access deny GREY_DOMAINS !interval_auth
http_access allow all

# And finally deny all other access to this proxy
#http_access deny all

...

# Squid normally listens to port 3128
http_port 3128 transparent
...

A rendszer hozzáférést ad 3600 másodperc időtartamra a grey_domains fájlban található oldalakhoz az adatbázisban szereplő felhasználóknak, majd újra be kell jelentkezniük, ha folytatni szeretnék a munkát a közösségi oldalon. Az adatbázis bővítésével megoldható a jogosult felhasználók monitorozása is (pl.: ki mikor mennyi időt töltött el a félig tiltott oldalakon ... stb.)

Nincsenek megjegyzések:

Megjegyzés küldése