Известно, что при подключении к открытым 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
Что же здесь вообще происходит?
- Пакеты, отправленные процессами с GID killswitch, пропускаются в сеть.
- Пакеты на интерфейсе tun0 пропускаются безусловно. Это тот самый сетевой интерфейс, который реализован поверх VPN-подключения. Если у вас этот сетевой интерфейс называется по-другому (опция dev в конфиге OpenVPN позволяет дать ему фиксированное имя вместо tunN), поменяйте его в правилах iptables выше.
- Пакеты на интерфейсе lo точно так же пропускаются. lo — это loopback-интерфейс, на котором располагается известный 127.0.0.1 (localhost). Поскольку некоторые приложения используют localhost для коммуникации между процессами, его блокировать нежелательно.
- Все остальные пакеты блокируются. Блокировка при этом происходит с отправкой 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, но зато позволяет выдавать и забирать доступ прямо во время работы процесса.
Автор: WGH