динамическая фильтрация траффика OpenVPN через iptables

    2013-05-04 13:34 | Автор: jekader | Filed under: FedoraMD

    Нашёл одну интересную задачу применительно к OpenVPN, возможно будет полезно кому-то ещё. Заключается она в фильтрации траффика OpenVPN для каждого клиента в отдельности прямо на сервере.

    Вступление:
    OpenVPN - это реализация VPN поверх SSL, очень гибкая и универсальная штука. Может служить и как Remote Access VPN (для подключения удалённых пользователей), так и как Site 2 Site (для взаимоподключения сетей). При этом, и на сервере и на клиенте появляется виртуальный интерфейс, и на этот интерфейс назначаются маршруты. Это позволяет, к примеру, не гонять интернет траффик зашифрованным, а открывать только отдельные сети.

    Дано:
    Итак, допустим у нас есть OpenVPN сервер, и к нему подключаются пользователи A, B и С
    Пользователю A нужен доступ только к SVN серверу, сидящему по адресу 192.168.2.2
    Пользователю B помимо SVN нужен ещё и файл-сервер 192.168.6.100
    Пользователю C требуется доступ к "боевым" серверам в сети 10.25.25.0.24

    Решение:
    каждому пользователю высылаем свой пакет маршрутов, это очень тривиально делается в OpenVPN директивой ccd. Каждый пользователь имеет свой файлик конфигурации, где написаны его личные настройки.

    OK, отправили маршруты, все счастливы. Но что будет, если пользователь A вручную пропишет себе маршруты пользователя C? OpenVPN никак этому не будет препятствовать, и пользователь получит доступ куда не надо!
    В моём примере всё просто решается добавлением пары правил в iptables. Но что если таких пользователей не трое, а 100? Или 1000? А также есть несколько OpenVPN серверов в целях повышения надёжности, и права пользователей меняются по несколько раз в день?

    Ничего готового на эту тему не было, так что на помощь пришла гибкость OpenVPN: я написал два маленьких bash скрипта. первый при подключении пользователя парсит конфиг и на каждый маршрут добавляет правило в цепочку FORWARD. Второй при отключении стирает правила, созданные первым скриптом. Благодаря этому, левый траффик отвергается, и клиент может ходить только в сети, разрешённые сервером.

    Первый скрипт, on_connect.sh, запускается при подключении:

    #!/bin/bash
    # script that adds iptables rules according to routes sent to the client. Default forward policy must be "DROP"
    ovpnconfig="/etc/openvpn/server.conf"
    ccd="/etc/openvpn/ccd"

    logfile="/var/log/openvpn/iptables.log"
    date >> $logfile

    rulecomment=$common_name"_openvpn"

    if [ -f $ovpnconfig ]
    then
    # read file | delete leading spaces         | delete double spaces | delete commented strings|delete quotes| get routes
    cat $ovpnconfig | sed 's/^[ \t]*//;s/[ \t]*$//' | sed 's/ \+ / /g' | grep -v "^#" | grep -v "^;" | tr -d '"'   | grep "^push route" | while read line; do
    network=$(echo $line | cut -f3 -d" ")
    netmask=$(echo $line | cut -f4 -d" ")
    echo "iptables -I FORWARD -s $ifconfig_pool_remote_ip -d $network/$netmask -j ACCEPT -m comment --comment $rulecomment" >> $logfile
    /usr/bin/sudo /sbin/iptables -I FORWARD -s $ifconfig_pool_remote_ip -d $network/$netmask -j ACCEPT -m comment --comment $rulecomment >> $logfile 2>&1
    done
    fi
    if [ -f $ccd/$common_name ]
    then
    # read file | delete leading spaces         | delete double spaces | delete commented strings|delete quotes| get routes
    cat $ccd/$common_name | sed 's/^[ \t]*//;s/[ \t]*$//' | sed 's/ \+ / /g' | grep -v "^#" | grep -v "^;" | tr -d '"'   | grep "^push route" | while read line; do
    network=$(echo $line | cut -f3 -d" ")
    netmask=$(echo $line | cut -f4 -d" ")
    echo "iptables -I FORWARD -s $ifconfig_pool_remote_ip -d $network/$netmask -j ACCEPT -m comment --comment $rulecomment" >> $logfile
    /usr/bin/sudo /sbin/iptables -I FORWARD -s $ifconfig_pool_remote_ip -d $network/$netmask -j ACCEPT -m comment --comment $rulecomment >> $logfile 2>&1
    done
    fi
    exit 0

    Второй скрипт, on_disconnect.sh - запускается при отключении:


    #!/bin/bash
    #scripts that removes rules with a given comment
    #make sure the script is run once at a time.
    #This will leave some rules in place, however that's much better than
    #having two scripts running simultaneously and screwing up iptables
    #
    logfile="/var/log/openvpn/iptables.log"
    date >> $logfile
    pidfile=/var/lock/openvpn_disconnect.pid
    if [ -e $pidfile ]; then
    pid=`cat $pidfile`
    if kill -0 $pid > /dev/null 2>&1; then
    #  echo "Already running"
    exit 1
    else
    rm $pidfile
    fi
    fi
    echo $$ > $pidfile
    #the script itself
    rulecomment=$common_name"_openvpn"
    #list rules with line numbers            | get needed rules  | sort reverse| remove line by line
    /usr/bin/sudo /sbin/iptables -L FORWARD --line-numbers | grep $rulecomment | sort -r -n  | while read line; do
    rulenumber=$(echo $line | cut -f1 -d" ")
    echo "iptables -D FORWARD $rulenumber" >> $logfile
    /usr/bin/sudo /sbin/iptables -D FORWARD $rulenumber
    done
    #remove the PID file after everything is done
    rm $pidfile
    exit 0

    Пояснения: первый скрипт читает конфиг openVPN и CCD файл, если такой существует. На каждое упоминание "push route" создаётся правило с комментарием вида username_openvpn. Второй скрипт стирает все правила с этим комментарием. Так как он работает по номерам строк, я сделал lock file, чтобы два скрипта не запустились одновременно. Это чревато порчей правил в iptables. Пускай лучше останутся пару лишних правил, чем упадёт весь сервер.

    Чтобы это счастье работало есть ещё несколько шагов.
    1) включаем скрипты в конфиг OpenVPN:
    script-security 2
    client-connect /etc/openvpn/on_connect.sh
    client-disconnect /etc/openvpn/on_disconnect.sh

    2) настраиваем sudo (ведь OpenVPN мы запускаем под nobody, а iptables может пускать только root):
    cat /etc/sudoers.d/openvpn_iptables
    Defaults:nobody !requiretty
    nobody ALL = NOPASSWD: /sbin/iptables

    3) настраиваем Selinux (мы ведь параноики):
    сначала отключаем временно enforcement
    setenforce 0
    теперь подключаемся к OpenVPN серверу с клиента, потом отключаемся, проверяем что всё работает.
    Дальше дело за малым - создать политику. Для этого запускаем:
    grep openvpn /var/log/audit/audit.log | audit2allow -M openvpn_sudo_ipt
    модуль готов, устанавливаем!
    semodule -i openvpn_sudo_ipt.pp
    включаем назад enforcement:
    setenforce 1
    теперь наши скрипты должны запускаться и работать как следует!

    TODO: парсинг директивы iroute

    PS: замечу, что эти скрипты были написаны за пару часов человеком без особого знания скриптинга. Если есть предложения к улучшению - комментируйте! Скрипты также закинул на gitHub для удобства: https://github.com/jekader/openvpn-iptables

    1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
    Loading...

    Метки:

    Comments (1) »


    1 комментарий

    1. Oleg:

      Отлично. Спасибо за скрипты. Пригодятся.

    Leave a comment

    *