Разворачиваем Tailscale VPN у себя в облаке

Tailscale — это технологичный VPN, который работает поверх WireGuard. Он не требует установки клиенту конфигов или сертификатов, настраивается за секунды и работает сквозь NATы и firewall’ы. Настраивать сервер тоже не нужно, потому что Tailscale предлагает только облачное решение. Есть бесплатный тир, который подходит для личного использования, но, если пользоваться таким VPNом для нужд компании (даже небольшой), ценник начинает сильно кусаться. В этой статье я расскажу, как развернуть управляющий сервер Tailscale у себя, чтобы пользоваться им бесплатно, и поделюсь опытом его эксплуатации.

Headscale

В создании собственного self-hosted решения нам поможет проект под названием Headscale — open-source реализация управляющего сервера Tailscale, того самого, который живёт в платном облаке.

Немного о проекте. Все компоненты Tailscale лежат в открытом доступе. Все, кроме GUI клиентов для проприетарных ОС (Windows, macOS, iOS) и управляющего сервера. Разработчик из Нидерландов Хуан Фонт при помощи исходников и магии обратной инженерии сумел разобраться в том, как работает управляющий сервер, и написал свою реализацию — Headscale. Сообщество подхватило, а проект набрал популярность.

Можно ли пользоваться Headscale

Кстати, авторы Tailscale тоже знают о существовании Headscale. Наша команда сильно переживала, что сейчас мы его возьмём в эксплуатацию, а потом юристы из Tailscale попросят прикрыть open-source лавочку. Но вышло всё наоборот, и авторы оригинала даже обрадовались, что такой проект появился. Более того, когда разработчики Tailscale начали работать над новой версией протокола, они связались с Хуаном и поделились внутренней документацией и кодом, чтобы его поддержка появилась и в Headscale.

В Headscale работают все фичи, что и в облачном решении, за небольшим исключением: не хватает пары quality of life фич в ACLах и нет поддержки клиентов на iOS (пока). Ещё есть ограничение в том, что Headscale ориентирован на одну сеть (tailnet), т.е. предназначен для одной организации/команды/семьи, однако это осознанное дизайнерское решение. Всё остальное отлично работает.

Установка

Давайте перейдём к практической части. Headscale написан на Go, поэтому всё что нужно — это скачать бинарник со страницы с релизами. Обновляться самостоятельно он не умеет, зато умеет проверять новую версию на гитхабе и сообщать об этом при запуске. Для удобства можно создать юнит файл /etc/systemd/system/headscale.service с таким содержимым:

[Unit] Description=headscale coordination server After=syslog.target After=network.target [Service] Type=simple User=headscale Group=headscale ExecStart=/usr/local/bin/headscale serve ExecReload=kill -HUP $MAINPID Restart=always RestartSec=5 NoNewPrivileges=yes PrivateTmp=yes ProtectSystem=strict ProtectHome=yes ReadWritePaths=/var/lib/headscale /var/run/headscale AmbientCapabilities=CAP_NET_BIND_SERVICE RuntimeDirectory=headscale [Install] WantedBy=multi-user.target

В этом случае не забудьте создать юзера и все необходимые директории.

Далее надо обязательно создать конфиг файл, хотя бы дефолтный, иначе Headscale не запустится. Взять его можно из репозитория, а положить — в /etc/headscale/config.yaml (по умолчанию) или указать путь руками через флаг --config. Запускается сервер так:

headscale serve

Инструкции по подключению для Windows и macOS можно найти прямо на сервере по ссылкам /windows и /apple, для Android и Linux — в документации.

Поделюсь нашим опытом эксплуатации. Не буду подробно разбирать содержимое конфиг файла, потому что он хорошо задокументирован. Вместо этого расскажу про настройку тех частей, которые описаны хуже или имеют подводные камни.

Exit nodes и subnets routing

По умолчанию Tailscale работает как overlay сеть, которая соединяет несколько хостов. Однако он умеет и в роутинг подсетей. А машина, через которую проксируется весь трафик (т.е. подсеть 0.0.0.0/0), в Tailscale называется exit node. Для них есть отдельная кнопка в клиенте, которая включает и выключает роутинг по желанию пользователя.

Включается роутинг в два шага. Сначала при регистрации хоста указать флаг --advertise-routes с перечнем подсетей, либо --advertise-exit-node. А затем идёт не совсем очевидная вещь — надо разрешить эти подсети в Headscale. Сделать это можно такой командой:

headscale routes enable -i -r <список подсетей>

С версии 0.16.0 появилась возможность использовать флаг --all вместо перечня подсетей.

ACL

Пожалуй, самая привлекательная для нашей команды фича — возможность настраивать правила взаимодействия между клиентами прямо на управляющем сервере. Описывается это в специальном файлике, в формате YAML или HuJSON. Последний — это надстройка над обычным JSON, но с комментариями и висящими запятыми. Мы у себя используем YAML, потому что нам так проще, поэтому в примерах буду использовать его же.

Итак, синтаксис можно посмотреть в официальной документации. Файл состоит из:

  • acls, списка правил.
  • groups, групп пользователей.
  • hosts, читаемых алиасов для IP адресов.
  • tagOwners, списка пользователей, которые могут назначать тэги на устройства.

Этот список меньше, чем в официальной документации из ссылки выше. Как я и говорил, часть фичей в ACL не реализована, однако основной костяк работает.

Прежде чем переходить к примерам, надо немного рассказать про тэги. В блоге Tailscale им посвящены две замечательные статьи: что это такое и как они появились. Если коротко, тэг — это метка, к которой можно привязать одно или несколько ACL правил. Удобство в том, что у одного устройства может быть много тэгов с разным набором правил. Давайте взглянем на пример:

hosts: logging-machine: 100.64.0.1 monitoring-machine: 100.64.0.2 acls: - action: accept src: - tag:monitoring dst: - logging-machine:443 - action: accept src: - tag:logging dst: - monitoring-machine:443 - action: accept src: - group:dev dst: - tag:staging:22,80,443 groups: group:dev: - vasya - petya

У нас есть:

  • 3 тэга: tag:staging, tag:monitoring, tag:logging
  • 2 хоста logging-machine и monitoring-machine
  • группа group:dev из двух разработчиков: Васи и Пети

Вот что означают правила, которые мы описали:

  • Все хосты, которые помечены тэгом tag:monitoring, могут ходить на 443-й порт хоста monitoring-machine.
  • Все хосты, которые помечены тэгом tag:logging, могут ходить на 443-й порт хоста logging-machine.
  • Все пользователи из группы group:dev могут ходить на все хосты, с тэгом tag:staging.

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

К staging-app привязаны все 3 тэга
К staging-app привязаны все 3 тэга

Соответственно, убирая тэги, мы убираем и правила, привязанные к ним:

Убрали тэг tag:monitoring, пропал доступ к monitoring-machine
Убрали тэг tag:monitoring, пропал доступ к monitoring-machine
Убрали tag:staging, разработчики больше не смогут зайти на staging-app
Убрали tag:staging, разработчики больше не смогут зайти на staging-app

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

Баг со звёздочкой

Звёздочкой обозначается любой хост, пользователь или порт. Можно использовать * вместо хоста или порта, но нельзя использовать их вместе *:*

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

acls: - action: accept src: - group:admin dst: - '*:*'

Чтобы это обойти, мы сделали тэги, которыми мы помечаем каждую машину в конкретном окружении: prod, test и staging. Но можно сделать ещё проще и ввести один «админский» тэг для всего.

acls: - action: accept src: - group:admin dst: - 'tag:prod:*' - 'tag:infra:*' - 'tag:staging:*' # Так тоже можно - action: accept src: - group:admin dst: - 'tag:admin:*'

Тэги и владельцы

У каждого тэга есть владелец, и только он может прикрепить его к хосту. Для этого в ACL есть секция tagOwners, в котором прописывается соответствие тэга и пользователя. И это будет работать только в случае, если хост был авторизован от имени этого пользователя. Сейчас объясню. Допустим, есть такой кусок ACL:

tagOwners: tag:prod: - vasya

Когда мы регистрируем хост командой tailscale up --advertise-tags tag:prod, нам предложат авторизоваться. Тэг будет работать только если мы авторизуемся от имени пользователя vasya, как и прописано в tagOwners.

Чтобы не мучаться с разными людьми, мы завели сервисного пользователя (namespace в терминологии Headscale), сделали его владельцем всех тэгов, и от его имени авторизуем все хосты в инфраструктуре.

headscale namespace create internal headscale preauthkeys create -n internal --reusable --expiration 9999h tailscale up --authkey <key> --advertise-tags tag:prod

И заодно, получается красивый magic домен hostname.internal.com. А секция с tagOwners выглядит так:

tagOwners: tag:prod: - internal tag:staging: - internal tag:infra: - internal tag:database: - internal ...

К сожалению, список получается довольно длинный, и с этим пока ничего не поделаешь.

Exit node в ACL

Чтобы дать пользователю возможность пользоваться экзит нодой, но не открывать остальные порты, можно использовать такую запись:

acls: - action: accept src: - group:exit-node-users dst: - exit-node:0

Но есть подвох. По умолчанию, если ACL правила пустые, то у всех есть доступ ко всему. Однако, если есть хоть одно правило, включается режим белого списка. И это касается экзит нод — нужно явно прописывать доступ к публичным подсетям. В Tailscale для этого есть волшебная автогруппа autogroup:internet, а в Headscale она пока не работает, поэтому будем использовать калькулятор подсетей. Мы у себя исключили сеть Tailscale и приватные подсети 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, получилось такое правило:

acls: - action: accept src: - '*' dst: - tag:exit-node:0 # workaround for exit nodes, allow all IPv4 range except for - 0.0.0.0/5:* # 10.0.0.0/8, 100.64.0.0/10, 172.16.0.0/12, 192.168.0.0/16 - 8.0.0.0/7:* - 11.0.0.0/8:* - 12.0.0.0/6:* - 16.0.0.0/4:* - 32.0.0.0/3:* - 64.0.0.0/3:* - 96.0.0.0/6:* - 100.0.0.0/10:* - 100.128.0.0/9:* - 101.0.0.0/8:* - 102.0.0.0/7:* - 104.0.0.0/5:* - 112.0.0.0/4:* - 128.0.0.0/2:* - 160.0.0.0/5:* - 168.0.0.0/6:* - 172.0.0.0/12:* - 172.32.0.0/11:* - 172.64.0.0/10:* - 172.128.0.0/9:* - 173.0.0.0/8:* - 174.0.0.0/7:* - 176.0.0.0/4:* - 192.0.0.0/9:* - 192.128.0.0/11:* - 192.160.0.0/13:* - 192.169.0.0/16:* - 192.170.0.0/15:* - 192.172.0.0/14:* - 192.176.0.0/12:* - 192.192.0.0/10:* - 193.0.0.0/8:* - 194.0.0.0/7:* - 196.0.0.0/6:* - 200.0.0.0/5:* - 208.0.0.0/4:* - 224.0.0.0/3:*

Заключение

Headscale и Tailscale стали для меня открытием года. Несмотря на баги и недоработки, управлять VPN сетью стало гораздо проще по сравнению с OpenVPN и голым WireGuard. Абсолютно вся конфигурация происходит на стороне сервера, а клиенту для подключения требуется всего лишь зайти в свой гугл аккаунт. Headscale активно допиливается сообществом и новые релизы выходят довольно часто. Да и разработчики Tailscale тоже помогают поддерживать проект.

1515
2 комментария

Пробовали web-морду для headscale? https://github.com/gurucomputing/headscale-ui

3
Ответить

Ещё нет, но видел что появилось несколько реализаций. Если честно, я почти всё время провожу в терминале, поэтому когда-то давно настроил удалённый доступ к CLI на сервере по gRPC, поэтому необходимости в веб-интерфейсе и не возникало.

Ответить