В данной статье я хотел бы рассказать об одном из способов раздачи wifi-интернета вашим гостям в офисе, кафе, дома и вообще где угодно. Особенность данного способа в том, что он требует минимум оборудования и простой в настройке. Сама по себе идея не оригинальна и схема не нова, я узнал о таком способе из книги «The book of PF» и я тут же занялся своей реализацией данной идеи. Сама идея заключается в следующем: мы переводим нашу точку доступа в режим сетевого моста, на wifi пароль не устанавливаем, т.е. делаем его открытым, наш сервер(шлюз) выдает wifi-клиентам IP-адреса и прочие настройки, после чего весь веб-трафик от клиента перенаправляется на определенный порт нашего шлюза, где работает специальное приложение, которое проводит авторизацию пользователей. После успешной авторизации IP-адрес пользователя добавляется в специальную таблицу в пакетном фильтре, таким образом, ему предоставляется доступ в интернет. В чем отличие данного способа от традиционных? Например, при использовании wpa2-psk и подобных алгоритмов для ограничения доступа к wifi-сети мы имеем всего один общий пароль, который необходимо сообщать каждому вашему гостю, также мы не можем ограничить по времени пользования сетью и т.д. Еще один вариант - IEEE 802.1X, но он достаточно громоздкий и не любое оборудование его поддерживает, плюс также необходима некоторая доработка для контроля и ограничения ваших пользователей.
Веб-интерфейс авторизации, мониторинга и управления акаунтами написан на языке программирования python 2.6 (2.7 также подойдет) c использование веб-фреймворка django 1.3. Веб-сервер nginx + uwsgi. В качестве серверной ОС используется FreeBSD, а в качестве пакетного фильтра PF. Я не вижу никаких препятствий, чтобы использовать мой веб-интерфейс в связке с linux/iptables или FreeBSD/ipfw, доработки потребует только исполняемый файл wificmd, который выполняет добавление/удаление записей в пакетный фильтр.
Начнем с настройки сети. В целях безопасности нам необходимо сделать так, чтобы точка доступа имела доступ только к шлюзу. Реализовать это можно несколькими способами: напрямую подключить точку доступа к отдельной сетевой карте шлюза, либо настроить VLAN, если ваше сетевое оборудование это поддерживает. В моей реализации используется второй вариант. Создан отдельный VLAN с именем openwifi, точка доступа подключена к нетегированному порту, а порт шлюза добавлен как тегированный. Логическая схема подключения показана на рисунке ниже.
Теперь, как уже говорилось выше, точка доступа переводится в режим сетевого моста. Далее нужно установить весь сопутствующий софт: nginx, uwsgi, pip (/usr/ports/devel/py-pip), isc-dhcpd, python, sudo. Плюс на свой вкус надо выбрать СУБД, в которой будут храниться данные, самый простой вариант – это sqlite3, для этого надо поставить py-sqlite3. Как это делается, описывать не буду, думаю и так понятно. Заключительным этапом ставим фреймворк django и проверяем, что все установлено корректно:
На этом установка всех необходимых программ завершена, переходим к настройке. Сперва настроим dhcp-server. Допустим наша wifi-сеть 192.168.40.0/24, интерфейс сети em2, тогда конфиг сервера будет примерно таким:
default-lease-time 43200; max-lease-time 43200; ddns-update-style none; authoritative; log-facility local7; option routers 192.168.40.1; option domain-name-servers 8.8.8.8, 8.8.4.4; subnet 192.168.40.0 netmask 255.255.255.0 { range 192.168.40.2 192.168.40.253; }
43200 – 12 часов в секундах, на какой срок выдаем адрес в аренду, днс-сервера указываем любые, можно публичные гугла, но я предпочитаю использовать свой внутри сети, поднятый на том же шлюзе, в случае использования внешних, необходимо будет разрешить к ним dns-запросы от wifi-сети. Ну а 192.168.40.1 – соответственно IP-адрес нашего шлюза в wifi-сети. И последний штрих, т.к. у меня в локальной сети уже есть dhcp-сервер, то нужно сделать так, чтобы они не конфликтовали, в rc.conf прописываем:
dhcpd_enable="Yes" dhcpd_ifaces="em2"
Таким образом dhcp-сервер будет работать только на wifi-интерфейсе. Nginx у нас будет работать по двум протоколам: http и https. На http будут перенаправляться все web-запросы неавторизованных wifi-клиентов, а затем nginx будет делать перенаправление с http на наш сервис авторизации работающий через https, ведь наша wifi-сеть открытая, а это значит, что все передаваемые по ней данные доступны любому желающему, поэтому мы обезопасим процесс авторизации таким образом. Почему было не сделать сразу перенаправление на https? Потому что мы бы увидели сообщение об ошибке, ведь клиент делает http-запрос, а сервер ждет https. Итак, конфиг nginx.conf будет выглядеть примерно так:
error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; server { listen 444; server_name 192.168.40.1; rewrite ^(.*)$ https://wifi.company.com permanent; } server { listen 443; server_name localhost; ssl on; ssl_certificate ssl/cert.pem; ssl_certificate_key ssl/cert.key; ssl_session_timeout 5m; ssl_protocols SSLv2 SSLv3 TLSv1; ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP; ssl_prefer_server_ciphers on; location / { uwsgi_pass unix:///tmp/uwsgi.sock; include uwsgi_params; } location /static/ { root /www/wifiadmin/wifiadmin; } }
Протокол http прослушивается на 444 порту (у меня 80-ый порт занят апачем) и все запросы перенаправляются на https://wifi.company.com, на этом адресе у нас работает сервис авторизации. Вместо доменного имени можно указать IP-адрес. Дальше в конфиге описывается https-сервер на стандартном порту 443, тут все стандартно, далее прописываем чтобы nginx контактировал с uwsgi через локальный сокет /tmp/uwsgi.sock и наконец прописываем статические файлы сервиса авторизации, считаем, что архив распакован в каталог /www.
Теперь нужно сгенерить ssl-сертификаты cert.pem и cert.key для nginx. Выполняем следующие команды, при генерации сертификата указываем свои данные:
Переходим к самому сервису авторизации. Скачиваем архив wifiadmin.tar и распаковываем его в /www, должно получиться следующее:
Редактируем файл /www/wifiadmin/uwsgi.xml, тут уже все прописано, единственное что нужно поменять - это параметр pythonpath, если вы распаковали архив не в /www.
<uwsgi> <pythonpath>/www/wifiadmin</pythonpath> <module>webapp</module> <socket>/tmp/uwsgi.sock</socket> <process>1</process> <master/> <enable-threads/> <uid>80</uid> <gid>80</gid> <pidfile>/var/run/uwsgi.pid</pidfile> </uwsgi>
Теперь можно проверить uwsgi:
Если он запустился без ошибок, останавливаем Ctrl + C и прописываем в rc.conf:
uwsgi_enable="YES" uwsgi_flags="-x /www/wifiadmin/uwsgi.xml"
и запускаем
Теперь надо скомпилировать небольшую программку(wificmd), которая будет добавлять/удалять IP-адреса клиентов из таблицы пакетного фильтра. Она принимает два аргумента, действие add/delete и сам IP-адрес. Написана программа на языке Си, на самом деле это не важно, главное то, что она должна быть бинарной, т.к. ее будет вызывать сервис авторизации, который работает через nginx, у которого в свою очередь ограничены права, а для того, чтобы внести изменения в пакетный фильтр требуются права суперпользователя. Получается у нас два варианта как разрешить веб-серверу запускать wificmd, либо установить sticky bit, либо настроить sudo. Ни первый ни второй вариант мы не можем реализовать со скриптом, поэтому пришлось сделать небольшую отдельную программу, очень ограниченную в возможностях, которая только умеет добавлять и удалять адреса из жестко прописанной в коде таблицы. Итак, выполняем команду:
Второй вариант с sudo более красивый, т.к. выполнять wificmd смогут не все непривилегированные пользователи, а только один - www, поэтому я использовал именно его. Ставим sudo и в sudoers прописываем wificmd так, чтобы пользователь www мог выполнять ее от имени суперпользователя без запроса пароля:
www ALL=NOPASSWD: /www/wifiadmin/wificmd
Теперь приступим к настройке wifiadmin, редактируем файл settings.py, ниже минимальный список опций, которые нужно изменить, все остальное можно оставить без изменений:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'dbnama', 'USER': 'user', 'PASSWORD': 'pass', 'HOST': '1.1.1.1', 'PORT': '', } } WIFI_NETWORK = '192.168.40.0/24'
Здесь мы задаем настройки подключения к БД, в моем случае это MySQL и последний параметр - wifi-сеть.
Теперь нам надо собрать статические файлы django и синхронизировать БД. Во время синхронизации БД нам будет предложено создать суперпользователя, соглашаемся, под этим пользователем мы будем администрировать учетные записи wifi.
Все, теперь можно попробовать залогиниться в административный интерфейс по адресу https://my_gw_address/admin/
Следующим этапом будет настройка пакетного фильтра, в моем случае - это PF. Ниже приведен примерный минимальный набор правил, все остальные правила фильтрации необходимо добавит исходя из ваших потребностей.
openwifi_net="192.168.40.0/24" ext_if="em0" int_if="em1" wifi_if="em2"
# Заставляем пакетный фильтр создать пустую таблицу table <openwifi> persist # Наши локальные сети table <not_routable> { 10.0.0.0/8, 192.168.0.0/16 } scrub in all fragment reassemble scrub out all random-id max-mss 1400 set skip on $int_if set skip on lo # Разрешаем нат от клиентов из таблицы openwifi nat from <openwifi> to any -> $ext_if # Но если клиент не в таблице openwifi, то перенаправляем его на сервис авторизации rdr pass on $wifi_if proto tcp from ! <openwifi> to any port 80 -> 127.0.0.1 port 444 # Это если у вас нет своего DNS-сервера nat proto udp from $openwifi_net to { 8.8.8.8, 8.8.4.4 } port 53 -> $ext_if block in pass out keep state # Это если у вас свой DNS pass in proto udp from $openwifi_net to $wifi_if port 53 # Открываем доступ к 443 порту от wifi-сети на wifi-интерфейсе pass in proto tcp from $openwifi_net to $wifi_if port 443 # Ну и разрешаем доступ в интернет pass in proto { tcp udp icmp } from <openwifi> to { !<not_routable> }
В заключении нужно прописать в crontab скрипт, который будет выполняться каждые 10 минут и закрывать истекшие сесии:
. */10 * * * * www /www/wifiadmin/wifiadmin/cron.py
На этом настройка закончена, теперь, если все было настроено корректно, при попытке входа на любой сайт через wifi, пользователя будет перекидывать на страничку авторизации.
Принцип работы следующий: клиенты подключаются к открытой wifi-сети, автоматически получают сетевые настройки и при первой попытке открыть любой веб-узел перенаправляются на страницу авторизации. После успешной авторизации адрес клиента добавляется в специальную таблицу в фаерволе, в БД добавляется информация в таблицу сессий и пользователю предоставляется доступ в интернет. Специальный скрипт cron.py выполняется каждые 10 минут и проверяет активные сессии, если есть сессии время которых превышает максимальное время сессии, указанное в конфиге, то она закрывается. Если пользователь, сессия которого активна, авторизуется с другого компьютера с другим IP-адресом, то его старая сессия закроется и откроется новая для текущего компьютера.
pfctl -t openwifi -T show
В админке в wifiadmin есть несколько разделов: Auth и Main. В первом разделе создаются пользователи и группы, это стандартный раздел django admin, по большому счету там можно создавать только пользователей и давать им права суперпользователя, эти акаунты нужны только для входа в админку. Второй раздел main относится к wifi, там как раз создаются пользователи для доступа в интернет. У пользователей есть несколько параметров, такие как срок действия акаунта, активен он или нет и т.д. В журнал в обычном режиме пишутся события безопасности, а в режиме отладки подробный отчет о всех выполненных действиях на каждом этапе. Ну и наконец раздел «сессии» содержит информацию о подключениях, дата подключения, активна ли сессия и с какого адреса было открыто подключение. Тут же есть функция ручного отключения пользователя, если сессия активна.
~~DISCUSSION~~