mirror of
https://github.com/anten-ka/gotelegram_pro.git
synced 2026-05-19 09:26:02 +00:00
v2.5.0: harden telemt user reloads
This commit is contained in:
@@ -54,6 +54,8 @@ LANG_RE = re.compile(r"^(en|ru)$")
|
||||
SENSITIVE_CONFIG_KEYS = {"secret"}
|
||||
BACKUP_NAME_RE = re.compile(r"^[A-Za-z0-9_.-]+\.tar\.gz(\.enc)?$")
|
||||
MAX_UNIQUE_IP_LIMIT = 1000000
|
||||
TELEMT_RESTART_DEBOUNCE_SECONDS = float(os.getenv("GOTELEGRAM_TELEMT_RESTART_DEBOUNCE", "8"))
|
||||
_LAST_TELEMT_RESTART = 0.0
|
||||
TRAFFIC_WINDOWS = {
|
||||
"15m": 15 * 60,
|
||||
"1h": 60 * 60,
|
||||
@@ -405,6 +407,15 @@ def restart_service(name: str) -> bool:
|
||||
|
||||
|
||||
def request_service_restart(name: str) -> bool:
|
||||
global _LAST_TELEMT_RESTART
|
||||
if name == "telemt":
|
||||
now = time.monotonic()
|
||||
if _LAST_TELEMT_RESTART > 0 and now - _LAST_TELEMT_RESTART < TELEMT_RESTART_DEBOUNCE_SECONDS:
|
||||
status = service_status(name)
|
||||
if status in {"running", "activating"}:
|
||||
return True
|
||||
run(["systemctl", "reset-failed", name], timeout=5)
|
||||
_LAST_TELEMT_RESTART = now
|
||||
code, _, _ = run(["systemctl", "--no-block", "restart", name], timeout=5)
|
||||
return code == 0
|
||||
|
||||
|
||||
@@ -279,6 +279,8 @@ _DOMAIN_RE = re.compile(
|
||||
)
|
||||
_USER_NAME_RE = re.compile(r"^[A-Za-z0-9_.-]{1,48}$")
|
||||
MAX_UNIQUE_IP_LIMIT = 1000000
|
||||
TELEMT_RESTART_DEBOUNCE_SECONDS = float(os.getenv("GOTELEGRAM_TELEMT_RESTART_DEBOUNCE", "8"))
|
||||
_LAST_TELEMT_RESTART = 0.0
|
||||
|
||||
|
||||
class FileLock:
|
||||
@@ -1638,7 +1640,15 @@ def save_telemt_users(users: Dict[str, str]) -> bool:
|
||||
|
||||
|
||||
async def refresh_telemt_after_user_change() -> bool:
|
||||
"""Restart telemt after config user changes."""
|
||||
"""Restart telemt after config user changes, coalescing rapid UI clicks."""
|
||||
global _LAST_TELEMT_RESTART
|
||||
now = time.monotonic()
|
||||
if _LAST_TELEMT_RESTART > 0 and now - _LAST_TELEMT_RESTART < TELEMT_RESTART_DEBOUNCE_SECONDS:
|
||||
code, stdout, _ = await sh("systemctl", "is-active", TELEMT_SERVICE, timeout=5)
|
||||
if code == 0 and stdout.strip() == "active":
|
||||
return True
|
||||
await sh("systemctl", "reset-failed", TELEMT_SERVICE, timeout=5)
|
||||
_LAST_TELEMT_RESTART = now
|
||||
code, _, _ = await sh("systemctl", "--no-block", "restart", TELEMT_SERVICE, timeout=5)
|
||||
return code == 0
|
||||
|
||||
|
||||
@@ -365,7 +365,7 @@ auto_migrate_legacy_state() {
|
||||
[ -z "$secret" ] && secret=$(first_telemt_user_secret "$TELEMT_CONFIG" 2>/dev/null || echo "")
|
||||
[ -z "$secret" ] && secret=$(generate_hex 32)
|
||||
|
||||
if [ -n "$users_block" ] && ! printf '%s\n' "$users_block" | grep -qE '^[[:space:]]*main[[:space:]]*='; then
|
||||
if [ -n "$users_block" ] && ! telemt_users_block_has_main "$users_block"; then
|
||||
users_block=$(printf 'main = "%s"\n%s\n' "$secret" "$users_block")
|
||||
users_block_needs_write=1
|
||||
fi
|
||||
|
||||
@@ -210,6 +210,25 @@ first_telemt_user_secret() {
|
||||
get_telemt_users_block "$config" | head -1 | sed 's/^[^=]*=[[:space:]]*//; s/^"//; s/".*$//' | tr -d ' '
|
||||
}
|
||||
|
||||
telemt_users_block_has_main() {
|
||||
local users_block="$1"
|
||||
printf '%s\n' "$users_block" | awk -F= '
|
||||
/^[[:space:]]*#/ || ! /=/ { next }
|
||||
{
|
||||
key=$1
|
||||
gsub(/^[[:space:]]+|[[:space:]]+$/, "", key)
|
||||
if (key ~ /^".*"$/ || key ~ /^\047.*\047$/) {
|
||||
key=substr(key, 2, length(key) - 2)
|
||||
}
|
||||
if (key == "main") {
|
||||
found=1
|
||||
exit
|
||||
}
|
||||
}
|
||||
END { exit found ? 0 : 1 }
|
||||
'
|
||||
}
|
||||
|
||||
replace_telemt_users_block() {
|
||||
local users_block="$1"
|
||||
local config="${2:-$TELEMT_CONFIG}"
|
||||
|
||||
@@ -243,6 +243,49 @@ class AdminFeatureTests(unittest.TestCase):
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertEqual(result.stdout.strip(), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
||||
|
||||
def test_telemt_users_block_detects_quoted_main_user(self):
|
||||
script = "\n".join([
|
||||
"set -e",
|
||||
f"source {shlex.quote(str(ROOT / 'lib' / 'telemt_config.sh'))}",
|
||||
"block=$'\"main\" = \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"\\n\"client\" = \"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\"'",
|
||||
"telemt_users_block_has_main \"$block\"",
|
||||
])
|
||||
|
||||
result = subprocess.run(
|
||||
["bash", "-lc", script],
|
||||
cwd=ROOT,
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
|
||||
def test_telemt_restart_requests_are_debounced(self):
|
||||
with tempfile.TemporaryDirectory() as raw:
|
||||
server = load_server(Path(raw))
|
||||
calls = []
|
||||
original_run = server.run
|
||||
original_status = server.service_status
|
||||
try:
|
||||
server._LAST_TELEMT_RESTART = 0.0
|
||||
server.TELEMT_RESTART_DEBOUNCE_SECONDS = 30.0
|
||||
server.service_status = lambda name: "running"
|
||||
|
||||
def fake_run(cmd, timeout=8):
|
||||
calls.append(cmd)
|
||||
return 0, "", ""
|
||||
|
||||
server.run = fake_run
|
||||
self.assertTrue(server.request_service_restart("telemt"))
|
||||
self.assertTrue(server.request_service_restart("telemt"))
|
||||
finally:
|
||||
server.run = original_run
|
||||
server.service_status = original_status
|
||||
|
||||
restart_calls = [cmd for cmd in calls if cmd[:3] == ["systemctl", "--no-block", "restart"]]
|
||||
self.assertEqual(len(restart_calls), 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user