Всем привет! Вчера я опубликовал статью о том, как настроить свой собственный VPN с помощью Outline и VPS. В конце статьи было голосование за тему программного взаимодействия с VPN через Python. Многие из вас выразили интерес, и вот продолжение специально для вас.

Для начала необходимо настроить VPN и получить ответ от вашего VPS сервера в следующем формате (данные вымышлены):

{"apiUrl":"https://000.86.000.204:22422/lvOQvrWWEOOfY9SSOBbgsesFP","certSha256":"AA26A5X1840FD23AD0F018550A5F9B7UHDA392D3da89EE0D08A1D5331KJF358"}

О том, как это сделать и зачем, вы можете прочитать в моей предыдущей статье. Для продолжения у нас должны быть:

  • VPS сервер с установленным Outline

  • Скачанная программа Outline Manager (для удобства тестирования)

  • Скачанная программа Outline Client (для проверки работоспособности ключей)

  • Установленный Python 3 (не ниже версии 3.8)

Если все это у вас есть, давайте запустим IDE и начнем писать код (я буду использовать PyCharm).

Сегодня мы напишем код по правилам, используя requirements.txt и .env.

Настраиваем файл .env:

API_URL=https://000.86.000.204:22422/lvOQvrWWEOOfY9SSOBbgsesFP
CERT_SHA=AA26A5X1840FD23AD0F018550A5F9B7UHDA392D3da89EE0D08A1D5331KJF358

Заполняем файл requirements.txt:

outline-vpn-api
python-decouple

Устанавливаем необходимые модули:

pip install -r requirements.txt

Описание установленных модулей:

outline-vpn-api – это обертка вокругrequests, настроенная для отправки запросов на сервера Outline. Можно было бы написать свою, но эта библиотека достаточно логична и понятна, так что не вижу смысла тратить время.

python-decouple – модуль для работы с .env. Я использую его давно, и он никогда меня не подводил, если вы привыкли к другой реализации - на ваше усмотрение.

Настраиваем файл config.py:

from decouple import config
from outline_vpn.outline_vpn import OutlineVPN

api_url = config('API_URL')
cert_sha256 = config('CERT_SHA')

client = OutlineVPN(api_url=api_url, cert_sha256=cert_sha256)

Здесь мы импортируем модуль для работы с .env и объект класса OutlineVPN. Далее настраиваем клиент для взаимодействия с Outline.

Файл main.py

Прежде чем приступить к написанию кода, немного поясню, чем мы будем заниматься.

Библиотека outline-vpn-api достаточно проста, но я предлагаю написать еще одну обертку над ней, свою. Это поможет в случаях, когда задачи не стандартные, например, обработка данных на лету.

Импортируем нашего клиента из настроек:

from config import client

Пишем функцию для преобразования гигабайт в байты:

def gb_to_bytes(gb: float):
    bytes_in_gb = 1024 ** 3  # 1 ГБ = 1024^3 байт
    return int(gb * bytes_in_gb)

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

Получение информации о всех ключах:

def get_keys():
    return client.get_keys()

Проходимся по ключам циклом и смотрим данные:

vpn_keys = get_keys()
for key in vpn_keys:
    print(key)

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

  • key_id (строковый идентификатор айди, очень важный)

  • name (строка, имя ключа, иногда полезно, например, чтоб понимать какой ключ и под какое устройство или чей ключ)

  • access_url (строка, ключ для подключения к VPN)

  • used_bytes (использованное количество байтов)

  • data_limit (лимит на использование данных)

  • Password, port и method (в данной статье рассматривать не будем, достаточно специфическая информация)

Pycharm сам подсказывает какие данные мы можем получить через точку.
Pycharm сам подсказывает какие данные мы можем получить через точку.

Получение информации по конкретному ключу:

def get_key_info(key_id: str):
    return client.get_key(key_id)

key_info = get_key_info("100")
print(key_info.access_url)

На примере выше мы получили не весь объект ключа, а конкретное значение (access_url), которое является самим ключом доступа к VPN.

Создание нового ключа:

def create_new_key(key_id: str = None, name: str = None, data_limit_gb: float = None):
    return client.create_key(key_id=key_id, name=name, data_limit=gb_to_bytes(data_limit_gb))

Кроме тех параметров, что я передал, функция client.create_key также может принимать: method, password и port. Эти параметры мы рассмотрим в следующий раз, а сейчас давайте разберемся, что же принимает наша функция:

  • key_id – строковый айдишник, если оставить None, то будет по умолчанию присвоен айди такого вида «1» (числовая строка)

  • name – имя ключа (описывал выше зачем нужен параметр)

  • data_limit_gb – тут мы передаем в виде float значения количество ГБ, которое будет доступно пользователю на 1 месяц (выше уже написали функцию, которая будет переводить ГБ в байты)

Давайте создадим первый ключ с такими данными:

  • key_id = None (пусть сам Outline придумает айди)

  • name = «HabrKey»

  • data_limit_gb = 1.5

new_key_info = create_new_key(name='HabrKey', data_limit_gb=1.5)
print(new_key_info)

Отлично! Наш ключ создан. На скриншоте выше вы можете увидеть, что ключу было присвоено имя, которое мы передали, и задан лимит по данным. При этом key_id был присвоен автоматически ("6"). Этот идентификатор понадобится нам для тестирования других функций.

Давайте посмотрим в программе Outline Manager создался ли наш ключ:

Для обновления окна Outline Manager воспользуйтесь CTRL + R
Для обновления окна Outline Manager воспользуйтесь CTRL + R

Мы видим, что ключ создан и лимит данных немного больше, чем мы указали (1.61 ГБ). Это связано с тем, что моя функция была намеренно упрощена. В качестве домашнего задания вы можете написать аналогичную функцию, которая будет точно преобразовывать любое значение в байты. Если у вас получится, поделитесь ею в комментариях.

Переименование ключа:

def rename_key(key_id: str, new_key_name: str):
    return client.rename_key(key_id, new_key_name)

status_rename = rename_key(key_id='6', new_key_name='HabrKeyNewName')
print(status_rename)

Думаю тут все понятно. Передали идентификатор и новое имя. Давайте посмотрим что получилось:

Обновление лимита данных:

def upd_limit(key_id: str, data_limit_gb: float):
    return client.add_data_limit(key_id, gb_to_bytes(data_limit_gb))

status_update = upd_limit(key_id='6', data_limit_gb=5)
print(status_update)

Эта функция предназначена для увеличения месячного лимита данных. В примере выше мы увеличили лимит до 5 ГБ. Проверяем:

Снова наблюдаем небольшое отклонение в объеме ГБ, но видим, что лимит успешно увеличен.

Снимаем лимит данных:

def delete_limit(key_id: str):
    return client.delete_data_limit(key_id)

status_delete_limit = delete_limit(key_id='6')
print(status_delete_limit)

Эта функция полностью снимает ограничение на лимит по данным. Проверим:

Удаление ключа:

def delete_key(key_id: str):
    return client.delete_key(key_id)

status_delete_key = delete_key("6")
print(status_delete_key)

Получение технической информации о сервере:

def get_service_info():
    return client.get_server_information()

В библиотеке outline-vpn-api есть и другие методы, но так как я пишу это руководство для тех, кто только знакомится с Outline, не вижу смысла перегружать информацией. Думаю, те, кому потребуется дополнительная функциональность, смогут разобраться самостоятельно.

Выводы

Несмотря на кажущуюся простоту программного взаимодействия с Outline, система достаточно мощная, удобная и гибкая. Рекомендую всем, как минимум, протестировать ее.

На этом пока все. Подписывайтесь – у меня для вас запасено много интересной информации. До скорого.

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


  1. salbey
    10.06.2024 07:45

    Добрый день, расскажите про динамические ключи Outline. У меня не получилось сформировать по руководству.


    1. yakvenalex Автор
      10.06.2024 07:45

      Добрый день, что вы подразумеваете под "динамические ключи"?


      1. salbey
        10.06.2024 07:45

        Dynamic Access Keys — это функция в Outline Client, которая позволяет хранить информацию о доступе к VPN в удалённом месте и обновлять её динамически.

        Это позволяет менять местоположение VPN, порт, пароль для доступа к нему и метод шифрования на лету без необходимости повторно выдавать ключи доступа всем пользователям.


  1. Chupaka
    10.06.2024 07:45

    bytes_in_gb = 1024 ** 3 # 1 ГБ = 1024^3 байт

    И дальше по тексту видим, что всё же 1 ГБ = 1000^3 байт :)