chore: реализована работа с самоподписными сертификатами
This commit is contained in:
1 parent
9bb3a2fc5d
commit
602199d510
23 files changed
+524
-108
No files matched your search
@@ -47,7 +47,7 @@ FAQ_TEXT = '''<b>Что это за бот?</b>
|
||||
Бот настраивает ваш собственный VPN на вашем сервере: панель 3x-ui с подключениями vless + tls + reality. По желанию перед ней ставится сервер маскировки.
|
||||
|
||||
<b>Что нужно от меня?</b>
|
||||
Арендованный сервер с Debian 13 (от 2 ядер, 2 Гб памяти, 20 Гб диска): ip и пароль root. По желанию - второй такой же сервер для маскировки, домен и прокси на выходе.
|
||||
Арендованный сервер с Debian 12 или 13 (от 2 ядер, 2 Гб памяти, 20 Гб диска): ip и пароль root. По желанию - второй такой же сервер для маскировки, домен и прокси на выходе.
|
||||
|
||||
<b>Зачем сервер маскировки?</b>
|
||||
Клиенты подключаются к серверу маскировки, он пересылает трафик на панель. Адрес панели никто не видит. Для маскировки нужен домен.
|
||||
|
||||
@@ -167,6 +167,17 @@ class Point(models.Model):
|
||||
def is_busy(self) -> bool:
|
||||
return self.status in (self.Status.INSTALLING, self.Status.RELEASING)
|
||||
|
||||
@property
|
||||
def panel_host(self) -> str:
|
||||
"""Адрес панели для https и подписки: домен, без него - ip панели. По ssh ходим только по ip."""
|
||||
return self.domain or self.panel.address
|
||||
|
||||
def panel_url(self) -> str:
|
||||
return f'https://{self.panel_host}:{self.panel_port}/{self.panel_path}'
|
||||
|
||||
def subscription_url(self, sub_id: str) -> str:
|
||||
return f'https://{self.panel_host}:{self.sub_port}/{self.sub_path}/{sub_id}'
|
||||
|
||||
def set_status(self, status: str, error: str = ''):
|
||||
self.status = status
|
||||
self.error = error
|
||||
|
||||
+16
-1
@@ -2,6 +2,7 @@
|
||||
from contextlib import contextmanager
|
||||
|
||||
import serverus
|
||||
from django.conf import settings
|
||||
|
||||
from .models import Point, Server
|
||||
|
||||
@@ -63,7 +64,7 @@ def set_domain(point: Point, domain: str):
|
||||
if not domain and point.mask:
|
||||
raise ValueError('маскировка работает только с доменом')
|
||||
with _installing(point):
|
||||
_check(point.panel, serverus.XuiPanel(point.panel.host()).issue_certificate(domain))
|
||||
_check(point.panel, serverus.XuiPanel(point.panel.host()).issue_certificate(domain, local_ca()))
|
||||
point.domain = domain
|
||||
point.save(update_fields=['domain'])
|
||||
|
||||
@@ -78,6 +79,19 @@ def set_outbound_proxy(point: Point, proxy: str):
|
||||
point.save(update_fields=['outbound_proxy'])
|
||||
|
||||
|
||||
def local_ca() -> serverus.LocalCA | None:
|
||||
"""Свой CA, если панели работают не с Let's Encrypt (SERVERUS_CERTIFICATES=local)."""
|
||||
if settings.SERVERUS_CERTIFICATES == 'local':
|
||||
return serverus.LocalCA(settings.SERVERUS_CA_DIR)
|
||||
return None
|
||||
|
||||
|
||||
def xui_api(point: Point) -> serverus.XuiApi:
|
||||
"""API панели точки: по домену, без него - по ip панели."""
|
||||
ca = local_ca()
|
||||
return serverus.XuiApi(point.panel_url(), point.panel_api_token, ca_file=str(ca.cert_path) if ca else None)
|
||||
|
||||
|
||||
def provision(server: Server):
|
||||
"""Базовая настройка сервера и специализация по его роли."""
|
||||
_bootstrap(server)
|
||||
@@ -114,6 +128,7 @@ def _install_panel(server: Server):
|
||||
sub_port=point.sub_port,
|
||||
sub_path=point.sub_path,
|
||||
domain=point.domain,
|
||||
ca=local_ca(),
|
||||
))
|
||||
point.panel_api_token = result.data.get('api_token', '')
|
||||
point.save(update_fields=['panel_api_token'])
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import tempfile
|
||||
from unittest import mock
|
||||
|
||||
import serverus
|
||||
from django.test import TestCase
|
||||
from django.test import TestCase, override_settings
|
||||
from Telegram.models import User as TelegramUser
|
||||
|
||||
from . import services
|
||||
@@ -161,6 +162,29 @@ class InstallTests(TestCase):
|
||||
self.assertEqual(Server.objects.get(pk=panel.pk).private_key, private_key)
|
||||
|
||||
|
||||
def test_install_with_local_ca(self):
|
||||
point = create_point(mask=None)
|
||||
runner = FakeRunner()
|
||||
with tempfile.TemporaryDirectory() as directory, patch_runner(runner):
|
||||
with override_settings(SERVERUS_CERTIFICATES='local', SERVERUS_CA_DIR=directory):
|
||||
services.install_point(point)
|
||||
api = services.xui_api(point)
|
||||
self.assertIn('BEGIN CERTIFICATE', runner.call('xui', '10.0.0.1')[1]['cert_fullchain'])
|
||||
self.assertIsNotNone(api.context)
|
||||
|
||||
def test_letsencrypt_by_default(self):
|
||||
point = create_point(mask=None)
|
||||
runner = FakeRunner()
|
||||
with patch_runner(runner):
|
||||
services.install_point(point)
|
||||
self.assertNotIn('cert_fullchain', runner.call('xui', '10.0.0.1')[1])
|
||||
|
||||
def test_panel_urls_use_domain_or_panel_ip(self):
|
||||
point = create_point(mask=None)
|
||||
self.assertTrue(point.panel_url().startswith(f'https://10.0.0.1:{point.panel_port}/'))
|
||||
point.domain = 'example.com'
|
||||
self.assertEqual(point.subscription_url('abc'), f'https://example.com:{point.sub_port}/{point.sub_path}/abc')
|
||||
|
||||
def test_install_sets_outbound_proxy(self):
|
||||
point = create_point(mask=None, outbound_proxy=Point.CF_WARP)
|
||||
runner = FakeRunner()
|
||||
|
||||
Reference in new issue
Block a user