Files
default/telegram-agy-bot/remote_audit.py
T

211 lines
8.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import sys
import time
import sqlite3
import logging
from pathlib import Path
from typing import Optional, Dict, Any, List, Tuple
from datetime import datetime
logger = logging.getLogger("AGYRemoteAudit")
DATA_DIR = Path("/root/telegram-agy-bot/data")
DB_PATH = DATA_DIR / "memory.db"
class RemoteAuditLogger:
"""Manages audit logging for SSH, FTP, and remote operations into SQLite."""
def __init__(self, db_path: Path = DB_PATH):
self.db_path = db_path
self._init_db()
def _get_connection(self) -> sqlite3.Connection:
self.db_path.parent.mkdir(parents=True, exist_ok=True)
conn = sqlite3.connect(str(self.db_path), timeout=15.0)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA journal_mode=WAL;")
conn.execute("PRAGMA synchronous=NORMAL;")
return conn
def _init_db(self):
"""Initializes remote_audit_logs table and indexes."""
try:
with self._get_connection() as conn:
conn.execute("""
CREATE TABLE IF NOT EXISTS remote_audit_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp REAL NOT NULL,
created_at TEXT NOT NULL,
user_id INTEGER NOT NULL,
project_name TEXT NOT NULL,
service_type TEXT NOT NULL, -- 'ssh', 'ftp'
action_type TEXT NOT NULL, -- 'push', 'pull', 'exec', 'test_connection', 'update_config'
requester TEXT NOT NULL, -- 'ai_agent', 'user_button', 'user_command'
details TEXT, -- command or parameters (never contains passwords)
status TEXT NOT NULL, -- 'success', 'failed', 'running'
result_summary TEXT, -- short summary or error message
duration_ms REAL DEFAULT 0.0
);
""")
conn.execute("""
CREATE INDEX IF NOT EXISTS idx_remote_audit_lookup
ON remote_audit_logs(user_id, project_name, service_type, timestamp DESC);
""")
conn.commit()
except Exception as e:
logger.error(f"Error initializing remote_audit_logs table: {e}")
def log_operation(
self,
user_id: int,
project_name: str,
service_type: str,
action_type: str,
requester: str,
status: str,
details: Optional[str] = None,
result_summary: Optional[str] = None,
duration_ms: float = 0.0,
timestamp: Optional[float] = None,
) -> int:
"""Logs a remote operation into the audit database."""
now_ts = timestamp or time.time()
dt_str = datetime.fromtimestamp(now_ts).strftime("%Y-%m-%d %H:%M:%S")
# Sanitize details (ensure no raw passwords accidentally entered)
safe_details = details or ""
if "password" in safe_details.lower():
safe_details = "[FILTERED DETAILS]"
try:
with self._get_connection() as conn:
cur = conn.execute(
"""
INSERT INTO remote_audit_logs (
timestamp, created_at, user_id, project_name,
service_type, action_type, requester, details,
status, result_summary, duration_ms
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
now_ts,
dt_str,
int(user_id),
str(project_name),
str(service_type).lower(),
str(action_type).lower(),
str(requester).lower(),
safe_details[:1000] if safe_details else None,
str(status).lower(),
str(result_summary)[:1000] if result_summary else None,
float(duration_ms),
),
)
conn.commit()
return cur.lastrowid
except Exception as e:
logger.error(f"Error writing to remote_audit_logs: {e}")
return 0
def get_recent_logs(
self,
user_id: Optional[int] = None,
project_name: Optional[str] = None,
service_type: Optional[str] = None,
limit: int = 15,
) -> List[Dict[str, Any]]:
"""Retrieves recent audit log records."""
try:
with self._get_connection() as conn:
query = "SELECT * FROM remote_audit_logs WHERE 1=1"
params: List[Any] = []
if user_id is not None:
query += " AND user_id = ?"
params.append(user_id)
if project_name:
query += " AND project_name = ?"
params.append(project_name)
if service_type:
query += " AND service_type = ?"
params.append(service_type)
query += " ORDER BY timestamp DESC LIMIT ?"
params.append(int(limit))
cur = conn.execute(query, params)
rows = cur.fetchall()
return [dict(r) for r in rows]
except Exception as e:
logger.error(f"Error fetching remote_audit_logs: {e}")
return []
def format_logs_for_tg(
self,
logs: List[Dict[str, Any]],
project_name: str,
is_fa: bool = True,
) -> str:
"""Formats audit logs into a clean Telegram HTML message."""
if not logs:
if is_fa:
return (
f"📜 <b>تاریخچه و لاگ عملیات ریموت</b>\n\n"
f"• 📁 <b>پروژه:</b> <code>{project_name}</code>\n\n"
f"️ <i>هنوز هیچ عملیات SSH یا FTP برای این پروژه ثبت نشده است.</i>"
)
else:
return (
f"📜 <b>Remote Operations Audit Log</b>\n\n"
f"• 📁 <b>Project:</b> <code>{project_name}</code>\n\n"
f"️ <i>No SSH or FTP operations logged for this project yet.</i>"
)
lines = []
if is_fa:
lines.append(f"📜 <b>تاریخچه عملیات ریموت و SSH/FTP</b>")
lines.append(f"• 📁 <b>پروژه:</b> <code>{project_name}</code>")
lines.append(f"• 📊 <b>تعداد لاگ‌های اخیر:</b> <code>{len(logs)}</code>\n")
else:
lines.append(f"📜 <b>Remote Operations & SSH/FTP Audit Log</b>")
lines.append(f"• 📁 <b>Project:</b> <code>{project_name}</code>")
lines.append(f"• 📊 <b>Recent records:</b> <code>{len(logs)}</code>\n")
for idx, log in enumerate(logs, 1):
st = log.get("status", "unknown")
st_icon = "✅" if st == "success" else ("❌" if st == "failed" else "⏳")
srv = log.get("service_type", "").upper()
act = log.get("action_type", "")
req = log.get("requester", "")
req_label = "🤖 AI" if "ai" in req else ("👤 کاربر" if is_fa else "👤 User")
dt = log.get("created_at", "")
dur = log.get("duration_ms", 0.0)
dur_str = f" ({dur:.1f}ms)" if dur > 0 else ""
res = log.get("result_summary") or ""
det = log.get("details") or ""
act_fa = {
"push": "پوش به پروداکشن 🚀",
"pull": "دریافت از سرور 📥",
"exec": "اجرای فرمان 💻",
"test_connection": "تست اتصال 🔍",
"update_config": "ویرایش تنظیمات ⚙️",
}.get(act, act)
act_display = act_fa if is_fa else act.capitalize()
item_text = f"<b>{idx}. {st_icon} [{srv}] {act_display}</b> | {req_label}\n"
item_text += f" 🕒 <code>{dt}</code>{dur_str}\n"
if det:
item_text += f" 📝 <code>{det[:60]}</code>\n"
if res:
item_text += f" 💬 <i>{res[:100]}</i>\n"
lines.append(item_text)
return "\n".join(lines)
# Global singleton instance
remote_audit = RemoteAuditLogger()