Известно, что при подключении к открытым Wi-Fi сетям ваш трафик может быть легко прослушан. Конечно, сейчас всё больше и больше сайтов используют HTTPS. Тем не менее, это ещё далеко не 100%. Возникает естественное желание обезопасить свой трафик при подключении к таким открытым Wi-Fi сетям.

Популярное решение этой проблемы — подключение через VPN. В таком случае ваш трафик передается в зашифрованном виде до VPN-сервера, и уже оттуда идет в интернет.

У такого решения есть небольшой недостаток: пока VPN-подключение ещё не установлено, все приложения на вашем компьютере (включая открытые вкладки браузера) получают доступ в интернет в обход VPN-подключения.

В этой статье я расскажу, как можно этого избежать.

Идея


Как мы будет решать эту проблему?

В общих чертах, мы хотим заблокировать весь доступ к сети всем приложениям с двумя исключениями:
  • Разрешить доступ через сетевой интерфейс VPN.
  • Разрешить процессу OpenVPN доступ в интернет напрямую, чтобы тот мог установить VPN-подключение.
Мы будем делать это при помощи iptables.

Если первый пункт решается очень просто, то второй вызывает вопросы. В iptables нельзя писать правила, которые бы сопоставляли название процесса.

Можно разрешить в iptables доступ к жестко прописанному IP VPN-сервера всем процессам (в предположении, что никаких других соединений по этому IP не производится). Это решение плохо тем, что мы не сможем подключаться к серверу по хостнейму. Кроме того, возникают проблемы с captive portal. Если какая-то точка доступа требует предварительно зайти на веб-страницу и щелкнуть там «Согласен», придется как-то вручную прописывать исключение. Поэтому такое решение является далеко не идеальным.

Одно из решений основано на использовании групп. iptables умеет сопоставлять пакеты по GID процесса, который эти пакеты отправил. Такое решение является весьма простым и эффективным. Но если какому-то процессу вдруг захочется поменять свой GID, он сразу же потеряет доступ в интернет.

Второй возможный вариант — использовать cgroups. Мы можем создать особую cgroup и помещать туда процессы, которым нужен свободный доступ в интернет, и всем пакетам, отсылаемым такими процессами, будет выставлять метка, по которой можно их матчить в iptables. Преимущество такого подхода состоит в том, что не нужно менять группу процесса, которая может быть использоваться ещё для чего-то. Требуется только назначить подсистеме net_cls конкретную cgroup. Плюс, в отличие от варианта с обычным группами, процесс можно переносить из одной cgroup в другую «на ходу», без перезапуска. Недостатки метода: более сложная настройка, требуется недавно вышедший iptables v1.6.0 (которого в большинство дистрибутивов ещё не добавили).

Я поподробнее рассмотрю вариант с обычными группами, и в конце поста вкратце опишу, как это можно сделать с cgroups.

Требования


Предполагается, что у вас уже есть VPN на основе OpenVPN и вы умеете пропускать весь трафик через него (например, при помощи опции redirect-gateway def1).

Также будем считать, что у вас есть достаточно свежее ядро с CONFIG_NETFILTER_XT_MATCH_OWNER и iptables.

Настройка iptables


В первую очередь необходимо создать специальную группу. Назовем её killswitch.
groupadd --system killswitch

Теперь добавим правила iptables:
iptables -A OUTPUT -m owner --gid-owner killswitch -j ACCEPT
iptables -A OUTPUT -o tun0 -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
iptables -A OUTPUT -j REJECT --reject-with icmp-admin-prohibited
Что же здесь вообще происходит?
  1. Пакеты, отправленные процессами с GID killswitch, пропускаются в сеть.
  2. Пакеты на интерфейсе tun0 пропускаются безусловно. Это тот самый сетевой интерфейс, который реализован поверх VPN-подключения. Если у вас этот сетевой интерфейс называется по-другому (опция dev в конфиге OpenVPN позволяет дать ему фиксированное имя вместо tunN), поменяйте его в правилах iptables выше.
  3. Пакеты на интерфейсе lo точно так же пропускаются. lo — это loopback-интерфейс, на котором располагается известный 127.0.0.1 (localhost). Поскольку некоторые приложения используют localhost для коммуникации между процессами, его блокировать нежелательно.
  4. Все остальные пакеты блокируются. Блокировка при этом происходит с отправкой ICMP-пакета «administratively prohibited» (код ошибки не играет существенной роли). Это лучше, чем просто дропать пакеты, так как в таком случае программы будут сразу получать ошибку, а не висеть до таймаута.

Если всё пойдет правильно, на этом моменте у вас должен пропасть доступ в интернет.

Чтобы запустить какую-то программу с доступом в интернет, можно использовать утилиту sg (а если добавить своего пользователя в эту группу, то можно будет вызывать её без sudo). Напомню, что до тех пор, пока эта группа не станет основной (effective GID), процесс не получит доступ в интернет без VPN. iptables не проверяет supplementary GIDs!
sg killswitch 'ping ya.ru'
Теперь всё, что осталось — это запустить внутри клиент OpenVPN при помощи sg.
sg killswitch 'openvpn config.ovpn'
Всё! Начиная с этого момента у вас должен заработать интернет во всех остальных программах.

Бонус: captive portals


Что делать, если для доступа в интернет нужно сперва зайти на страничку в браузереи нажать там «согласен», как, например, в московском метро?

Эта проблема легко решается. Точно так же, как запускается OpenVPN-клиент, можно запустить и любое другое приложение.

Так, у меня на такие случаи есть специальный профиль Firefox в вечном приватном режиме. Если запустить его через sg killswitch, он точно так же получит доступ в интернет без VPN.
sg killswitch 'firefox -P my_special_profile_name -no-remote'

Бонус: cgroups


Решение на основе cgroups требует iptables v1.6.0 и ядра с опцией CONFIG_NETFILTER_XT_MATCH_CGROUP.
cgcreate -g net_cls:killswitch # создаем cgroup killswitch с подсистемой net_cls
echo 0x00100001 > /sys/fs/cgroup/net_cls/killswitch/net_cls.classid # настраиваем метку, которая будет присваиваться пакетам (10:1)
chmod 666 /sys/fs/cgroup/net_cls/killswitch/tasks # позволяет всем пользователям запускать процессы в этой cgroup

iptables -A OUTPUT -m cgroup --cgroup 0x00100001  -j ACCEPT
iptables -A OUTPUT -o tun0 -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
iptables -A OUTPUT -j REJECT --reject-with icmp-admin-prohibited

cgexec -g net_cls:killswitch ping ya.ru
cgexec -g net_cls:killswitch sudo openvpn config.ovpn
cgexec -g net_cls:killswitch firefox -P my_special_profile_name -no-remote
Следующие команды позволяют выдать доступ в интернет без VPN Firefox, а затем забрать его обратно:
pgrep -w firefox | xargs cgclassify -g net_cls:killswitch
pgrep -w firefox | xargs sudo cgclassify -g net_cls:/

Бонус: xtables-addons condition


Сидеть через VPN дома или на работе может и не быть особой нужды. -m condition --condition killswitch из xtables-addons, добавленный в последнее правило iptables (которое с REJECT), может сделать killswitch легко переключаемым через echo <0|1> > /proc/net/nf_condition/killswitch.

Заключение


В статье были рассмотрены два подхода к реализации killswitch при помощи iptables: через обычные группы и через cgroups. Оба варианта работают весьма гибко, позволяя по необходимости выдавать произвольным процессам доступ в интернет без VPN. Оба подхода практически эквивалентны, но способ через cgroups чуть сложнее настроить, требует очень свежего iptables, но зато позволяет выдавать и забирать доступ прямо во время работы процесса.

Комментарии (11)


  1. blind_oracle
    03.01.2016 09:10
    +1

    Сноуден одобряэ!

    А если серьезно, то очень интересно, спасибо.
    Как-то такая задача даже никогда не вставала — весь важный траффик у меня давно уже завернут в SSL, а остальной пусть слушают…


  1. artem_dev
    03.01.2016 13:48

    Спасибо, интересно!
    А как подобную схему использовать для смартфона\планшета?


  1. int_0x80
    03.01.2016 17:55

    Идея красивая, но не без изъяна. Про получение ипа через dhcp (port 67) забыли


    1. WGH
      03.01.2016 18:02
      +1

      Ну нет, это иъзян не самой идеи, а реализации идеи, который к тому же легко поправить.
      А вообще на практике это даже и не нужно: DHCP-клиенты используют сырые сокеты, которые просто обходят iptables.


      1. int_0x80
        03.01.2016 21:43

        Да, конечно же в реализации, не точно выразился.

        А вот насчет сырых сокетов, это что-то за гранью, иптаблы работают «на уровне» сетевого стека, и им все равно какими сокетами был сформирован пакет.

        Например, классическая реализация подразумевает запрет всех исходящих соединений кроме ипа впн сервера, (ваша идея как раз хороша тем, что исключает прописывание его каждый раз) и в ней точно так же блокируется dhcp реквест, по этому что бы все работало, необходимо дописывать разрешение на отправку udp на 67-ой порт


        1. WGH
          03.01.2016 22:17
          +1

          Смысл сырых сокетов как раз в том, чтобы обойти весь стек (в частности, IP). DHCP-клиенту нужно отправить пакеты с исходящим адресом 0.0.0.0 на нужный интерфейс, что обычные DGRAM-сокеты не позволяют.

          В принципе, ядро могло бы перед отправкой и распарсить эти пакеты, сформированные RAW-сокетами, и применить к ним цепочку OUTPUT, но оно так не делает (только что проверил).

          Но это относится только к IPPROTO_RAW (который и используют DHCP-клиенты). ping, например, использует socket(PF_INET, SOCK_RAW, IPPROTO_ICMP), и может управлять лишь содержимым IP-пакета, но не его заголовком. IP-заголовок формирует ядро, и такой пакет проходит через OUTPUT как положено.


          1. int_0x80
            03.01.2016 23:31
            +1

            После гугления, действительно под линухом оказывается можно bypass'ить фаер… что имхо, очень, очень зря

            Просто на маке/iphone (pf) и на фре (тоже pf) такая штука не работает и нужно открывать dhcp


            1. DarkByte
              04.01.2016 15:34

              Разве для использования raw сокетов не требуются права суперпользователя? Он ведь и так при желании может фаер обойти.


              1. WGH
                04.01.2016 18:12
                +1

                Одно дело «при желании», другое дело — «случайно».


              1. int_0x80
                04.01.2016 22:13
                +3

                Требуются, но система должна быть предсказуемой.
                И файрвол должен быть «последней инстанцией» которая принимает решение, сказано все резать — значит так и должно быть,
                а тут появляются исключения…

                Насчет рута… понимаете, получить рута — это не единственная задача злоумышленника, достигнув которой он стал всемогущ и расслабился.
                Вторая его основная задача — оставаться не видимым, если он начнет менять правила файрвола, добавлять юзеров и проявлять прочую активность в системе — это рано или поздно обнаружат.
                А тут получается ему замечательный подарок, который позволит контролировать сервер даже если там будет жестко зарезан исходящий трафик. И админ будет свято уверен в том, что все «под контролем».


  1. ComodoHacker
    03.01.2016 20:43
    -2

    Кажется, С началом нового года Хабр приобретает округлую форму, напоминающую новогодний торт.