Automatizando ACLs en SQUID

Buenas noches en esta entrada os traigo una forma muy sencilla de automatizar el uso de SQUID como proxy de nagevación temporal.

¿Qué es un proxy de navegación?I

Un proxy, o servidor proxy, en una red informática, es un servidor —programa o dispositivo—, que hace de intermediario en las peticiones de recursos que realiza un cliente (A) a otro servidor (C) (Wikipedia).

Proxy inverso/ Reverse Proxy
Arquitectura de un proxy inverso

Los proxys web son dispositivos que se utilizan para permitir, acelerar, inspeccionar o cachear peticiones a recursos web (HTTP/HTTPS). Esto permite reducir el ancho de banda usado, acelerar la navegación, o modificar contenido en función de reglas de capa 7. Este comportamiento es muy útil para poder controlar qué el tráfico que esta circulando por la red. Existen varios tipos de proxy según su arquitectura:

  • Proxys transparentes: Este tipo de proxys se añade de forma transparente en el flujo de la conexión de internet y el usuario sin que este tenga que configurar nada en su equipo (Las direcciones IP de origen y de destino no son alteradas)
  • Reverse Proxys: Este tipo de proxys instalan entre la entrada de internet y los servidores web, su funcionamiento se basa en cachean peticiones estáticas para liberar a los servidores web de tener que procesarlas cada vez que les llegan.
  • Forward Proxys: Este tipo d e proxys de navegación se encargan de cachear, aplicar ACLs, inspección de tráfico,.. pero es el usuario final que debe configurar el equipo para reenviar las peticiones al proxy.
Forward Proxy
Arquitectura del ForwardProxy

¿Qué es squid?

SQUID es un proxy open source (http://www.squid-cache.org/) pensado para optimizar el uso de la web implementando cachés de contenido. Puede funcionar tanto como reverse proxy como forward proxy, es decir, sirve para optimizar la navegación de internet como para mejorar el delivery de una página web.

Para instalar SQUID en Centos 7 basta con ejecutar :

    yum -y install epel-release
    yum -y update
    yum clean all
    yum -y install squid
    systemctl start squid
    systemctl enable squid

De este modo tendremos SQUID funcionando accesible desde todas las redes privadas, para una una instalación doméstica es suficiente pero si se desea hilar más fino, squid necesita algunos tuneos. Para que no se pueda conectar todo el mundo ni acceder a todo el contenido SQUID incorpora varias ACLs modificables a través de su configuración. (/etc/squid/squid.conf)


# Example rule allowing access from your local networks.
# Adapt to list your (internal) IP networks from where browsing
# should be allowed
acl localnet src 0.0.0.1-0.255.255.255	# RFC 1122 "this" network (LAN)
acl localnet src 10.0.0.0/8		# RFC 1918 local private network (LAN)
acl localnet src 100.64.0.0/10		# RFC 6598 shared address space (CGN)
acl localnet src 169.254.0.0/16 	# RFC 3927 link-local (directly plugged) machines
acl localnet src 172.16.0.0/12		# RFC 1918 local private network (LAN)
acl localnet src 192.168.0.0/16		# RFC 1918 local private network (LAN)
acl localnet src fc00::/7       	# RFC 4193 local private network range
acl localnet src fe80::/10      	# RFC 4291 link-local (directly plugged) machines

¿Porqué no deberíamos dejar todos los rangos privado abiertos?

En una empresa, no todos los segmentos de red deben tener acceso libre a internet puesto que durante un ataque, simplifica mucho la instalación de software malicioso, el control del malware y la posible exfiltración de información.

¿Cómo securizarlo?

En una época donde la agilidad no es un mérito sino un requisito, es necesario implementar una solución que pueda ser ágil sin comprometer la integridad de los sistemas de la información. Por esta razón, tener que editar el archivo de configuración cada vez que un servidor debe conectarse es no es funcional. Por suerte, SQUID implementa un tipo de ACLs para llamar a scripts/binarios externos:

external_acl_type acl_ext ttl=10 %SRC /etc/squid/acl_ext.py
acl ExtAcl external acl_ext
http_access allow ExtAcl

Con estas tres líneas, SQUID será capaz de comunicarse con un componente externo enviando la dirección IP de origen, en este caso un script de Python que se conectará a una DB MySql dónde estarán las reglas de acceso:

#!/usr/bin/python

import mysql.connector
import sys
import logging

logger = logging.getLogger('squid_auth')
logger.setLevel(logging.DEBUG)
fh = logging.FileHandler('/var/log/squid_ext.log')
fh.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
logger.addHandler(fh)


def match_acl(src):
    """USAGE:The function returns True if the user and passwd match False otherwise"""
    # Write your own function definition.
    # Use mysql, files, /etc/passwd or some service or whatever you want
    mydb = mysql.connector.connect(
        host="mydb.example.com",
        user="db_user",
        passwd="db_pwd",
        database="mydb"
    )
    mycursor = mydb.cursor()
    mycursor.execute("SELECT count(*) as c FROM squid_acls where valid_to >= now() and `start_ip` <= INET_ATON(%s) and `end_ip` >= INET_ATON(%s)",(src,src))
    myresult = mycursor.fetchall()
    logger.debug(myresult)
    return myresult[0][0] > 0


try:
    while True:
        # read a line from stdin
        src = sys.stdin.readline().strip()
        if src:
            if match_acl(src):
                logger.error("{}: OK".format(src))
                sys.stdout.write('OK\n')
            else:
                logger.error("{}: OK".format(src))
                sys.stdout.write('ERR\n')
            # Flush the output to stdout.
            sys.stdout.flush()
except Exception as ex:
    logger.error(ex)

Como backend tengo un una aplicación de Laravel que gestiona la tabla de mysql, el archivo de migración de esta tabla es:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddSquidAclTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('squid_acls', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('start_ip');
            $table->integer('end_ip');
            $table->timestamp('valid_to');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('squid_acls');
    }
}

El controlador de la API sería para esta tabla sería:

<?php

namespace App\Http\Controllers;

use App\Library\Networking\IpHelpers;
use App\Models\SquidAcl;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class SquidAclController extends Controller
{
    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        request()->validate([
            'ip' => 'required',
            'time' => 'required|numeric'
        ]);

        $range = IpHelpers::getIpRange($request->input('ip'));
        if ($range != null) {

            $squid = new SquidAcl();
            $squid->start_ip = $range['start_ip'];
            $squid->end_ip = $range['end_ip'];
            $squid->valid_to = DB::raw('NOW() + INTERVAL ' . $request->input('time') . ' MINUTE');
            $squid->save();
            return response()->json(['result' => 1]);
        }
        return response()->json(['result' => 0]);
    }
}

La clase IpHelper que se encarga de obtener los rangos de IPs contenidos en un CIDR, en una IP, o en un dominio:

<?php
namespace App\Library\Networking;

class IpHelpers
{

    public static function getIpRange($cidr)
    {
        if (self::isIpv4($cidr)) {
            $start = $end = ip2long($cidr);
            return array('start_ip' => $start, 'end_ip' => $end);
        } elseif (self::isCidr($cidr)) {

            list($ip, $mask) = explode('/', $cidr);

            $maskBinStr = str_repeat("1", $mask) . str_repeat("0", 32 - $mask);      //net mask binary string
            $inverseMaskBinStr = str_repeat("0", $mask) . str_repeat("1", 32 - $mask); //inverse mask

            $ipLong = ip2long($ip);
            $ipMaskLong = bindec($maskBinStr);
            $inverseIpMaskLong = bindec($inverseMaskBinStr);
            $netWork = $ipLong &amp; $ipMaskLong;

            $start = $netWork + 1;//ignore network ID(eg: 192.168.1.0)

            $end = ($netWork | $inverseIpMaskLong) - 1; //ignore brocast IP(eg: 192.168.1.255)
            return array('start_ip' => $start, 'end_ip' => $end);
        } else {
            putenv('RES_OPTIONS=retrans:1 retry:1 timeout:1 attempts:1');
            $cidr = gethostbyname($cidr);
            if (isset($cidr) &amp;&amp; self::isIpv4($cidr)) {
                return self::getIpRange($cidr);
            }
            return null;
        }

    }

    public static function isIpv4($ip)
    {
        return filter_var($ip, FILTER_VALIDATE_IP) !== false;
    }

    public static function isCidr($cidr)
    {
        $parts = explode('/', $cidr);
        if (count($parts) != 2) {
            return false;
        }

        $ip = $parts[0];
        $netmask = intval($parts[1]);

        if ($netmask < 0) {
            return false;
        }

        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            return $netmask <= 32;
        }

        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
            return $netmask <= 128;
        }

        return false;
    }

}

Y para finalizar la ruta a la API:

Route::middleware('auth:api')->post('admin/squid/acl', 'SquidAclController@store');

Por ejemplo, si deseamos dar permisos temorales de conexión durante 60 minutos, bastaría con lanzar:

curl https://localhost/api/admin/squid/acl --data 'ip=192.168.33.33&amp;time=60' -H "Authorization: Bearer XXXXX" --http1.1 

Donde XXXX es el token del usuario que tiene permisos para ejecutar esta acción.

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.