Сказ о том как pet-project превратился в небольшой пассивный доход (часть 2)
Первая Часть
DNS Балансировка
Предыдущая часть закончилась неудачной балансировкой, которая не решает практически никаких проблем. В комментариях кто-то спросил, почему я не использовал балансировку на уровне DNS. Так вот, я ее использовал. Оказалось, что c помощью DNS записей можно организовать балансировку Round Robin. Для этого в конфигурации Wireguard всего лишь нужно использовать доменное имя вместо IP адреса. Теперь конфигурация Wireguard будет выглядеть вот так:
[Interface]
PrivateKey =
Address = /32
DNS = 8.8.8.8, 1.1.1.1
[Peer]
PublicKey =
AllowedIPs = 0.0.0.0/0
Endpoint = domainName.com:
Схема запросов будет выглядеть примерно так:
Рис. 1
Плюсы и минусы DNS балансировки
Данную балансировку я использовал довольно длительное время.
Плюсы:
Нет необходимости иметь свой собственный сервер для балансировки, соответственно, не нужно платить за дополнительный трафик и дополнительные серверы.
Для добавления/удаления новых серверов достаточно добавить/удалить A запись у совего DNS провайдера.
Минусы:
При добавлении нового сервера может быть большой временной лаг, так как нужно некоторое время, прежде чем DNS серверы подтянут к себе обновленную информацию.
При удалении сервера необходимо сначала удалить DNS запись и только через некоторое время можно потушить сервер, так как не все DNS серверы успеют убрать запись о старом IP адресе.
Если у нас падает какой-то из серверов, DNS запись не обновится автоматически, и некоторая часть пользователей будет пытаться подсоединиться к серверу, который не работает
Балансировка не происходит оптимальным образом, так как используется алгоритм Round Robin.
Про получение пользователем конфигурации
рис. 2
Таблица actions:
id | int64 (уникальный идентификатор каждой записи) |
action | ENUM (1 — подключить пользователя; 2 — отключить пользователя) |
user_id | uuid (уникальный идентификатор пользователя) |
timestamp | timestamptz (время создания записи) |
Таблица users:
id | uuid (уникальный идентификатор пользователя) |
chat_id | int64 (уникальный id пользователя в Telegram) |
public_key | text (публичный ключ Wireguard) |
private_key | text (приватный ключ Wireguard) |
wireguard_ip | text (уникальный ip адрес каждого пользователя внутри интерфейса Wireguard) |
subcription_end | timestamptz (время, когда у пользователя кончится подписка) |
Как я уже говорил в прошлой части, у меня есть Master и Slave хосты:
Master включает в себя:
TelegramBot — отвечает за взаимодействие с пользователем. Следит за состоянием подписки, принимает платежи от пользователей, регистрирует новых пользователей, возвращает пользователю конфигурацию для подключения к VPN.
SlavePingWorker — отвечает за проверку исправности серверов. Каждые несколько минут он пингует все slave. Если slave не отвечает, то отправляется ALERT.
PostgresDB — хранит данные пользователей и таблицу actions.
Server — отдает slave хостам записи из таблицы actions.
Slave включает в себя:
Рассмотрим шаги на рис. 2
Пользователь делает запрос в TelegramBot для получения конфигурации.
TelegramBot получает chat_idпользователя. Далее он генерирует public_key, private_key, wireguard_ip и добавляет все эти данные в таблицу users. Также TelegramBot делает следующую запись в таблице actions:
id | action | user_id | timestamp |
1 | 1 (подключить пользователя) | <Текущее время> |
Создается конфигурация пользователя и возвращется пользователю в виде .conf файла.
Slave1Worker и Slave2Worker каждые 5 секунд делают запрос в Master на получение свежих записей из таблицы actions. Если action = 1, то ключи пользователя добавляются в Wireguard, если action = 2, то ключи пользователя удаляются.
Безопасность
Так как master и slave хосты отправляют запросы по IP адресу (неудобно использовать доменные имена, так как по доменному имени у нас реализована балансировка), нету возможности использовать SSL. Из-за этого возникает 2 уязвимости связанные с Man-in-the-middle attack.
Передаваемые между master и slave данные возможно прочесть (злоумышленник может украсть ключи пользователя и использовать VPN вместо него).
Запросы между master и slave можно перехватить и потом отправить повторно. Так как API master и slave не является идемпотентным, то возможно изменить внутреннее состояние системы и вызвать ошибки.
Было приниято решение все запросы шифровать с помощью RSA ключа. Для этого создаются пары приватный/публичный ключ. Выглядит это так:
рис. 3
Запрос кодируется с помощью master_private_key
Запрос уходит во внешнюю сеть
Запрос приходит в slave хост
Запрос декодируется с помощью master_public_key
Формируется ответ
Ответ кодируется с помощью slave_private_key
Ответ уходит во внешнюю сеть
Master получает ответ
Ответ декодируется с помощью slave_public_key и обрабатывается в responseHandler
Шифрование запросов между серверами делает невозможным прочесть передаваемые данные. Для того чтобы защититься от повторного отправления запроса, было решено добавлять в запрос timestamp. В таком случае, при получении запроса можно проверять, когда был подписан запрос, и, если подпись старая, то отклонять такие запросы. Я решил использовать дедлайн для подписи в 5 секунд. Этого оказалось достаточным для того, чтобы запросы успевали доходить до адресата.
Далее будет еще одна статья, в которой я расскажу подробнее про организацию кода в своем проекте, github actions, про делегацию балансировки клиентам и про то, какие есть планы на будущее.