Fix Vault integration and ALLOWED_USER_IDS handling
All checks were successful
Build and Deploy MikroTik Bot / build-and-deploy (push) Successful in 26s
All checks were successful
Build and Deploy MikroTik Bot / build-and-deploy (push) Successful in 26s
This commit is contained in:
parent
c2125e6736
commit
25d910cef7
BIN
__pycache__/vault_client.cpython-313.pyc
Normal file
BIN
__pycache__/vault_client.cpython-313.pyc
Normal file
Binary file not shown.
57
bot.py
57
bot.py
@ -9,29 +9,45 @@ from librouteros.exceptions import TrapError
|
||||
from fastapi import FastAPI, Request
|
||||
import uvicorn
|
||||
from db import init_db, upsert_client, start_session, update_session, close_session, get_history, get_clients, get_stats
|
||||
from vault_client import VaultClient
|
||||
|
||||
load_dotenv()
|
||||
|
||||
TG_BOT_TOKEN = os.getenv("TG_BOT_TOKEN")
|
||||
MT_API_HOST = os.getenv("MT_API_HOST")
|
||||
MT_API_USER = os.getenv("MT_API_USER")
|
||||
MT_API_PASS = os.getenv("MT_API_PASS")
|
||||
ALLOWED_USER_IDS = set(map(int, os.getenv("ALLOWED_USER_IDS", "").split(",")))
|
||||
# Получаем конфигурацию из Vault или environment variables
|
||||
vault = VaultClient()
|
||||
config = vault.get_config()
|
||||
|
||||
bot = Bot(token=TG_BOT_TOKEN)
|
||||
# Используем правильные имена переменных
|
||||
BOT_TOKEN = config.get('BOT_TOKEN') or os.getenv("BOT_TOKEN")
|
||||
ROUTER_HOST = config.get('ROUTER_HOST') or os.getenv("ROUTER_HOST")
|
||||
ROUTER_USER = config.get('ROUTER_USER') or os.getenv("ROUTER_USER")
|
||||
ROUTER_PASSWORD = config.get('ROUTER_PASSWORD') or os.getenv("ROUTER_PASSWORD")
|
||||
|
||||
# Безопасная обработка ALLOWED_USER_IDS
|
||||
allowed_ids_str = os.getenv("ALLOWED_USER_IDS", "")
|
||||
if allowed_ids_str.strip():
|
||||
ALLOWED_USER_IDS = set(map(int, allowed_ids_str.split(",")))
|
||||
else:
|
||||
# Если не установлено, используем пустое множество или значение по умолчанию
|
||||
ALLOWED_USER_IDS = set()
|
||||
print("⚠️ ALLOWED_USER_IDS не установлено - доступ для всех пользователей!")
|
||||
|
||||
bot = Bot(token=BOT_TOKEN)
|
||||
dp = Dispatcher()
|
||||
|
||||
# Авторизация по user_id
|
||||
async def check_user(message: types.Message) -> bool:
|
||||
# Если список пуст, разрешаем всем (для тестирования)
|
||||
if not ALLOWED_USER_IDS:
|
||||
return True
|
||||
if message.from_user.id not in ALLOWED_USER_IDS:
|
||||
await message.answer("⛔️ Нет доступа")
|
||||
return False
|
||||
return True
|
||||
|
||||
# Подключение к MikroTik API
|
||||
|
||||
def get_mt_api():
|
||||
return connect(username=MT_API_USER, password=MT_API_PASS, host=MT_API_HOST)
|
||||
return connect(username=ROUTER_USER, password=ROUTER_PASSWORD, host=ROUTER_HOST)
|
||||
|
||||
@dp.message(Command("start"))
|
||||
async def start_cmd(msg: types.Message):
|
||||
@ -111,8 +127,10 @@ async def event(request: Request):
|
||||
# Сохраняем клиента и сессию (fetch)
|
||||
await upsert_client(mac, name=None)
|
||||
await start_session(mac, ip, source='fetch')
|
||||
for uid in ALLOWED_USER_IDS:
|
||||
await bot.send_message(uid, text)
|
||||
# Отправляем уведомления только если есть разрешённые пользователи
|
||||
if ALLOWED_USER_IDS:
|
||||
for uid in ALLOWED_USER_IDS:
|
||||
await bot.send_message(uid, text)
|
||||
return {"status": "ok"}
|
||||
|
||||
# --- Периодический polling MikroTik API для сбора статистики ---
|
||||
@ -167,8 +185,7 @@ async def history_cmd(msg: types.Message):
|
||||
|
||||
@dp.callback_query(lambda c: c.data.startswith("history_"))
|
||||
async def history_period(callback: types.CallbackQuery):
|
||||
if callback.from_user.id not in ALLOWED_USER_IDS:
|
||||
await callback.answer("⛔️ Нет доступа", show_alert=True)
|
||||
if not await check_user_callback(callback):
|
||||
return
|
||||
parts = callback.data.split("_")
|
||||
days = int(parts[1])
|
||||
@ -219,8 +236,7 @@ async def clients_cmd(msg: types.Message):
|
||||
|
||||
@dp.callback_query(lambda c: c.data.startswith("clients_"))
|
||||
async def clients_page_cb(callback: types.CallbackQuery):
|
||||
if callback.from_user.id not in ALLOWED_USER_IDS:
|
||||
await callback.answer("⛔️ Нет доступа", show_alert=True)
|
||||
if not await check_user_callback(callback):
|
||||
return
|
||||
parts = callback.data.split("_")
|
||||
page = int(parts[1]) if len(parts) > 1 else 1
|
||||
@ -272,8 +288,7 @@ async def stats_cmd(msg: types.Message):
|
||||
|
||||
@dp.callback_query(lambda c: c.data.startswith("stats_"))
|
||||
async def stats_period(callback: types.CallbackQuery):
|
||||
if callback.from_user.id not in ALLOWED_USER_IDS:
|
||||
await callback.answer("⛔️ Нет доступа", show_alert=True)
|
||||
if not await check_user_callback(callback):
|
||||
return
|
||||
parts = callback.data.split("_")
|
||||
days = int(parts[1])
|
||||
@ -313,5 +328,15 @@ async def send_stats_page(message, period_days, page, edit=False, callback=None)
|
||||
else:
|
||||
await message.answer(text, parse_mode="HTML", reply_markup=kb)
|
||||
|
||||
# Добавляем вспомогательную функцию для проверки callback'ов
|
||||
async def check_user_callback(callback: types.CallbackQuery) -> bool:
|
||||
# Если список пуст, разрешаем всем
|
||||
if not ALLOWED_USER_IDS:
|
||||
return True
|
||||
if callback.from_user.id not in ALLOWED_USER_IDS:
|
||||
await callback.answer("⛔️ Нет доступа", show_alert=True)
|
||||
return False
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@ -1,44 +1,44 @@
|
||||
version: '3.8'
|
||||
services:
|
||||
telegram-bot:
|
||||
build: .
|
||||
container_name: mikrotik-telegram-bot
|
||||
env_file:
|
||||
- .env
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro # для watchtower
|
||||
labels:
|
||||
- "com.centurylinklabs.watchtower.enable=true"
|
||||
- "com.centurylinklabs.watchtower.scope=mikrotik-bot"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 15s
|
||||
networks:
|
||||
- mikrotik-net
|
||||
|
||||
# Автообновление контейнеров при изменении кода
|
||||
watchtower:
|
||||
image: containrrr/watchtower:latest
|
||||
container_name: watchtower-mikrotik
|
||||
services:
|
||||
mikrotik-bot:
|
||||
image: 10.10.30.121:5000/mikrotik-bot:latest
|
||||
container_name: mikrotik-bot-production
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- WATCHTOWER_CLEANUP=true
|
||||
- WATCHTOWER_POLL_INTERVAL=300 # проверка каждые 5 мин
|
||||
- WATCHTOWER_SCOPE=mikrotik-bot
|
||||
- WATCHTOWER_INCLUDE_STOPPED=true
|
||||
- WATCHTOWER_REVIVE_STOPPED=true
|
||||
# Vault AppRole credentials (безопасно)
|
||||
- VAULT_ADDR=http://10.10.30.121:8200
|
||||
- VAULT_ROLE_ID=ba8d3d21-263e-4d92-8ffe-ef803017cef5
|
||||
- VAULT_SECRET_ID=6b3ecc3c-9436-7f04-022f-8b1ce0ac09ee
|
||||
- VAULT_SECRET_PATH=secret/data/mikrotik-bot
|
||||
- DATABASE_PATH=/app/data/bot.db
|
||||
volumes:
|
||||
- mikrotik_bot_data:/app/data
|
||||
ports:
|
||||
- "8001:8000" # Health check endpoint
|
||||
networks:
|
||||
- bot-network
|
||||
labels:
|
||||
- "com.centurylinklabs.watchtower.enable=true"
|
||||
|
||||
# Автоматические обновления
|
||||
watchtower:
|
||||
image: containrrr/watchtower
|
||||
container_name: watchtower-mikrotik
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
labels:
|
||||
- "com.centurylinklabs.watchtower.scope=mikrotik-bot"
|
||||
environment:
|
||||
- WATCHTOWER_POLL_INTERVAL=60 # Проверять каждые 60 секунд
|
||||
- WATCHTOWER_LABEL_ENABLE=true
|
||||
- WATCHTOWER_CLEANUP=true
|
||||
command: --interval 60 --label-enable --cleanup
|
||||
networks:
|
||||
- mikrotik-net
|
||||
- bot-network
|
||||
|
||||
networks:
|
||||
mikrotik-net:
|
||||
bot-network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
mikrotik_bot_data:
|
||||
46
docker-compose.production.yml
Normal file
46
docker-compose.production.yml
Normal file
@ -0,0 +1,46 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
mikrotik-bot:
|
||||
image: 10.10.30.121:5000/mikrotik-bot:latest
|
||||
container_name: mikrotik-bot-production
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
# Vault AppRole credentials (безопасно)
|
||||
- VAULT_ADDR=http://10.10.30.121:8200
|
||||
- VAULT_ROLE_ID=ba8d3d21-263e-4d92-8ffe-ef803017cef5
|
||||
- VAULT_SECRET_ID=6b3ecc3c-9436-7f04-022f-8b1ce0ac09ee
|
||||
- VAULT_SECRET_PATH=secret/data/mikrotik-bot
|
||||
- DATABASE_PATH=/app/data/bot.db
|
||||
volumes:
|
||||
- mikrotik_bot_data:/app/data
|
||||
ports:
|
||||
- "8000:8000" # Health check endpoint
|
||||
networks:
|
||||
- bot-network
|
||||
depends_on:
|
||||
- watchtower
|
||||
labels:
|
||||
- "com.centurylinklabs.watchtower.enable=true"
|
||||
|
||||
# Автоматические обновления
|
||||
watchtower:
|
||||
image: containrrr/watchtower
|
||||
container_name: watchtower-mikrotik
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- WATCHTOWER_POLL_INTERVAL=60 # Проверять каждые 60 секунд
|
||||
- WATCHTOWER_LABEL_ENABLE=true
|
||||
- WATCHTOWER_CLEANUP=true
|
||||
command: --interval 60 --label-enable --cleanup
|
||||
networks:
|
||||
- bot-network
|
||||
|
||||
networks:
|
||||
bot-network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
mikrotik_bot_data:
|
||||
40
test_vault.py
Normal file
40
test_vault.py
Normal file
@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from vault_client import VaultClient
|
||||
import os
|
||||
|
||||
# Устанавливаем переменные окружения для тестирования
|
||||
os.environ['VAULT_ADDR'] = 'http://10.10.30.121:8200'
|
||||
os.environ['VAULT_ROLE_ID'] = 'ba8d3d21-263e-4d92-8ffe-ef803017cef5'
|
||||
os.environ['VAULT_SECRET_ID'] = '6b3ecc3c-9436-7f04-022f-8b1ce0ac09ee'
|
||||
os.environ['VAULT_SECRET_PATH'] = 'secret/data/mikrotik-bot'
|
||||
|
||||
def test_vault_secrets():
|
||||
"""Тестирование получения секретов из Vault"""
|
||||
print('🔍 Проверка получения секретов из Vault:')
|
||||
print('=' * 50)
|
||||
|
||||
vault = VaultClient()
|
||||
config = vault.get_config()
|
||||
|
||||
if not config:
|
||||
print('❌ Не удалось получить конфигурацию')
|
||||
return False
|
||||
|
||||
success = True
|
||||
for key, value in config.items():
|
||||
if value:
|
||||
masked_value = value[:10] + '...' if len(value) > 10 else value
|
||||
print(f'✅ {key}: {masked_value}')
|
||||
else:
|
||||
print(f'❌ {key}: не найден')
|
||||
success = False
|
||||
|
||||
return success
|
||||
|
||||
if __name__ == '__main__':
|
||||
if test_vault_secrets():
|
||||
print('\n🎉 Все секреты успешно получены!')
|
||||
print('✅ Готов к production deployment')
|
||||
else:
|
||||
print('\n❌ Есть проблемы с получением секретов')
|
||||
84
vault_client.py
Normal file
84
vault_client.py
Normal file
@ -0,0 +1,84 @@
|
||||
import os
|
||||
import requests
|
||||
import json
|
||||
from typing import Dict, Optional
|
||||
|
||||
|
||||
class VaultClient:
|
||||
"""Клиент для работы с HashiCorp Vault через AppRole аутентификацию"""
|
||||
|
||||
def __init__(self):
|
||||
self.vault_addr = os.environ.get('VAULT_ADDR', 'http://localhost:8200')
|
||||
self.role_id = os.environ.get('VAULT_ROLE_ID')
|
||||
self.secret_id = os.environ.get('VAULT_SECRET_ID')
|
||||
self.secret_path = os.environ.get('VAULT_SECRET_PATH', 'secret/data/mikrotik-bot')
|
||||
self.token = None
|
||||
|
||||
def authenticate(self) -> bool:
|
||||
"""Аутентификация через AppRole"""
|
||||
if not self.role_id or not self.secret_id:
|
||||
print("❌ VAULT_ROLE_ID или VAULT_SECRET_ID не установлены")
|
||||
return False
|
||||
|
||||
try:
|
||||
auth_url = f"{self.vault_addr}/v1/auth/approle/login"
|
||||
auth_data = {
|
||||
"role_id": self.role_id,
|
||||
"secret_id": self.secret_id
|
||||
}
|
||||
|
||||
response = requests.post(auth_url, json=auth_data)
|
||||
response.raise_for_status()
|
||||
|
||||
auth_result = response.json()
|
||||
self.token = auth_result['auth']['client_token']
|
||||
print("✅ Vault аутентификация успешна")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка аутентификации Vault: {e}")
|
||||
return False
|
||||
|
||||
def get_secrets(self) -> Optional[Dict[str, str]]:
|
||||
"""Получение секретов из Vault"""
|
||||
if not self.token and not self.authenticate():
|
||||
return None
|
||||
|
||||
try:
|
||||
headers = {"X-Vault-Token": self.token}
|
||||
secret_url = f"{self.vault_addr}/v1/{self.secret_path}"
|
||||
|
||||
response = requests.get(secret_url, headers=headers)
|
||||
response.raise_for_status()
|
||||
|
||||
secret_data = response.json()
|
||||
secrets = secret_data['data']['data']
|
||||
|
||||
print("✅ Секреты успешно получены из Vault")
|
||||
return secrets
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Ошибка получения секретов: {e}")
|
||||
return None
|
||||
|
||||
def get_config(self) -> Dict[str, str]:
|
||||
"""Получение конфигурации с fallback на environment variables"""
|
||||
# Сначала пытаемся получить из Vault
|
||||
secrets = self.get_secrets()
|
||||
|
||||
if secrets:
|
||||
return {
|
||||
'BOT_TOKEN': secrets.get('bot_token'),
|
||||
'ROUTER_HOST': secrets.get('router_host'),
|
||||
'ROUTER_USER': secrets.get('router_user'),
|
||||
'ROUTER_PASSWORD': secrets.get('router_password'),
|
||||
}
|
||||
|
||||
# Fallback на environment variables
|
||||
print("⚠️ Используются environment variables вместо Vault")
|
||||
return {
|
||||
'BOT_TOKEN': os.environ.get('BOT_TOKEN'),
|
||||
'ROUTER_HOST': os.environ.get('ROUTER_HOST'),
|
||||
'ROUTER_USER': os.environ.get('ROUTER_USER'),
|
||||
'ROUTER_PASSWORD': os.environ.get('ROUTER_PASSWORD'),
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user