В статье подробно рассмотрен один из вариантов фильтрации трафика по реестру
Роскомнадзора с помощью SQUID на базе FreeBSD, состыкованного с бекбоне CISCO
по протоколу WCCP2.
После долгих размышлений на тему фильтрации, производительности (если пустить в
фильтр весь http-трафик, то фильтрация должна осуществляться на скоростях более
гигабита в секунду), была выбрана следующая схема:
Трафик HTTP и HTTPS, выходящий из облака нашей сети (в примере VLAN90) через
наш бордер CISCO, мы будем сравнивать с ACL, содержащим IP-адреса, извлеченные
из списка Роскомнадзора. В случае совпадения, мы отправляем трафик по протоколу
WCCP (Web Cache Communication Protocol) на сервер(а) SQUID. Данная схема
позволит нам избежать анализа всего трафика и анализировать только тот, который
идет на заявленные IP, что не может не сказаться положительно на общей
производительности системы.
Из минусов данной связки следует, что допустим если внесенный в реестр ресурс с
IP=3.3.3.3, доменом=bad.xxx и URL=http://www.bad.xxx/1.jpg переезжает на другой
IP, то фильтр работать не будет. Но с другой стороны актуализация списков
блокировки, это не наша проблема. Данный подход позволит нам с одной стороны не
тратится на дорогостоящие системы анализа проходящего трафика, с другой стороны
исключить "пионерский" подход блокировки всего ресурса по IP, а
блокировать только указанный в реестре Роскомнадзора контент. Связку по WCCP
лучше поднимать на приватных адресах, что бы не писать лишние ACL в SQUID .
И так, приступим к реализации. Для начала нам надо вытащить электронную подпись
с рутокена, полученного в роскомнадзоре. Для этого воспользуемся утилитой
P12FromGostCSP, и экспортируем подпись в формате PKSC#12.
Если экспорт удался, то можем перейти далее к построению системы. Если экспорт
не удался (у меня в одном случае из четырех было именно так), то автоматическая
загрузка реестра Роскомнадзора будет не доступна, и Вам придется файл качать и
выкладывать реестр руками.
Берем любой сервер на базе FreeBSD (я предпочитаю данную ОС, но ничего не
мешает Вам сделать по образу и подобию на любой *NIX машине), на котором мы
будем заниматься загрузкой базы Роскомнадзора, ее обработкой и у меня этот же
сервер как правило обеспечивает анализ и фильтрацию контента. Так как системный
OpenSSL в FreeBSD не поддерживает шифрование ГОСТ, ставим свежую версию из
портов. Это не затрагивает работоспособность всей системы, и портовый OpenSSL
прекрасно существует параллельно системному:
# which openssl
/usr/bin/openssl
# openssl version
OpenSSL 0.9.8y 5 Feb 2013
# /usr/local/bin/openssl version
OpenSSL 1.0.1e 11 Feb 2013
#
Находим файл /usr/local/openssl/openssl.cnf и пишем в конец файла:
[openssl_def]
engines = engine_section
[engine_section]
gost = gost_section
[gost_section]
default_algorithms = ALL
engine_id = gost
#dynamic_path = /usr/local/lib/engines/libgost.so
CRYPT_PARAMS = id-Gost28147-89-CryptoPro-A-ParamSet
В начало этого файла пишем :
openssl_conf = openssl_def
Копируем на сервер наш сертификат в формате PKSC#12 и конвертируем его в формат PEM:
/usr/local/bin/openssl pkcs12 -in p12.pfx -out cert.pem -nodes -clcerts
Подготавливаем структуру директорий для работы наших скриптов и копируем туда
сконвертированный сертификат:
mkdir /var/db/zapret-info/
mkdir /var/db/zapret-info/cfg
mkdir /var/db/zapret-info/arch
cp cert.pem /var/db/zapret-info/cfg/
mkdir /tftpboot
Создаем файл запрос в соответствии с требованиями Роскомнадзора (где данные
между тэгами изменены на данные вашей фирмы):
echo '<operatorName>ООО "РОГА И КОПЫТА"</<operatorName>' > /var/db/zapret-info/cfg/request.xml
echo '<inn>00XXXXXXXXXX</inn>' >> /var/db/zapret-info/cfg/request.xml
echo '<ogrn>XXXXXXXXXXXXX</ogrn>' >> /var/db/zapret-info/cfg/request.xml
echo '<email>[email protected]</email>' >> /var/db/zapret-info/cfg/request.xml
В зависимости от раскладок системной консоли полученный файл
/var/db/zapret-info/cfg/request.xml необходимо сконвертировать в кодировку WINDOWS-1251.
Скачиваем нижеприложенные скрипты. Скрипт download.pl был найден мной на
просторах Интернета и упрощен, в оригинале он был чуток более замороченный.
Задача данного скрипта - создать, подписать и отправить запрос в
Роскомнадзор и загрузить реестр после отправки.
#!/usr/bin/perl -w
use strict;
use SOAP::Lite;
use MIME::Base64;
use Sys::Syslog qw(:DEFAULT setlogsock);
use POSIX qw(strftime);
# XXX dirty!!!
binmode STDOUT, ':utf8';
binmode STDERR, ':utf8';
my $BASE = "/var/db/zapret-info";
my %n;
($n{sec},$n{min},$n{hour},$n{mday},$n{mon},$n{year},$n{wday},$n{yday}, $n{isdst}) = localtime(time());
$n{year}+=1900;
$n{mon}++;
$n{mon}=~s/^(\\d)$/0$1/;
$n{mday}=~s/^(\\d)$/0$1/;
$n{hour}=~s/^(\\d)$/0$1/;
$n{min}=~s/^(\\d)$/0$1/;
$n{sec}=~s/^(\\d)$/0$1/;
my $dt="$n{year}-$n{mon}-$n{mday}T$n{hour}:$n{min}:$n{sec}.000+04:00";
my $dd="$n{year}$n{mon}$n{mday}$n{hour}";
my $service = SOAP::Lite->service("http://www.zapret-info.gov.ru/services/OperatorRequest?wsdl");
my $reqfn = "$BASE/request-$dd.xml";
my $binfn = "$BASE/request-$dd.bin";
my $xmlreqfn = "$BASE/cfg/request.xml";
my $certfn = "$BASE/cfg/cert.pem";
my $request="" ;
open (XML, "<$xmlreqfn");
while (<XML>) { $request .= $_; }
close (XML);
die "Resuest template not found in $xmlreqfn." if (length($request)==0);
open (XMLREQ, ">$reqfn") or die "Can't create new request";
print XMLREQ "<?xml version=\\"1.0\\" encoding=\\"windows-1251\\"?>\\n";
print XMLREQ "<request>\\n\\t<requestTime>$dt</requestTime>\\n";
print XMLREQ $request;
print XMLREQ "</request>";
close XMLREQ;
system ("/usr/local/bin/openssl smime -sign -in $reqfn -out $binfn".
" -signer $certfn -outform DER -nodetach");
open XMLREQ, "<$reqfn" or die "Can't open $reqfn";
my $xmlreq = do { local $/ = undef; <XMLREQ>; };
close XMLREQ;
open XMLREQSIG, "<$binfn" or die "Can't open $binfn";
my $xmlreqsig = do { local $/ = undef; <XMLREQSIG>; };
close XMLREQSIG;
my @sendresult = $service->sendRequest(
$xmlreq,
$xmlreqsig );
if ($sendresult[0] eq 'false') {
mylog("error request $sendresult[1]");
} elsif ($sendresult[0] eq 'true') {
open (CODESTRING, ">$BASE/codestring");
print CODESTRING $sendresult[2];
close CODESTRING;
mylog("sent request $binfn: $sendresult[1]");
};
if (-e "$BASE/codestring") {
open CODESTRING, "$BASE/codestring";
my $codestring = <CODESTRING>;
close CODESTRING;
my $cnt = 0;
while(1) {
my @getresult = $service->getResult($codestring);
if ($getresult[0] eq 'false') {
mylog ("$getresult[1]");
sleep 60;
} elsif ($getresult[0] eq 'true') {
my $outarch = decode_base64($getresult[1]);
open (OUT, ">$BASE/out.zip");
print OUT $outarch;
close OUT;
unlink "$BASE/codestring";
if (-e "$BASE/out.zip") {
system("/bin/mv $reqfn $BASE/arch/");
system("/bin/mv $binfn $BASE/arch/");
system("/bin/cp $BASE/out.zip $BASE/arch/out-$dt.zip");
system("/usr/bin/unzip -o $BASE/out.zip -d $BASE/dump");
unlink "$BASE/out.zip";
mylog ("Done. Everything seems to be ok.");
exit 0;
};
mylog("Shouldn't reach. DEBUG ME!!!");
exit 255;
} else {
mylog ("getresult is unknown");
exit 255;
};
$cnt++;
if ($cnt>30) {
mylog ("too much tries, bailing out");
exit 255;
}
};
# notreached
} else {
mylog ("codestring file not found");
};
sub mylog {
my $logstring = $_[0];
my($now_string);
$now_string = strftime "%d-%m-%Y %H:%M:%S", localtime;
print(STDERR $now_string." ".$logstring."\\n");
};
В задачи скрипта xml-paser.pl входит обработка полученного реестра и подготовка
двух файлов - block.acl и denied.conf. Файл block.acl содержит правила
для нашей cisco и как правило загружается по tftp. Файл denied.conf содержит
URL для редиректора SQUID. Скрипт анализирует, префикс http или https указан в
URL и в соотвествии с данным префиксом генерирует ACL для CISCO. Для ресурсов с
множественныйми IP и/или URL, скрипт генерирует два ACL - как по порту
www, так и по порту 443 (так как реестр сам по себе сырой, и такие записи не
предусматривают однозначной трактовки какой протокол на каком IP использовать).
#!/usr/bin/perl
use XML::Simple;
use Data::Dumper ;
use Encode ;
use URI::Escape;
use Text::Iconv;
$db_path='/var/db/zapret-info/' ;
$tftp_root='/tftpboot' ;
$query_ip='XXX.XXX.XXX.XXX' ;
$simple=XML::Simple->new( KeepRoot=>1,
RootName=>'reg:register'
);
$xml=$simple->XMLin($db_path.'dump/dump.xml');
my %list = %{$xml->{'reg:register'}->{'content'}};
open(FURL, ">${db_path}denied.conf");
open(FACL, ">${tftp_root}/block.acl");
open(FTMP, ">/tmp/block.ip");
print(FACL "no ip access-list extended WCCP_REDIRECT\\n");
print(FACL "ip access-list extended WCCP_REDIRECT\\n");
print(FACL "deny tcp host ${query_ip} any\\n");
sub _url {
eval {
$url=shift;
$url=~s/ //g;
$url=uri_escape_utf8($url, "^A-Za-z0-9\\-\\._~:/\\?\\=\\&\\%");
print(FURL $url."\\n");
}
}
while ( my($key, $value) = each %list) {
my $domain=$value->{'domain'} ;
my $ip=$value->{'ip'} ;
my $url=$value->{'url'} ;
if(ref($ip) eq 'ARRAY'){
my @ip_list=@{$ip};
foreach $ip (@ip_list) {
print(FTMP "$ip eq $port\\n");
print(FTMP "$ip eq 443\\n");
}
} else {
my $proto=substr($url, 0, 5) ;
if ("\\L$proto" eq 'https') {
$port = '443';
} else {
$port = 'www';
}
print(FTMP "$ip eq $port\\n");
}
if(ref($url) eq 'ARRAY'){
my @url_list=@{$url};
foreach $url (@url_list) { _url($url); }
} else { _url($url); }
}
close(FTMP);
open(FTMP ,"sort -u /tmp/block.ip|");
while (<FTMP>) {
$ip=$_;
$ip=~s/
//g;
print(FACL "permit tcp any host $ip \\n");
}
close(FTMP);
print(FACL "deny ip any any\\n");
print(FACL "!\\n");
print(FACL "end\\n");
close(FACL);
close(FURL);
open(SER ,"${db_path}/serial");
read(SER,$serial,6);
close(SER);
if (length($serial)==0) { $serial=0; }
$serial+=1;
open(SER ,">${db_path}/serial");
printf(SER "%06d",$serial);
close(SER);
exit 0;
В начале данного файла надо установить значение переменных, в соответсвии с
Вашими настройками. Переменная $tftp_root должна быть установлена в
соответствии с настройками Вашего tftp-сервера, переменная $query_ip должна
содержать IP-адрес интерфейса сервера, через который посылается в Интернет
фильтрованный трафик, для исключения образования кольцевой обработки запросов.
Для работы скриптов нам требуется установить следующие порты :
cd /usr/ports/net/p5-SOAP-Lite; make install
cd /usr/ports/converters/p5-MIME-Base64; make install
cd /usr/ports/sysutils/p5-Sys-Syslog; make install
cd /usr/ports/textproc/p5-XML-Simple; make install
cd /usr/ports/devel/p5-Data-Dumper; make install
cd /usr/ports/converters/p5-Encode; make install
cd /usr/ports/net/p5-URI; make install
cd /usr/ports/converters/p5-Text-Iconv; make install
На данном этапе мы реализовали получение и разбор реестра Роскомнадзора в
требуемый для нас вид. Переходим к настройке нашего железа.
Конфигурируем CISCO для работы с сервером SQUID по WCCP. Группа 0 используется
для отсылки http-трафика, группа 70 - для отсылки https-трафика.
ip wccp 0 redirect-list WCCP_REDIRECT group-list 10 accelerated
ip wccp 70 redirect-list WCCP_REDIRECT group-list 10 accelerated
access-list 10 remark +++ WCCP_SQUID_PROXY +++
access-list 10 permit 1.1.2.2
Создаем пользователя, который будет грузить access-листы и настраиваем rsh.
username blocker privilege 15 password password
no ip rcmd domain-lookup
ip rcmd rsh-enable
ip rcmd remote-host blocker 1.1.2.2 root enable
Ставим из портов SQUID. При выборе опций обязательно контролируем, что WCCP и
WCCP2 протоколы включены. Создаем конфиг (в примере для версии 3.2) /usr/local/etc/squid/squid.conf
http_port 1.1.2.2:9090
http_port 1.1.2.2:3128 transparent
https_port 1.1.2.2:3129 transparent ssl-bump generate-host-certificates=on dynamic_cert_mem_cache_size=4MB cert=/usr/local/etc/squid/squid.pem
always_direct allow all
ssl_bump allow all
sslproxy_cert_error allow all
sslproxy_flags DONT_VERIFY_PEER
sslcrtd_program /usr/local/libexec/squid/ssl_crtd -s /var/squid/ssl_db -M 4MB
icp_port 0
hierarchy_stoplist cgi-bin ?
acl QUERY urlpath_regex cgi-bin \\?
cache deny QUERY
acl apache rep_header Server ^Apache
cache_mem 1 MB
cache_swap_low 90
cache_swap_high 95
maximum_object_size 1 KB
maximum_object_size_in_memory 50 KB
cache_replacement_policy heap LFUDA
cache_dir ufs /var/squid/cache 1 1 1 no-store
logfile_rotate 7
dns_nameservers 8.8.8.8 8.8.4.4
hosts_file /etc/hosts
refresh_pattern ^ftp: 1440 20% 10080
refresh_pattern ^gopher: 1440 0% 1440
refresh_pattern . 0 20% 4320
quick_abort_min 0 KB
quick_abort_max 0 KB
half_closed_clients off
acl purge method PURGE
acl CONNECT method CONNECT
acl SSL_ports port 443
acl Safe_ports port 80
acl Safe_ports port 443 # https
acl Safe_ports port 1025-65535 # unregistered ports
http_access allow manager localhost
http_access deny manager
http_access allow purge localhost
http_access deny purge
http_access deny !Safe_ports
http_access deny CONNECT !SSL_ports
http_access allow localhost
http_access allow all
http_reply_access allow all
icp_access allow all
cache_mgr [email protected]
cache_effective_group proxy
memory_pools off
log_icp_queries off
cachemgr_passwd q1w2e3r4 all
client_db off
buffered_logs on
wccp2_router 1.1.2.1
wccp2_forwarding_method 2
wccp2_return_method 2
wccp2_assignment_method 2
wccp2_service dynamic 0
wccp2_service_info 0 protocol=tcp flags=dst_ip_hash,dst_port_hash priority=240 ports=80
wccp2_service dynamic 70
wccp2_service_info 70 protocol=tcp flags=dst_ip_hash,dst_port_hash priority=240 ports=443
wccp2_address 1.1.2.2
redirect_program /usr/local/etc/squid/redirector.pl
url_rewrite_children 20 startup=10 idle=1 concurrency=0
Обратите внимание на строки wccp2_router и wccp2_address. Соответственно тут
должны быть адреса на интерфейсе со стороны CISCO и SQUID. Трафик на сервер
SQUID посылается путем замены MAC-адреса, без организации дополнительного GRE-тунеля.
Скрипт редиректора копируется в /usr/local/etc/squid/:
#!/usr/bin/perl
use IO::Handle;
use POSIX qw(strftime);
use POSIX qw(setsid);
my $db_path = "/var/db/zapret-info/";
my $block_url = "http://my.domain/block/";
my $log_file = "/var/log/squid/redirector.log";
$0 = 'redirect' ;
$| = 1 ;
my @banners ;
my @X ;
my $serial =0;
sub _log {
my $log_str = shift;
my $now_str = strftime "%d-%m-%Y %H:%M:%S", localtime;
printf(FLOG "[%s] %s\\n", $now_str, $log_str);
}
sub _rule_refresh {
open(SER ,"${db_path}/serial");
read(SER,$num,6);
close(SER);
if (length($num)==0) { $num=0; }
if ($num > $serial) {
_log("Reading file ${db_path}/denied.conf serial ${num}");
open (IN_FILE, "${db_path}/denied.conf") || die $!;
my @tmp_data = <IN_FILE>;
chomp @tmp_data;
@banners=();
push @banners, map { qr /\\Q$_\\E/ } grep { ! /^\\s*$/ } @tmp_data;
close(IN_FILE);
$serial=$num;
}
}
$SIG{INT} = \\&_rule_refresh;
open(FLOG, ">>${log_file}");
$| = 1 ;
FLOG->autoflush(1);
_log("Redirector started.");
while (<>) {
chomp ;
@X = split ;
($url, $who, $ident, $method) = @X ;
_rule_refresh ;
$url = $block_url if grep ($url=~/$_/i, @banners) ;
print "$url $who $ident $method\\n" ;
}
_log("Redirector exited.");
close(FLOG);
exit 0;
В начале данного файла мы должны установить значение переменных $db_path,
$block_url, $log_file в соответствии с Вашими настройками. $db_path содержит
путь до директории с файлом denied.conf и serial, $block_url должен содержать
адрес страницы на которую мы перекидываем клиента при блокировании ресурса.
Что бы анализировать HTTPS-трафик, нам нужно сгенерировать самоподписанный сертификат:
cd /usr/local/etc/squid/
openssl req -new -newkey rsa:1024 -days 365 -nodes -x509 -keyout squid.pem -out squid.pem
Запускаем SQUID. Если все настроено верно, то на CISCO по команде sh ip wccp,
мы увидим что-то похожее на такое:
#sh ip wccp
Global WCCP information:
Router information:
Router Identifier: 1.1.1.1
Protocol Version: 2.0
Service Identifier: 0
Number of Service Group Clients: 1
Number of Service Group Routers: 1
...
Redirect access-list: WCCP_REDIRECT
...
Service Identifier: 70
Number of Service Group Clients: 1
Number of Service Group Routers: 1
...
Redirect access-list: WCCP_REDIRECT
...
Если соединение WCCP2 установилось, мы может попробовать запустить трафик на
SQUID. Для этого в ipfw мы должны поставить правила переадресации трафика :
ipfw add 100 fwd 1.1.2.2,3128 tcp from not me to any dst-port 80 via ${wccp_intf} in
ipfw add 100 fwd 1.1.2.2,3129 tcp from not me to any dst-port 443 via ${wccp_intf} in
(где ${wccp_intf} - интерфейс для обмена wccp-трафиком)
На CISCO запускаем трафик на SQUID :
conf t
int vlan90
ip wccp 0 redirect in
ip wccp 70 redirect in
Для загрузки acl в CISCO поднимаем tftp-сервер, раскомментировав
соответствующую строку в /etc/inetd.conf и перезапустив inetd . Проверяем
загрузку правил по rsh, выполнив из консоли сервера :
( sleep 2 ; echo '' ; sleep 15 ) | rsh -l blocker 1.1.2.1 copy tftp://1.1.2.2/block.acl running-config ;
Проверяем в CISCO что правила загрузились. Если все отлично, создаем скрипт,
который мы включаем в на выполнение в крон :
#!/bin/sh
. /var/db/zapret-info/cfg/loader.conf
/scripts/download.pl
if [ $? -eq 0 ];
then
/scripts/xml-parser.pl
else
exit 254 ;
fi
if [ $? -eq 0 ];
then
( sleep 2 ; echo '' ; sleep 15 ) | rsh -l blocker ${cisco_address} copy tftp://${tftp_address}/block.acl running-config ;
else
exit 253 ;
fi
exit 0;
Создадим файл конфигурации скрипта в /var/db/zapret-info/cfg/loader.conf
echo 'cisco_address="1.1.2.1"' > /var/db/zapret-info/cfg/loader.conf
echo 'tftp_address="1.1.2.2"' >> /var/db/zapret-into/cfg/loader.conf
Если Вы все сделали без ошибок и поняли идею, Вы получили работающий полностью
в автоматическом режиме сервер фильтрации трафика, синхронизированный с
реестром Роскомнадзора, фильтрующий ВСЕ (!) ресурсы внесенные в реестр, в том
числе URI написанные на русском языке и URI на защищенных SSL-ресурсах (для
пользователя соединение отдаётся с использованием самоподписанного сертификата).
|