null

Пишем captive portal без авторизации

Однажды нам потребовалось реализовать систему, которая при первичном подключении клиента перенаправляет его на страничку с публичной офертой и предоставляет услуги доступа к Интернет только после её принятия. Мой коллега провёл обзор предметной области и не нашёл каких-то готовых решений, которые можно внедрить «малой кровью». В существующих решениях, как правило, присутствовала авторизация пользователей (в некоторых даже аутентификация) и было совершенно не понятно, как включать/выключать доступ к Интернет. Поэтому было принято решение написать собственный captive портал.

На сервере установлен Debian GNU/Linux 8.7.1 и оказалась предустановленной СУБД MySQL, которую можно использовать для обеспечения персистентности данных о клиентах. Кроме того, чтобы не плодить костылей с валидацией данных и управлением маршрутизатором из php скриптов, очевидным решением показалось повесить BEFORE INSERT и AFTER DELETE триггеры. Но по умолчанию MySQL не умеет вызывать внешние программы из триггеров, поэтому мы собрали MySQL-UDF. Сделать это можно следующим образом:

# cd $HOME/
# git clone https://github.com/mysqludf/lib_mysqludf_sys.git
# cd lib_mysqludf_sys/
# apt install libmysqlclient-dev
# sed -i.bak s/gcc/gcc -fPIC/ Makefile
# make
# cp /usr/lib/lib_mysqludf_sys.so /usr/lib/mysql/plugin/
# ./install.sh
# service mysql restart

После этого мы создали пользователя в СУБД и таблицу. Кстати говоря, если от существующей СУБД неизвестны учётные данные, в Debian есть backdoor вида /etc/mysql/debian.cnf, в котором лежат учетные данные от админа. Таблица имеет поле для временной метки и IP адреса клиента. Все эти операции можно сделать вот так:

# mysql -u debian-sys-maint -p
show databases;
create database captive_portal;
create user captive_portal with password 'qwerty';
grant all privileges on captive_portal to captive_portal;
mysql -u captive_portal -p captive_portal
create table clients (id int not null auto_increment, datetime datetime not null, ip varchar(255) not null, primary key(id));
delimiter //
create trigger ipset_add before insert on clients for each row begin declare rc int(10); set rc = sys_eval(concat('touch /tmp/', NEW.ip)); end//
create trigger ipset_del after delete on clients for each row begin declare rc int(10); set rc = sys_eval(concat('rm /tmp/', OLD.ip)); end//
delimiter ;

В триггерах вызывается обёртка для добавления/удаления IP адреса в список разрешенных. Собственно, в нашем случае это ssh-коннекция на проприетарный маршрутизатор и управление его пакетным фильтром. И в нашем случае обёртка имеет следующий вид:

#!/bin/sh

SSH=/usr/bin/ssh
KEY=/root/captive/key
USER=root
HOST=127.0.0.1

[ -z "$2" ] && exit 3

case $1 in
        add) $SSH -i$KEY -oStrictHostKeyChecking=no $USER@$HOST "pf-add $2" ;;
        del) $SSH -i$KEY -oStrictHostKeyChecking=no $USER@$HOST "pf-del $2" ;;
        *) exit 2 ;;
esac

Осталось лишь сконфигурировать веб-сервер, чтобы он перенаправлял запросы на php-страничку, которая взаимодействует с СУБД и управляет доступом клиентов к Интернет. У нас уже были установлены nginx в качестве reverse proxy и apache в качестве backend сервера, поэтому конфигурация довольно типична, добавляем в apache ports.conf строку "Listen 9999" и меняем конфигурационные файлы nginx и apache соответственно:

    location = /auth.php {
        proxy_pass http://127.0.0.1:9999;
        proxy_set_header X-Real-IP $remote_addr; 
    }
<VirtualHost *:9999>
        ServerAdmin webmaster@localhost
        DocumentRoot /var/www/auth/
        ErrorLog ${APACHE_LOG_DIR}/error_auth.log
        CustomLog ${APACHE_LOG_DIR}/access_auth.log combined
</VirtualHost>

Сама страничка, осуществляющая управление имеет следующий код:

<?php

$db = mysql_connect('localhost', 'captive_portal', 'qwerty') or die;

mysql_select_db('captive_portal') or die;

$ip = empty($_SERVER['HTTP_X_REAL_IP'];) ?
   $_SERVER['REMOTE_ADDR'] : $_SERVER['HTTP_X_REAL_IP'];

$sql = "insert into clients(datetime, ip) values (now(), '$ip');";
mysql_query($sql) or die;

mysql_close($db) or die;

$r = empty($_GET['r']) ? "http://tune-it.ru" : $_GET['r'];
echo "<script> window.location.assign('$r'); </script>";

?>

Для полноценной работы решения осталось лишь перенаправить все запросы пользователей на веб-сервер. В нашем случае это делается через веб-морду проприетарного маршрутизатора, в которой мы настроили перенаправление при нажатии на кнопку "Согласен" на URL вида http://server/auth.php?r=http://company.com Что позволяет после успешной аутентификации переключить пользователя на страницу компании. Но в общем случае, можно завернуть всё http с помощью iptables -- типичное DNAT решение, которое, считаю, можно опустить. А для периодической инвалидации IP адресов можно добавить следующую запись в cron: 

55 5 * * * echo 'delete from clients where now()-datetime  > 21600;' | mysql -ucaptive_portal -ppassword captive_portal

 

korg

 

Коротко о себе

Работаю в компании Tune-IT, администрирую инфраструктуру компании и вычислительную сеть кафедры Вычислительной ТехникиСПбНИУ ИТМО.

Интересы: администрирование UNIX и UNIX-like систем и активного сетевого оборудования, написание shell- и perl-скриптов, изучение технологий глобальных сетей.
Люблю собирать GNU/Linux и FreeBSD, использовать тайлинговые оконные менеджеры и писать системный софт.

Ничего не найдено. n is 0