Autentificación SSO en Squid con eDirectory o LDAP

Hola!!

Esta vez vamos a ver algo sobre autentificación de Squid con eDirectory y LDAP de forma que los usuarios conectados a eDirectory no tengan que introducir el usuario/clave para acceder a través del proxy. Si el usuario no está conectado o no existe, se utilizará la consulta directa al LDAP o autentificar mediante la consulta a un archivo del tipo htpasswd que lo definiremos como «usuarios externos».

Además de la alternancia entre sistemas de autentificación, también es posible indicar usuarios que estarán bloqueados.

Para la autentificación con eDirectory nos ayudamos de un script en Perl que obtiene los datos de los usuarios conectados. Este script guarda los usuarios en el archivo /usr/local/etc/squid3/ipusrs y debemos de crearlo en /usr/local/sbin/netaddr_ldap.pl:

#!/usr/bin/perl -w
## netaddr_ldap.pl
##
## Perl script to read networkAddress and uid of connected users from eDirectory
## and save it to a file in the format IP:USER
##
##
## (c) 2013 - nuxsmin - http://cygnux.org
##
## Esta obra está bajo una licencia de Creative Commons Reconocimiento-NoComercial-CompartirIgual 3.0 Unported.
#
## This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

## LDAP servers
$ldapServers =  ["ldap-server1.local.com", "ldap-server2.local.com","ldap-server3.local.com"];

##$filter =  "(&(objectclass=person)(nGWObjectID=*))";  ## Filter by person and nGWObjectID (Groupwise ID)
$filter =  "(&(objectclass=person)(uid=*))"; ## Filter by person and uid (Novell ID)

## File to save data from searching
$file = "/usr/local/etc/squid3/ipusrs";

use Net::LDAP;

$ldap = Net::LDAP->new($ldapServers, timeout=>5)  
    or  die "Can't connect to any LDAP server: $@";

print " Connected to ".$ldap->host()." ...\n";

$mesg = $ldap->start_tls();
$mesg = $ldap->bind;    ## Anonymous bind to LDAP

## If we need auth ...
#$mesg = $ldap->bind( "cn=user,o=Novell", password => "mypassword");

## Do search. We remove the base to search in the whole directory
$mesg = $ldap->search(
#            base   => "$basedn",
            scope  => "sub",
            deref => "never",
            filter => "$filter",
#            attrs => ['networkAddress','nGWObjectID'] ## Use when filtering by GroupWise ID
            attrs => ['networkAddress','uid']            
 );

$mesg->code && die $mesg->error;

open (FILE,">$file") || die "ERROR: Can't open file $file\n";

print " Retrieving connected users ...\n";

## Uncomment to get a full entry dump
#foreach $entry ($mesg->entries) { $entry->dump; }

## We read LDAP entries and retrieve the IP address and UID
foreach $entry ($mesg->entries) {
  foreach $attr ($entry->attributes) {
    if ( $attr eq "networkAddress" && $entry->get_value($attr)) {
      $uid = $entry->get_value("nGWObjectID");
      $uid =~ s/ +/_/g;
      #print "\nuid=".$uid;
      next if $uid eq "admin" || $uid eq "usuarioiprint";

      ## Each entry can have serveral IP addresses
      foreach $val ($entry->get_value($attr)) {
        #printf "Line: %vd\n", $val;
        ($type,$rest) = split(/#/,$val);
        #printf "Type: %d # Rest: %vd\n", $type, $rest;

        ## We only will retrieve the types 1 (IP) and 9 (TCP)
        if ($type==1 || $type==9) {
          ## The last 4 bytes are the IP address for the types IP and TCP
          $addr = substr($rest,-4);
          printf "Type: %d Addr: %vd\n", $type, $addr;

      ## Translate the IP address to a readable format
      $ip_addr = sprintf("%vd", $addr);

      ## We can exclude invalid IP addresses 
      next if $ip_addr =~ /192.168.1./;

      ## Write to the file one line per IP address in the format IP:UID
      if ( length ($ip_addr) > 3 ){
            print FILE $ip_addr.":".$uid."\n";
      }
        }
      }
    }
  }
}

## Close the file and LDAP connection
close (FILE);
$mesg = $ldap->unbind;

print " Processing duplicated entries ...\n";

## Function to delete duplicated entries. We do a backup of the file.
my %seen  = ();
{
   local @ARGV = ($file);
   local $^I = '.bak';
   while(<>){
      $seen{$_}++;
      next if $seen{$_} > 1;
      print;
   }
}

print " List of connected users was saved in the file \'$file\'\n";

Es necesario modificar la variable $ldapServers para ajustarla a los servidores de eDirectory que tengamos

Una vez obtenidos los datos desde el directorio LDAP, vamos a crear el script que va a realizar a la autentificación múltiple. Lo crearemos en /usr/local/squid_multi_auth.pl:

#!/usr/bin/perl 
##
## squid_multi_auth.pl
## 
## Perl script to authenticate with several subsystems
##
##
## (c) 2013 - nuxsmin - http://cygnux.org
##
## Esta obra está bajo una licencia de Creative Commons Reconocimiento-NoComercial-CompartirIgual 3.0 Unported.
##
## This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.

#use Digest::MD5 qw(md5 md5_hex md5_base64);
use URI::Escape;
use MIME::Base64;
use Apache::Htpasswd;
use Net::LDAPapi;

$| = 1;

## Set these vars to your environment. Files must exist
my $ldapServer = "ldap-server.local.com"; ## LDAP server address
my $eDirFile = "/usr/local/etc/squid3/ipusrs"; ## File with users connected to eDirectory (generated by netaddr_ldap.pl)
my $extUsrFile = "/usr/local/etc/squid3/extusrs"; ## File with manually added users (generated with htpasswd)
my $denyFile = "/usr/local/etc/squid3/deny_users"; ## File with denied users (one user per line)

my $log = "/tmp/squid_multi_auth.log"; ## Log file


while () {
	chomp;
	my ($inputIp,$inputUser,$inputAuth) = split();
	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
	
	open ($fhLog, ">>",$log) || die "ERROR: Can't open file $log\n";
	
	printf $fhLog "-- %4d-%02d-%02d %02d:%02d:%02d\n",$year+1900,$mon+1,$mday,$hour,$min,$sec;
	
	if ( $ARGV[0] eq "edir" ){
		if ( ! $inputIp ){
			printf "ERR\n";
			printf $fhLog "ERR no_ip\n";
		} else {
			&auth_edir($inputIp, $fhLog);
		}
	} else {
		if ( ! $inputIp || ! $inputUser || ! $inputAuth ){
			printf "ERR\n";
			printf $fhLog "ERR no_ip | no_user | no_auth\n";
		} else {
			if( &check_deny($inputUser, $fhLog) == 0 ){
				# Retrieve the password from auth header
				$inputAuth = uri_unescape($inputAuth);
				($type,$authData) = split(/ /, $inputAuth);
				$authString = decode_base64($authData);
				($authUser,$authPass) = split(/:/, $authString);

				# Test all available auth methods
				&auth_ldap(lc($authUser), $authPass, $fhLog) || &auth_external(lc($authUser), $authPass,$inputIp, $fhLog);
			}
		}
	}
	
	print $fhLog "--\n";
	close ($fhLog);
}

# Function to make SSO auth with eDirectory users file
sub auth_edir{
	my $srcip = shift;
	my  $fhLog = shift;

	open (FH, $eDirFile) || die "ERROR: Can't open file $eDirFile\n";

	print $fhLog "auth_edir: $srcip\n";

	my @buf = ;
	close (FH);
	
	chomp(@buf);
	
	my @lines = grep (/$srcip/,@buf);

	if ( scalar(@lines) == 0 || ! $srcip ){
		print $fhLog "ERR log=no_ip\n";
		print "ERR log=no_ip\n";
	} else {
		foreach $conexion (@lines){
			
			($ip,$uid) = split(/:/,$conexion);
			$uid=lc($uid);

			if( &check_deny($uid, $fhLog) == 0 ){ 
				print $fhLog "OK user=$uid log=acl_edir_users\n";
				print "OK user=$uid log=auth_edir\n";
			}
		}
	}
}

# Function to make external users (manually added) auth
sub auth_external{
	my $inputUser = shift;
	my $inputPass = shift;
	my $inputIp = shift;
	my $fhLog = shift;
		
	open (FH, $extUsrFile) || die "ERROR: Can't open file $extUsrFile\n";

	print $fhLog "auth_ext: $inputUser - $inputIp\n";

	my @buf = ;
	close (FH);
	
	chomp(@buf);

	my @line = grep (/^$inputUser:/,@buf);
	
	if ( scalar(@line) == 0 || $inputUser  eq "" ){
		print $fhLog "ERR log=no_ext_user\n";
		print "ERR log=no_ext_user\n";
	} else {
		$chkPass = new Apache::Htpasswd({passwdFile => $extUsrFile, ReadOnly   => 1});
		
		if ( ! $chkPass->htCheckPassword($inputUser, $inputPass) ){
			print "ERR user=$inputUser log=auth_externo\n";
			print $fhLog "ERR user=$inputUser log=auth_externo\n";
		} else {
			print "OK user=$inputUser log=auth_externo\n";
			print $fhLog "OK user=$inputUser log=auth_externo\n";
		}
	}
}

# Function to make LDAP direct auth
sub auth_ldap{
	my $uid = shift;
	my $password = shift;
	my $fhLog = shift;

	print $fhLog "auth_ldap: $uid\n";
	
	if ( !$uid || !$password ) {
		print $fhLog "auth_ldap: no uid | no pass\n";
		return undef;
	}

	$ldap = new Net::LDAPapi($ldapServer);

	# Anonymous LDAP bind
	if ($ldap->bind_s != LDAP_SUCCESS){
		$ldap->perror("bind_s");
		print $fhLog "auth_ldap: error connect\n";
		print "ERR\n";
		return undef;
	}

	# Look for user by uid
	if($ldap->search_s("",  LDAP_SCOPE_SUBTREE, "uid=$uid", ["c"], 1) != LDAP_SUCCESS){
		$ldap->perror("search_s");
		print $fhLog "auth_ldap: error search\n";
		return undef;
	}

	# Check is there is only one entry
	if ($ldap->count_entries != 1){
		$ldap->unbind;
		print $fhLog "auth_ldap: error entries\n";
		return undef;
	}

	# Select the first entry
	if ($ldap->first_entry == 0){
		$ldap->unbind;
		return undef;
	}

	# Retrieve the entry DN to make LDAP login with the user
	if (($dn = $ldap->get_dn) eq ""){
		$ldap->unbind;
		print $fhLog "auth_ldap: error dn\n";
		return undef;
	}

	# Connect to LDAP with the user data
	if ($ldap->bind_s($dn,$password) != LDAP_SUCCESS){
		$ldap->unbind;
		print $fhLog "auth_ldap: error login\n";
		return undef;
	}

	$ldap->unbind;
	print $fhLog "OK user=$uid log=auth_ldap\n";
	print "OK user=$uid log=auth_ldap\n";
	return 1;
}

# Function to check if the user is blocked (denied users)
sub check_deny{
	my $user = shift;
	my $fhLog = shift;
	
	open (FH, $denyFile) || die "ERROR: Can't open file $denyFile\n";
	my @buf = ;
	close (FH);
	
	print $fhLog "check_deny: $user\n";

	if ( grep (/^$user$/,@buf) ){
		print "ERR log=deny_users\n";
		print $fhLog "ERR log=deny_users\n";
		return 1;
	} else {
		#print FHLOG "ERR\n";
		#print "ERR\n";
		#close (FHLOG);
		return 0;
	}
}

Las variables a tener en cuenta son:

  • $ldapServer : nombre o ip del servidor LDAP.
  • $eDirFile : archivo donde se encuentran los usuarios conectados a eDirectory.
  • $extUsrFile : archivo de claves para los usuarios no-LDAP. Se genera con la utilidad htpasswd.
  • $denyFile : archivo con la lista de usuarios bloqueados (uno por línea).
  • $log : archivo de log

Los archivos deben de existir antes de ejecutar el script

El posible que se necesite descargar/instalar los módulos de perl necesarios

Para indicarle a Squid que utilice el sistema de autentificación múltiple, es necesario modificar la «Autentificación Básica» para que utilice un «fake auth», para que siempre responda «OK» a la petición de usuario/clave y luego delegarle ésta a una ACL externa que es la que ejecuta el script squid_multi_auth.pl. Hay que modificar el archivo /etc/squid3/squid.conf:


auth_param basic program /usr/local/sbin/basic_fake_auth
auth_param basic children 5
auth_param basic realm Proxy Internet
auth_param basic credentialsttl 24 hours

external_acl_type IPUser ttl=3600 children=10 negative_ttl=300 %SRC /usr/local/sbin/squid_multi_auth.pl edir
external_acl_type ExternalUser ttl=14400 children=10 negative_ttl=120 %SRC %LOGIN %{Proxy-Authorization} /usr/local/sbin/squid_multi_auth.pl

La primera ACL autentifica a los usuarios de eDirectory en modo SSO y la segunda consulta al LDAP directamente o al archivo de usuarios externos.

Es posible que necesites instalar/descargar los módulos de Perl necesarios

Espero que os sirva…hasta otra 😉

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Imagen CAPTCHA

*