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

Пользовательский флоу

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

Концепт флоу

Из приветственного сообщения можно перейти в "Условия", из "Условий" — назад на главный экран; при нажатии на кнопку с каким-либо периодом выставляется счет, который оплачивается прямо в мессенджере, а после успешной оплаты пользователь получает файл конфигурации и инструкции по его настройке

Для ответов на часто задаваемые вопросы и для повторного скачивания файла конфигурации были выдуманы команды /help и /download

База данных

Конечно, для корректной работы нужно где-то хранить данные, а для этого их нужно как-то организовать. В самом начале я определил для себя три сущности: пользователи, счета и серверы. Структура БД выглядела примерно так:

Структура БД v1

Сначала я подумал, что будет "круто" с точки зрения технической реализации высчитывать дату окончания подписки пользователей на основе информации об оплаченных инвойсах, но очень быстро передумал, когда дело дошло до написания SQL запросов для шедулера, который должен был управлять клиентскими сертификатами и уведомлять пользователей о скором окончании подписки

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

Структура БД v2

В этой версии к таблице users был добавлен столбец end_timestamp, определяющий дату окончания подписки, а к таблице servers — столбец user_count, по которому для пользователя определялся свободный сервер. Эта версия задержалась почти до выпуска проекта в прод

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

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

Структура БД v3

Тут у таблицы users появились такие столбцы, как is_trial_avl для определения, доступен ли пользователю триал, is_notified для того, чтобы определить, был ли пользователь уведомлен об окончании подписки, и для пущей уверенности is_revoked, чтобы отслеживать был ли успешно отозван доступ в случае окончания подписки

Интерфейс управления

Одним из основных вопросов было то, как управлять данными бота. Изначально хотелось написать какой-нибудь REST интерфейс с простенькой страничкой, но очень не хотелось открывать порты наружу. Потому в качестве элемента управления был выбран сам бот

Чтобы разграничить код админских команд от основного был написан класс AdminMixin, содержащий реализацию таких сервисных команд, как create_server, list_users или get_metrics. От этого класса начал наследоваться класс бота, а все методы класса AdminMixin (они же обработчики) были покрыты декоратором @admin_only

Инфраструктура

С точки зрения инфраструктуры проект можно разделить на 3 составляющие: код бота, код шедулера и управляющий сокет-сервер: обычный TCP сокет-сервер, целью которого является принятие и выполнение команды на генерацию клиентских сертификатов и файла конфигурации или отзыв клиентского сертификата

Инфраструктура

На VPS с ботом крутится БД без открытых вовне портов, код самого бота и шедулер, проверяющий у кого скоро закончится подписка и кого надо об этом уведомить, а также чей сертификат пора отозвать. И бот, и шедулер общаются с VPN серверами посредством сокет-соединения. Я решил не допускать ошибок, допущенных при проектировании БД, и чтобы не усложнять проект, от несанкционированного использования защитил контролирующий сокет-сервер самым простым, выдуманным протоколом с использованием секретного ключа размером 128 байт

Возможность подобрать такой ключ стремится к нулю и равна 1 к 62^128 , что равно примерно 1 к 266*10^227

Для пущей надежности и повышения скорости, стоило бы уменьшить размер ключа и закрыть входящие соединения для всех, кроме конкретного IP, но такое решение усложняло бы переезд в случае необходимости, да и по результатам тестов бот и без того отрабатывал достаточно быстро

Как это работает?

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

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

Шедулер можно было бы реализовать на базе Kafka или другого брокера сообщений и даже с использованием готового фреймворка для фоновых задач, но в процессе нагрузочного тестирования это было сочтено нецелесообразным

Управляющий сокет-сервер, получая команду, от имени конкретного пользователя запускает скрипт на bash, который либо выполняет ряд инструкций для генерации сертификатов и конфигураций, либо ряд инструкций для отзыва сертификатов, перегенерации crl.pem файла, а также отключения пользователя от сервера через managment interface сервера OpenVPN

Для быстрого и удобного масштабирования был написан скрипт на bash, который за несколько инструкций ввода позволяет поднять готовый OpenVPN сервер, настроить пользователей, поднять системный сервис с управляющим сокет-сервером и ограничить сеть. Этот же скрипт рандомным образом выбирает порт для управляющего сокет-сервера и генерирует секретный ключ. По результатам его выполнения в терминале выводится команда, которую нужно ввести админу для добавления нового сервера в базу бота, а на почту приходит вложение с кредсами сервисной учетки и авторизованным ssh ключом для неё

Заключение

На все работы у меня ушло примерно полторы недели с учетом отладки. Хочу сказать большое спасибо моим друзьям, которые очень помогли мне с обкаткой и тестированием. В качестве системы оплаты использовался Bot Payments — фича, которая уже достаточно давно появилась в телеграм, и которую хотелось, но никак не доводилось попробовать

Где посмотреть на результат?

Посмотреть и попробовать бота можно тут

Вот такой получился пет-проект... Большое спасибо за ваше внимание!

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