Writing Squid authenticators

An authenticator that you can use in Squid is pretty simple. So if you find you need to use an authentication scheme that is not yet supported you can pretty easily write one yourself. All you need is some basic knowledge of scripting languages (bash, Perl, Python, whatever).

The authentication requests are passed to the external authenticator like this:

  • Squid launches a number of authenticators (instances of your script)
  • Each authenticator waits for input (STDIN) and parses it line by line. It never exits!
  • Squid sends lines like "USERNAME PASSWORD" to the authenticator. Just one pair of username and password per line.
  • The authenticator reads the line, decides whether the credentials are okay and either prints "OK" or "ERR" out to STDOUT.

This is an example framework for an authenticator in Perl:

use strict;

while (<STDIN>)
{
        chomp;
        my ($username, $password) = split;
        &authenticate($username, $password);
}

sub authenticate
{
        my ($username, $password) = @_;
        if (...)        # authentication ok
        {
                print "OK\n";
        }
        else    # authentication failed
        {
                print "ERR\n";
        }
}

Of course you need to add your own authentication mechanism in the &authenticate sub where the "…" are.

Nagios plugin for checking Squid

I once needed a way to make sure our Squid proxy cluster operates correctly. So I wrote a rather simple Squid check plugin for the Nagios monitoring software. André Moura has pimped it further. Feel free to use it. It’s also available from monitoringexchange.org.

Squid log reader

Reading Squid’s log files can be hard for us humans. This Perl script will help you decipher them.

Note that since Squid version 2.6 you can customize the log format. See http://www.squid-cache.org/Doc/config/logformat/ for details. I personally prefer this log format:

logformat squid %{%d.%m.%Y-%H:%M:%S}tl.%tu %6tr %>a %Ss/%03Hs %<st %rm %ru %un %Sh/%<A %mt



#!/usr/bin/perl -w
#
# Squid log reader
#
# Author: Christoph Haas <email@christoph-haas.de>
# License: GPL >= 2
#
# Version 1.01 (6.3..2005)

use Getopt::Std;
use Time::Local;
use strict;

$|=1;

my %opt=();
my $logfile;

unless ($ARGV[0])	# no arguments?
{
	&usage();
}

getopts("fu:i:ha:l:s:e:",\%opt);

if ($ARGV[0])
{
	print "Unknown option (".join('/', @ARGV).")\n" if @ARGV;
}

if (defined $opt{h})
{
	&usage;
}

if (defined $opt{l})
{
	$logfile = $opt{l};
}
else
{
	$logfile = 'access.log';
}

if (defined $opt{f})
{
	open (LOG, "tail -f $logfile|") || die "Where is '$logfile'?";
}
else
{
	open (LOG, "<$logfile") || die "Where is '$logfile'?";
}

my $starttime;
my $endtime;
if (defined $opt{s})
{
	$opt{s} =~ /^(\d+)\.(\d+)\.(\d+)-(\d+):(\d+)$/ or die "-s format looks weird\n";
	my ($startday, $startmonth, $startyear, $starthour, $startminute) = ($1, $2, $3, $4, $5);
	$startmonth--;
	$startyear-=1900;
	$starttime = timelocal(0,$startminute,$starthour,$startday,$startmonth,$startyear);
}
if (defined $opt{e})
{
	$opt{e} =~ /^(\d+)\.(\d+)\.(\d+)-(\d+):(\d+)$/ or die "-e format looks weird\n";
	my ($endday, $endmonth, $endyear, $endhour, $endminute) = ($1, $2, $3, $4, $5);
	$endmonth--;
	$endyear-=1900;
	$endtime = timelocal(0,$endminute,$endhour,$endday,$endmonth,$endyear);
}

my $line;
while (<LOG>)
{
	my ($timestamp, $junk, $ip, $errorcode, $size, $method, $url, $user,
		$routing, $mime_type)
		= split;

	# Handle options
	if (defined $opt{u})
	{
		next if $opt{u} ne $user;
	}

	if (defined $opt{i})
	{
		next if $opt{i} ne $ip;
	}

	if (defined $opt{a})
	{
		my $search = $opt{a};
		next if $url!~/$search/o;
	}

	if ($line++ == 0)
	{
		print "Log starts at ".localtime($timestamp)."\n";
	}

	if (defined $starttime && $starttime>0)
	{
		next if $timestamp < $starttime;
	}
	if (defined $endtime && $endtime>0)
	{
		exit if $timestamp > $endtime;
	}

	print "Time:           ".localtime($timestamp)."\n";
	print "IP:             $ip\n";
	print "Error:          $errorcode\n";
	print "Size:           $size Bytes\n";
	print "Method:         $method\n";
	print "URL:            $url\n";
	print "User:           $user\n";
	print "Routing method: $routing\n";
	print "MIME type:      $mime_type\n";
	print "--------------------------------\n";
}

close LOG;

######################

sub usage
{
	print "Usage: $0 [-f] [-i IP-address] [-u username]\n";
	print " -f         = follow access log file (like 'tail -f')\n";
	print " -u user    = show only accesses from this user\n";
	print " -a url     = show only accesses to this address/url\n";
	print " -i ipaddr  = show only accesses from this IP address\n";
	print " -l logfile = use this log file as input\n";
	print " -s dd.mm.yyyy-hh:mm   = start time (no entries before this time)\n";
	print " -e dd.mm.yyyy-hh:mm   = end time (no entries after this time)\n";
	print " -h         = show this usage information\n";
	exit 10;
}

Using LDAP to authenticate Squid proxy users

This article is meant to help set up LDAP authentication with a Squid proxy. Often administrators are using Squid as the central proxy for a company. For security reasons users need to enter their username and password before they are allowed to surf the internet. Firewalls usually deny accesses to the internet that would bypass the proxys. This way – if a user it doing dangerous things – you know who it was.

(Please be fair to your users and don’t lurk around what people are doing. In many countries this is forbidden anyway. If you don’t want them to surf private web sites during business-hours then use a decent blocking software instead of lurking on them.)

What is LDAP?

LDAP is short for "lightweight directory access protocol". It is not a directory service (which is often confused). Instead it is a way to access a directory through a common interface. You may already have a user directory based on OpenLDAP, Novell Directory Service (NDS) or Microsoft Active Directory. Any client that speaks LDAP can fetch information from those services.

LDAP provides a tree structure. Just an example:

+-- Company
|
+-- IT-Services
| |
| +-- George
| |
| +-- Tim
|
+-- Sales
| |
| +-- Joe
|
+-- Management
|
+-- Tina
|
+-- George

There are different types of nodes like "o" for organizations or "ou" for organizational units. To complicate matters a bit there are some limitations on how these nodes can be connected. In the above example you start your tree with the main node which is your company. This node is addressed as

o=Company

To refer to the sales department you would write the path to the node:

ou=Sales,o=Company

This path is called the "distinguished name" (dn). The dn is always unique in your directory.

And there may be different types of leafs like people or printers. You have probably noticed that I have two "George"s in the tree that are two different persons. George in the IT department has another distinguished name than George in the Management (although he probably would like to get the same salary). His distinguished name is:

cn=George,ou=IT-Services,o=Company

I hope that you now have a basic understanding of how LDAP structures look like. This article does not cover how to set up your own directory. If you like to play around with LDAP without spending money you may want to take a look at OpenLDAP (see http://www.openldap.org). 

Making Squid use LDAP for authenticating your users

As you probably already know you can make Squid ask your users for an authentication before they are allowed to surf the web. These are the parts of your configuration that set up how LDAP authentication is supposed to work: 

auth_param basic program /usr/lib/squid/ldap_auth -b o=Company -h ldapserver
   -D cn=Tim,ou=IT-Services,o=Company -w timspassword -f (&(objectclass=person)(cn=%s))
auth_param basic children 50
auth_param basic realm Web-Proxy
auth_param basic credentialsttl 1 minute

The option -b defines the context (branch of the tree) where you want to search for that users. In this example you are searching everywhere in the whole company tree. -h defines the hostname of the LDAP server to use. Next I need a user account to search the tree for the user (Novell requires that as it does not seem to allow anonymous searching). This account is provided using the -D option. -w lists the according password. And last I use a filter to search for all persons that have the name we look for (%s is the name this authenticator gets from Squid).

(Hint: this line is used in production in a network using a Novell Directory Service. There are other ways to call the ldap_auth authenticator which may be easier. But we don’t know where the user is located in the LDAP tree so we do a full search. Tim is a user account that is used to do that search.)

You also need to use ACLs to force the users to authenticate. Example: 

acl ldap-auth proxy_auth REQUIRED
http_access deny !ldap-auth

This would deny everybody access who is not authenticated (thus the "!").

By the way: an authenticator is just any program that reads lines from STDIN (like the console) that look like this

username password

and return either OK (if the credentials are okay) or ERR if something went wrong. 

Using LDAP groups

In addition to simple authentication you may also want to grant different privileges to different users. The easiest way is to use LDAP groups. A group is just a list of dinstiguished names. Example group:

  • dn=cn=Tim,ou=IT-Services,o=Company
  • dn=cn=Tina,ou=Management,o=Company

Assume that these two users are allowed to use Google where all other users are not allowed. First you need to define the LDAP group mapping: 

external_acl_type ldapgroup %LOGIN /usr/lib/squid/squid_ldap_group -b o=Company
   -f (&(objectclass=person)(cn=%v)(groupMembership=cn=%a,ou=Proxygroups,o=Company))
   -D cn=Tim,ou=IT-Services,o=Company -w timspassword -h ldapserver

The filter expression looks a little complicated. It just means that we are looking for any user (person) with the name we are looking for (%v) who is a member of the group we are looking for (groupMembership). You can now just create any number of groups in the tree like

cn=googleallowed,ou=Proxygroups,o=Company

Then this pile of ACLs should block off everyone from google.com who is not a member of that group: 

acl ldapgroup-googleallowed external ldapgroup googleallowed
acl google dstdomain google.com
http_access deny google !ldapgroup-googleallowed

You may be curious how such a group authorizator works. Simple. It just reads line after line from STDIN (like the console) where each line looks like

username groupname

and returns either OK or ERR depending on whether the user is a member of that group or not.

How to keep your users from bypassing your proxy

Many administrators do not seem to recognize the true purpose of a proxy. That is to make sure nothing but a certain protocol is used through the proxy. Squid is a HTTP proxy. It makes sure that people use nothing but HTTP through it.

I apologize if I sound rude but from listening to other administrators on IRC (#squid on irc.freenode.net) I learned that they have no idea how to actually use Squid properly. Many people think that Squid is a SOCKS proxy or an anonymous proxy or a firewall or whatever. No, it’s a HTTP proxy – and a damn good one, too.

Mistake 1: Interception caching

Interception caching (also known as transparent proxying) means that your firewall redirects all connections on TCP port 80 to the Squid. The browser believes that it is talking directly to the web server while in fact it is talking to the Squid.

Advantages:

  • the users do not need to change their browser settings

Disadvantages:

  • you can not use authentication
  • all other ports than 80 will not be proxied

I often hear excuses like “I have 400 workstations that I would need to touch to change the proxy settings”. If you really do not have control over your workstations then you have a much more serious security problem than surfing. Interception caching is very convenient. But when you care about security it is not an option.

Mistake 2: Direct accesses are allowed

It also seem to be common (and pretty bad) that network admins set up a Squid proxy while still allowing direct connections from the LAN to the internet. Even if the network admin preconfigures the end users’ workstations to use the proxy it is simple to switch it back off and access the internet directly.

The simple rule should be: there are no direct connections between your local network and the internet. All connections go through securely configured proxy servers.

Solution

Often users on IRC (#squid) ask how they can prevent their users from changing the browser’s proxy settings back. This is the wrong question! The correct solution is to block direct accesses at the firewall and run all connections through the proxy. Your firewall rules should look something like this:

  1. Accept connections from your LAN to your Squid on port 3128
  2. Accept connections from your Squid to the Internet
  3. Deny any other connections

Of course you will have additional rules that allow ICMP or relay emails. But these are the important rules regarding proxy surfing. Now your users will either use the proxy to surf the internet or just get no connections. Combine this with authentication and you finally have control about what the workstations do. This is not only useful to torture your users or lurk what they are surfing but rather keep simple spyware away that either does not know how to use a proxy or does not have the username and password needed to login.

Another advantage of having a proxy setup like this:

You can use private IP addresses (like from 10.0.0.0/8) in your local network. Just the proxy needs to get an IP address that is valid on the internet.

How Squid ACLs work

For less experienced Squid administrators the concept of ACLs can be confusing at first. But they offer a great way of controlling who is allowed to access which web pages when.

ACLs

First you need to define certain criteria like accesses from the marketing department or accesses to google.com or need to authenticate. There are certain types of ACLs for that purpose. The complete list of ACLs can be found at http://www.visolve.com/squid/squid24s1/access_controls.php

The syntax of an acl is:

acl name type definition1 definition2 definition3 ...

Examples:

acl accesses_to_google dstdomain .google.com
acl accesses_to_search_engines dstdomain .yahoo.com .google.com .vivisimo.com
acl accesses_from_marketing_department src 10.52.0.0/16
acl need_to_authenticate proxy_auth

You can also use lists of definitions that are stored in files on your hard disk. Let’s assume you have a list of search engines URLs that you want to allow:

/etc/squid/search-engines-urls.txt:
.google.com
.yahoo.com
.altavista.com
.vivisimo.com

Then the ACL for that file would look like:

acl accessess_to_search_engines dstdomain "/etc/squid/search-engines-urls.txt"

The quotes are important here to tell Squid it needs to look up definitions in that file.

Using the ACLs: http_access

Defining the ACLs alone does not actually block anything – it’s just a definition. ACLs can be used in various places of your squid.conf. The most useful feature is the http_access statement. It works similar to the way a firewall would handle rules. For each request that Squid receives it will look through all the http_access statements in order until it finds a line that matches. It then either accepts or denys depending on your setting. The remaining rules are ignored.

The general syntax of an http_access line is:

http_access (allow|deny) acl1 acl2 acl3 ...

Example:

http_access allow accesses_from_admins
http_access deny accesses_to_porn_urls
http_access allow accesses_during_lunchtime
http_access deny all

This would allow accessing from the admins (whatever that ACL looks like – probably a src ACL pointing to the subnet where the admin workstations are in). For everyone else it will deny accesses to porn URLs. Then it would allow accesses from everyone to every web site during lunch time. And finally all other accesses would be denied.

Combining ACLs (AND/OR)

Often you need to combine ACLs. Let’s say you want to allow access to google.com only for the back office. This combines two ACLS with an AND. This would look like this:

http_access allow accesses_to_google.com accesses_from_back_office

If you wanted to use an OR and say either accesses from the back office or accesses to google.com are allowed then the line would look like this:

http_access allow accesses_to_google.com
http_access allow accesses_from_back_office

To summarize: AND means putting the conditions in one line. OR means using seperate lines.

Custom error pages (deny_info)

By default when you deny access the user gets the error page that is stored in the ERR_ACCESS_DENIED file. But luckily you can define your own custom error pages and display them when you deny certain accesses. A simple example:

acl google dstdomain google.com
deny_info error-google google
http_access deny google

Put an error page into the directory where the HTML files are stored (look for error_directory in your squid.conf) and name it error-google. If the user tries to access www.google.com the access is denied and your error page is shown.

Careful when you combine ACLs on a http_access line. Example:

acl google dstdomain google.com
acl admin src 10.0.5.16
deny_info google error-google
http_access deny admin google

This will deny access only for the user from the IP address 10.0.5.16 when www.google.com is accessed. As you can see I have combined the ACLs admin and google. In such a combination the last ACL in the line is taken into account for lookups of deny_info. So it’s important that you define a deny_info for the google ACL.

Re-Authentication control

Usually when a user is authenticated at the proxy you cannot “log out” and re-authenticate. The user has to close and re-open the browser windows to be able to re-login at the proxy. A simple configuration will probably look like this:

acl my_auth proxy_auth REQUIRED
http_access allow my_auth
http_access deny all

Now there is a tricky change that was introduced in Squid 2.5.10. It allows to control when the user is prompted to authenticate. Now it’s possible to force the user to re-authenticate although the username and password are still correct. Example configuration:

acl my_auth proxy_auth REQUIRED
acl google dstdomain .google.com
http_access allow my_auth
http_access deny google my_auth
http_access deny all

In this case if the user requests www.google.com then the second http_access line matches and triggers re-authentication. Remember: it’s always the last ACL on a http_access line that “matches”. If the matching ACL has to do with authentication a re-authentication is triggered. If you didn’t want that you would need to switch the order of ACLs so that you get http_access deny my_auth google.

You might also run into an authentication loop if you are not careful. Assume that you use LDAP group lookups and want to deny access based on an LDAP group (e.g. only members of a certain LDAP group are allowed to reach certain web sites). In this case you may trigger re-authentication although you don’t intend to. This config is likely wrong for you:

acl ldap-auth proxy_auth REQUIRED
acl ldapgroup-allowed external LDAP_group PROXY_ALLOWED

http_access deny !ldap-auth
http_access deny !ldapgroup-allowed
http_access allow all

The second http_access line would force the user to re-authenticate time and again if he/she is not member of the PROXY_ALLOWED group. This is perhaps not what you want. You rather wanted to deny access to non-members. So you need to rewrite this http_access line so that an ACL matches that has nothing to do with authentication. This is the correct example:

acl ldap-auth proxy_auth REQUIRED
acl ldapgroup-allowed external LDAP_group PROXY_ALLOWED
acl dummy src 0.0.0.0/0.0.0.0

http_access deny !ldap-auth
http_access deny !ldapgroup-allowed dummy
http_access allow all

This way the second http_access line still matches. But it’s the dummy ACL which is now last in the line. Since dummy is a static ACL (that always matches) and has nothing to do with authentication you will find that the access is just denied.

© 2021 workaround.org - Proudly powered by theme Octo