company_banner

xtables-addons: фильтруем пакеты по странам


    Задача блокировки трафика из определенных стран кажется простой, но первое впечатление обманчиво. Сегодня расскажем, как это можно реализовать.

    Предыстория


    Результаты поиска в Google по этой теме огорчают: большинство решений давно «протухли» и порой кажется, что эту тему отложили в долгий ящик и навсегда забыли про нее. Мы же «прошерстили» много старых записей и готовы поделиться современной версией инструкции.
    Рекомендуем прочитать статью полностью, прежде чем выполнять указанные команды.

    Подготовка операционной системы


    Фильтрация будет настраиваться с помощью утилиты iptables, которой требуется расширение для работы с GeoIP-данными. Такое расширение можно найти в xtables-addons. xtables-addons устанавливает расширения для iptables в виде самостоятельных модулей ядра, благодаря чему не нужно перекомпилировать ядро ОС.

    На момент написания статьи актуальная версия xtables-addons — 3.9. Тем не менее, в стандартных репозиториях Ubuntu 20.04 LTS можно найти только 3.8, а в репозиториях Ubuntu 18.04 — 3.0. Установить расширение из пакетного менеджера можно следующей командой:

    apt install xtables-addons-common libtext-csv-xs-perl

    Отметим, что между версией 3.9 и текущим состоянием проекта есть небольшие, но важные различия, о которых мы поговорим позднее. Для сборки из исходных кодов устанавливаем все необходимые пакеты:

    apt install git build-essential autoconf make libtool iptables-dev libxtables-dev pkg-config libnet-cidr-lite-perl libtext-csv-xs-perl

    Клонируем репозиторий:

    git clone https://git.code.sf.net/p/xtables-addons/xtables-addons xtables-addons-xtables-addons
    

    cd xtables-addons-xtables-addons

    xtables-addons содержит множество расширений, но нас интересует только xt_geoip. Если не хочется тащить в систему ненужные расширения, их можно исключить из сборки. Для этого нужно отредактировать файл mconfig. Для всех желаемых модулей поставить y, а все ненужные отметить n. Собираем:

    ./autogen.sh

    ./configure

    make

    И устанавливаем с правами суперпользователя:

    make install

    Во время установки модулей ядра может возникнуть ошибка примерно такого содержания:

    INSTALL /root/xtables-addons-xtables-addons/extensions/xt_geoip.ko
    At main.c:160:
    - SSL error:02001002:system library:fopen:No such file or directory: ../crypto/bio/bss_file.c:72
    - SSL error:2006D080:BIO routines:BIO_new_file:no such file: ../crypto/bio/bss_file.c:79
    sign-file: certs/signing_key.pem: No such file or directory

    Такая ситуация возникает из-за невозможности подписать модули ядра, т.к. нечем подписывать. Решить эту проблему можно парой команд:

    cd /lib/modules/(uname -r)/build/certs

    cat <<EOF > x509.genkey

    [ req ]
    default_bits = 4096
    distinguished_name = req_distinguished_name
    prompt = no
    string_mask = utf8only
    x509_extensions = myexts
    
    [ req_distinguished_name ]
    CN = Modules
    
    [ myexts ]
    basicConstraints=critical,CA:FALSE
    keyUsage=digitalSignature
    subjectKeyIdentifier=hash
    authorityKeyIdentifier=keyid
    EOF

    openssl req -new -nodes -utf8 -sha512 -days 36500 -batch -x509 -config x509.genkey -outform DER -out signing_key.x509 -keyout signing_key.pem

    Скомпилированный модуль ядра установлен, но система его не определяет. Попросим систему создать карту зависимостей с учетом нового модуля, а затем загрузим его:

    depmod -a

    modprobe xt_geoip

    Убедимся, что xt_geoip загружен в систему:

    # lsmod | grep xt_geoip
    xt_geoip               16384  0
    x_tables               40960  2 xt_geoip,ip_tables

    Дополнительно убедимся, что расширение загрузилось в iptables:

    # cat /proc/net/ip_tables_matches 
    geoip
    icmp

    Нас все устраивает и остается только добавить название модуля в /etc/modules, чтобы модуль заработал после перезагрузки ОС. С этого момента iptables понимает команды geoip, но ему не хватает данных для работы. Приступаем к загрузке базы данных geoip.

    Получение базы данных GeoIP


    Создаем каталог, в котором будет хранится информация, понятная расширению iptables:

    mkdir /usr/share/xt_geoip

    В начале статьи мы упоминали, что между версией из исходного кода и версией из пакетного менеджера есть различия. Самое заметное различие заключается в смене поставщика базы данных и скрипте xt_geoip_dl, который скачивает актуальные данные.

    Версия из пакетного менеджера


    Скрипт лежит по пути /usr/lib/xtables-addons, однако при попытке запуска можно увидеть не самую информативную ошибку:

    # ./xt_geoip_dl 
    unzip:  cannot find or open GeoLite2-Country-CSV.zip, GeoLite2-Country-CSV.zip.zip or GeoLite2-Country-CSV.zip.ZIP.

    Ранее в качестве базы данных использовался продукт GeoLite, ныне известный как GeoLite Legacy, распространяемый по лицензии Creative Commons ASA 4.0 компанией MaxMind. С этим продуктом произошло сразу два события, которые «сломали» совместимость с расширением для iptables.

    Во-первых, в январе 2018 года объявили о прекращении поддержки продукта, и второго января 2019 года с официального сайта убрали все ссылки на скачивание старой версии базы. Новым пользователям рекомендуется использовать продукт GeoLite2 или его платную версию GeoIP2.

    Во-вторых, c декабря 2019 года MaxMind заявила о значительном изменении в доступе к их базам. Чтобы соответствовать Калифорнийскому закону о защите прав потребителей, компания MaxMind приняла решение «прикрыть» распространение GeoLite2 регистрацией.

    Так как мы хотим воспользоваться их продуктом, зарегистрируемся на этой странице.


    После на почту придет сообщение с просьбой установить пароль. Теперь, когда мы завели аккаунт, нужно создать лицензионный ключ. В личном кабинете находим пункт My License Keys, а затем нажимаем на кнопку Generate new License Key.

    При создании ключа нам зададут только один вопрос: будем ли мы использовать этот ключ в программе GeoIP Update? Отвечаем отрицательно и нажимаем на кнопку Confirm. Во всплывающем окне будет отображен ключ. Сохраните этот ключ в надежное место, так как после закрытия всплывающего окна вы больше не сможете посмотреть ключ полностью.


    У нас есть возможность скачивать базы GeoLite2 в ручном режиме, но их формат не совместим с форматом, который ожидает скрипт xt_geoip_build. Здесь на помощь приходят скрипты GeoLite2xtables. Для работы скриптов устанавливаем perl-модуль NetAddr::IP:

    wget https://cpan.metacpan.org/authors/id/M/MI/MIKER/NetAddr-IP-4.079.tar.gz

    tar xvf NetAddr-IP-4.079.tar.gz

    cd NetAddr-IP-4.079

    perl Makefile.PL

    make

    make install

    Далее клонируем репозиторий со скриптами и записываем в файл полученный ранее лицензионный ключ:

    git clone https://github.com/mschmitt/GeoLite2xtables.git

    cd GeoLite2xtables

    echo YOUR_LICENSE_KEY=\’123ertyui123\' > geolite2.license

    Запускаем в работу скрипты:

    # Скачиваем данные GeoLite2
    ./00_download_geolite2
    # Скачиваем информацию о странах (для соответствия коду)
    ./10_download_countryinfo
    # Конвертируем GeoLite2 базу в формат GeoLite Legacy 
    cat /tmp/GeoLite2-Country-Blocks-IPv{4,6}.csv |
    ./20_convert_geolite2 /tmp/CountryInfo.txt > /usr/share/xt_geoip/dbip-country-lite.csv
    MaxMind накладывает ограничение в 2000 скачиваний в сутки и при большом количестве серверов предлагает кэшировать обновление на прокси-сервере.
    Обратите внимание, что выходной файл обязательно должен называться dbip-country-lite.csv. К сожалению, 20_convert_geolite2 выдает не идеальный файл. Скрипт xt_geoip_build ожидает три колонки:

    • начало диапазона адресов;
    • конец диапазона адресов;
    • код страны в iso-3166-alpha2.

    А выходной файл содержит шесть колонок:

    • начало диапазона адресов (строковое представление);
    • конец диапазона адресов (строковое представление);
    • начало диапазона адресов (числовое представление);
    • конец диапазона адресов (числовое представление);
    • код страны;
    • название страны.

    Это несоответствие критично и может быть исправлено одним из двух способов:

    1. править 20_convert_geolite2;
    2. править xt_geoip_build.

    В первом случае сокращаем printf до нужного формата, а во втором — изменяем присваивание переменной $cc на $row->[4]. После этого можно выполнять сборку:

    /usr/lib/xtables-addons/xt_geoip_build -S /usr/share/xt_geoip/ -D /usr/share/xt_geoip

    . . .
     2239 IPv4 ranges for ZA
      348 IPv6 ranges for ZA
       56 IPv4 ranges for ZM
       12 IPv6 ranges for ZM
       56 IPv4 ranges for ZW
       15 IPv6 ranges for ZW

    Отметим, что автор GeoLite2xtables не считает свои скрипты готовыми для продакшена и предлагает следить за разработкой оригинальных скриптов xt_geoip_*. Поэтому перейдем к сборке из исходных кодов, в которой эти скрипты уже обновлены.

    Версия из исходных кодов


    При установке из исходных кодов скрипты xt_geoip_* располагаются в каталоге /usr/local/libexec/xtables-addons. В этой версии скрипта используется база данных IP to Country Lite. Лицензия — Creative Commons Attribution License, а из доступных данных те самые необходимые три столбца. Скачиваем и собираем базу:

    cd /usr/share/xt_geoip/

    /usr/local/libexec/xtables-addons/xt_geoip_dl

    /usr/local/libexec/xtables-addons/xt_geoip_build

    После этих действий iptables готов к работе.

    Используем geoip в iptables


    Модуль xt_geoip добавляет всего два ключа:

    geoip match options:
    [!] --src-cc, --source-country country[,country...]
    	Match packet coming from (one of) the specified country(ies)
    [!] --dst-cc, --destination-country country[,country...]
    	Match packet going to (one of) the specified country(ies)
    
    NOTE: The country is inputed by its ISO3166 code.

    Способы формирования правил для iptables, в целом, остаются неизменными. Для использования ключей из дополнительных модулей необходимо явно указывать название модуля с ключом -m. Например, правило для блокировки входящих TCP-соединений на 443 порту не из США на всех интерфейсах:

    iptables -I INPUT ! -i lo -p tcp --dport 443 -m geoip ! --src-cc US -j DROP

    Файлы, созданные xt_geoip_build, используются только при создании правил, но не учитываются при фильтрации. Таким образом, для корректного обновления базы geoip необходимо сначала обновить iv*-файлы, а затем пересоздать все правила, которые используют geoip в iptables.

    Заключение


    Фильтрация пакетов в зависимости от принадлежности к странам — несколько забытая временем стратегия. Несмотря на это, программные средства для такой фильтрации развиваются и, возможно, в скором времени в пакетных менеджерах появится новая версия xt_geoip с новым поставщиком geoip-данных, что значительно упростит жизнь системным администраторам.

    Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

    А вам приходилось использовать фильтрацию по странам?

    • 64,0%Да16
    • 36,0%Нет9
    Selectel
    ИТ-инфраструктура для бизнеса

    Комментарии 17

      +1
      А зачем нужно блокировать целые страны? Я вот постоянно от этого страдаю, т.к. живу в другой стране и некоторые российские ресурсы мне недоступны. Приходится из-за них специальный VPN с российским IP держать. Но может быть, я чего-то не понимаю и есть веские причины для таких блокировок.
        0
        Поверьте, есть веские причины для этого. Представьте, что у вас есть небольшой интернет-магазинчик с целевой аудиторией только на Россию и страны СНГ. Все заказы за всю историю поступали только оттуда. Ваш сервер ежедневно подвергается брутфорс-атакам, к примеру, из Китая и Африки. Решение простое — заблокировать все IP-адреса из этих стран и дропать этот трафик. Оно не идеально, но весьма хорошо работает.
          +1
          Почему бы не блокировать тех, кто участвовал в атаке?
            0
            А зачем, если можно одной командой заблокировать целую страну и пускай себе пытаются атаковать дальше? Да и сервер не будет тратить ресурсы на блокировку по отдельности.
              0
              у меня есть небольшой сайтик на wordpress (не то, чтобы там был нужен именно вордпресс, основной функционал на него не завязан). Так вот, периодически, волнами, набегают боты, которые начинают регистрироваться на сайте, причем процесс регистрации они даже не завершают, т.е. не проходят по ссылке подтверждения из письма. Если их не банить, то за день легко можно получить +5 тыс учеток. Некоторые — особо талантливые — делают по несколько учеток в минуту.
              Зачем? Я до сих пор не знаю. Банить их по ip — бесполезно, т.к. прибегают с других ip из того же датацентра. После бана пары сотен ip процесс на некоторое время приостанавливается (но не заканчивается — всегда есть ЕЩЕ новые ип, откуда будет хотя бы одна новая регистрация в час/день). Спустя неделю-две они закупают новые ip в этих же датацентрах и процесс начинается по-новой.
              Ботов легко видно по логам — это ящики на yahoo, всяких странных сайтах.

              Поэтому да, если у вас целевая аудитория — определенная страна, то в итоге проще тупо забанить всё остальное

              P.S.: Хотя, конечно, баны тоже не до конца помогают — на другом сайте активно искали дырки с ип компа (с сильно большой нагрузкой на cpu у хостинга, те возмущаться стали), расположенном в каком-то африканском посольстве в Москве. Тоже было весело
              0
              Хм. Как идея для реализации не плохо, спасибо за информацию:) Теперь буду думать шире
              0

              Причин может быть много. Например у меня есть сайт на котором размещены материалы признанные запрещенными для чтения жителями РФ, а потому то что является по GeoIP адресами из РФ я туда просто не пускаю. Если жители РФ поддерживают власть осуществляющую цензуру, то это их право и я его уважаю.


              А еще есть сайты предлагающие услуги жителям нескольких городов только в РФ, только по ним работают сотрудники фирмы чей сайт. И там я спокойно запрещаю все адреса из Юго-Восточной Азии, Африки и Южной Америки(а это регионы с чьих адресов очень часто долбятся ботнеты), а Европу и США не запрещаю, что бы не мешать поисковикам(лень мне чисто подсети поисковиков отдельно разрешать).


              И это были самые простые примеры, а бывают и более сложные случаи.

              0
              UPD Промахнулся веткой :(
                0

                Но есть вариант проще и менее напряжный для сервиса, чем сование в IPTables стран.
                Никакие лицензии на файлы не нужны, берем файлы по странам http://www.ipdeny.com/ipblocks/data/countries/ тут и засовываем их в ipset. Скрипт дать или сами написать сможете?

                  0
                  403 Forbidden
                    0
                    $COUNTRY
                    http://www.ipdeny.com/ipblocks/data/countries/$COUNTRY.zone

                    Для скачки
                    То есть cn.zone, ru.zone и так далее

                  0
                  Как по мне, iptables + ipset для этого, куда лучший вариант, в помощь github.com/chr0mag/geoipsets
                    0

                    Вариант в один скрипт
                    Требует наличия только iptables, ipset и wget, не требует никаких ключей к геобазам


                    #!/usr/bin/env bash
                    # Description:  Uses IPSET and IPTABLES to block full countries from accessing the server for all ports and protocols
                    # Syntax:       countries_block.bash countrycode [countrycode] ......
                    #               Use the standard locale country codes to get the proper IP list. eg.
                    #               countries_block.bash cn ru ro
                    #               Will create tables that block all requests from China, Russia and Romania
                    # Note:         To get a sorted list of the inserted IPSet IPs for example China list(cn) run the command:
                    #               ipset list cn | sort -n -t . -k 1,1 -k 2,2 -k 3,3 -k 4,4
                    # #############################################################################
                    # Defining some defaults
                    iptables="/sbin/iptables"
                    tempdir="/tmp"
                    sourceURL="http://www.ipdeny.com/ipblocks/data/countries/"
                    #
                    # Verifying that the program 'ipset' is installed
                    if ! (dpkg -l | grep '^ii  ipset' &>/dev/null); then
                        echo "ERROR: 'ipset' package is not installed and required."
                        echo "Please install it with the command 'apt-get install ipset' and start this script again"
                        exit 1
                    fi
                    [ -e /sbin/ipset ] && ipset="/sbin/ipset" || ipset="/usr/sbin/ipset"
                    #
                    # Verifying the number of arguments
                    if [ $# -lt 1 ]; then
                        echo "ERROR: wrong number of arguments. Must be at least one."
                        echo "countries_block.bash countrycode [countrycode] ......"
                        echo "Use the standard locale country codes to get the proper IP list. eg."
                        echo "countries_block.bash cn ru ro"
                        exit 2
                    fi
                    #
                    # Now load the rules for blocking each given countries and insert them into IPSet tables
                    for country; do
                        # Read each line of the list and create the IPSet rules
                        # Making sure only the valid country codes and lists are loaded
                        if wget -q -P $tempdir ${sourceURL}${country}.zone; then
                            # Destroy the IPSet list if it exists
                            $ipset flush $country &>/dev/null
                            # Create the IPSet list name
                            echo "Creating and filling the IPSet country list: $country"
                            $ipset create $country hash:net &>/dev/null
                            (for IP in $(cat $tempdir/${country}.zone); do
                                # Create the IPSet rule from each IP in the list
                                echo -n "$ipset add $country $IP --exist - "
                                $ipset add $country $IP -exist && echo "OK" || echo "FAILED"
                            done) >$tempdir/IPSet-rules.${country}.txt
                            # Destroy the already existing rule if it exists and insert the new one
                            $iptables -D INPUT -p tcp -m set --match-set $country src -j DROP &>/dev/null
                            $iptables -I INPUT -p tcp -m set --match-set $country src -j DROP
                            # Delete the temporary downloaded counties IP lists
                            rm $tempdir/${country}.zone
                        else
                            echo "Argument $country is invalid or not available as country IP list. Skipping"
                        fi
                    done
                    # Display the result of the iptables rules in INPUT chain
                    echo "======================================"
                    echo "IPSet lists registered in iptables:"
                    $iptables -L INPUT -n -v | grep 'match-set'
                    # Dispaly the number of IP ranges entered in the IPset lists
                    echo "--------------------------------------"
                    for country; do
                        echo "Number of ip ranges entered in IPset list '$country' : $($ipset list $country | wc -l)"
                    done
                    echo "======================================"
                    #
                    #eof
                      –1
                      Ну вот, еще лучше вариант. Спасибо, кстати. Хоть в провайдинге и не работаю, но может пригодиться.

                      Я, просто к тому, что вариант автора, выглядит, простите, черезжопный. С кучей make из сорцов и тп. Напоминает шутку про то, что слакварист из любой системы сделает слакварью
                        0

                        Да не за что
                        Главный плюс этого варианта что все в один небольшой скрипт, скрипт полностью читаемый и легко переписываемый при желании или необходимости

                    0
                    make install
                    зачем вы даёте читателю вредные советы?
                    для Debian/Ubintu есть checkinstall и куча других способов сделать «так же, но правильно»
                      0
                      Бан по странам (или разрешение конкретной страны/стран) не всегда помогает, всегда найдется несколько подсетей — откуда будут ломится и их надо отсекать.
                      Есть еще один способ, правда аппаратный (не страшно дорогой) между своим своими серверами и нетом поставить маршрутизатор Vigor2926 (например) с примерно таким набором правил:
                      — разрешить доступ с определенных IP адресов
                      — запретить доступ с определенных IP диапазонов адресов
                      — разрешить доступ для группы стран
                      — запретить все
                      примерно так…

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое