Сказ о том как 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

Рис. 1

Плюсы и минусы DNS балансировки

Данную балансировку я использовал довольно длительное время.

Плюсы:

  • Нет необходимости иметь свой собственный сервер для балансировки, соответственно, не нужно платить за дополнительный трафик и дополнительные серверы.

  • Для добавления/удаления новых серверов достаточно добавить/удалить A запись у совего DNS провайдера.

Минусы:

  • При добавлении нового сервера может быть большой временной лаг, так как нужно некоторое время, прежде чем DNS серверы подтянут к себе обновленную информацию.

  • При удалении сервера необходимо сначала удалить DNS запись и только через некоторое время можно потушить сервер, так как не все DNS серверы успеют убрать запись о старом IP адресе.

  • Если у нас падает какой-то из серверов, DNS запись не обновится автоматически, и некоторая часть пользователей будет пытаться подсоединиться к серверу, который не работает

  • Балансировка не происходит оптимальным образом, так как используется алгоритм Round Robin.

Про получение пользователем конфигурации

рис. 2

рис. 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

  1. Пользователь делает запрос в TelegramBot для получения конфигурации.

  2. TelegramBot получает chat_idпользователя. Далее он генерирует public_key, private_key, wireguard_ip и добавляет все эти данные в таблицу users. Также TelegramBot делает следующую запись в таблице actions:

id

action

user_id

timestamp

1

1 (подключить пользователя)

<Текущее время>

  1. Создается конфигурация пользователя и возвращется пользователю в виде .conf файла.

  2. Slave1Worker и Slave2Worker каждые 5 секунд делают запрос в Master на получение свежих записей из таблицы actions. Если action = 1, то ключи пользователя добавляются в Wireguard, если action = 2, то ключи пользователя удаляются.

Безопасность

Так как master и slave хосты отправляют запросы по IP адресу (неудобно использовать доменные имена, так как по доменному имени у нас реализована балансировка), нету возможности использовать SSL. Из-за этого возникает 2 уязвимости связанные с Man-in-the-middle attack.

  1. Передаваемые между master и slave данные возможно прочесть (злоумышленник может украсть ключи пользователя и использовать VPN вместо него).

  2. Запросы между master и slave можно перехватить и потом отправить повторно. Так как API master и slave не является идемпотентным, то возможно изменить внутреннее состояние системы и вызвать ошибки.

Было приниято решение все запросы шифровать с помощью RSA ключа. Для этого создаются пары приватный/публичный ключ. Выглядит это так:

рис. 3

рис. 3

  1. Запрос кодируется с помощью master_private_key

  2. Запрос уходит во внешнюю сеть

  3. Запрос приходит в slave хост

  4. Запрос декодируется с помощью master_public_key

  5. Формируется ответ

  6. Ответ кодируется с помощью slave_private_key

  7. Ответ уходит во внешнюю сеть

  8. Master получает ответ

  9. Ответ декодируется с помощью slave_public_key и обрабатывается в responseHandler

Шифрование запросов между серверами делает невозможным прочесть передаваемые данные. Для того чтобы защититься от повторного отправления запроса, было решено добавлять в запрос timestamp. В таком случае, при получении запроса можно проверять, когда был подписан запрос, и, если подпись старая, то отклонять такие запросы. Я решил использовать дедлайн для подписи в 5 секунд. Этого оказалось достаточным для того, чтобы запросы успевали доходить до адресата.

Далее будет еще одна статья, в которой я расскажу подробнее про организацию кода в своем проекте, github actions, про делегацию балансировки клиентам и про то, какие есть планы на будущее.

© Habrahabr.ru