mirror of
https://github.com/anten-ka/gotelegram_pro.git
synced 2026-06-10 12:23:00 +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"}
|
SENSITIVE_CONFIG_KEYS = {"secret"}
|
||||||
BACKUP_NAME_RE = re.compile(r"^[A-Za-z0-9_.-]+\.tar\.gz(\.enc)?$")
|
BACKUP_NAME_RE = re.compile(r"^[A-Za-z0-9_.-]+\.tar\.gz(\.enc)?$")
|
||||||
MAX_UNIQUE_IP_LIMIT = 1000000
|
MAX_UNIQUE_IP_LIMIT = 1000000
|
||||||
|
TELEMT_RESTART_DEBOUNCE_SECONDS = float(os.getenv("GOTELEGRAM_TELEMT_RESTART_DEBOUNCE", "8"))
|
||||||
|
_LAST_TELEMT_RESTART = 0.0
|
||||||
TRAFFIC_WINDOWS = {
|
TRAFFIC_WINDOWS = {
|
||||||
"15m": 15 * 60,
|
"15m": 15 * 60,
|
||||||
"1h": 60 * 60,
|
"1h": 60 * 60,
|
||||||
@@ -405,6 +407,15 @@ def restart_service(name: str) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def request_service_restart(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)
|
code, _, _ = run(["systemctl", "--no-block", "restart", name], timeout=5)
|
||||||
return code == 0
|
return code == 0
|
||||||
|
|
||||||
|
|||||||
@@ -279,6 +279,8 @@ _DOMAIN_RE = re.compile(
|
|||||||
)
|
)
|
||||||
_USER_NAME_RE = re.compile(r"^[A-Za-z0-9_.-]{1,48}$")
|
_USER_NAME_RE = re.compile(r"^[A-Za-z0-9_.-]{1,48}$")
|
||||||
MAX_UNIQUE_IP_LIMIT = 1000000
|
MAX_UNIQUE_IP_LIMIT = 1000000
|
||||||
|
TELEMT_RESTART_DEBOUNCE_SECONDS = float(os.getenv("GOTELEGRAM_TELEMT_RESTART_DEBOUNCE", "8"))
|
||||||
|
_LAST_TELEMT_RESTART = 0.0
|
||||||
|
|
||||||
|
|
||||||
class FileLock:
|
class FileLock:
|
||||||
@@ -1638,7 +1640,15 @@ def save_telemt_users(users: Dict[str, str]) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
async def refresh_telemt_after_user_change() -> 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)
|
code, _, _ = await sh("systemctl", "--no-block", "restart", TELEMT_SERVICE, timeout=5)
|
||||||
return code == 0
|
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=$(first_telemt_user_secret "$TELEMT_CONFIG" 2>/dev/null || echo "")
|
||||||
[ -z "$secret" ] && secret=$(generate_hex 32)
|
[ -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=$(printf 'main = "%s"\n%s\n' "$secret" "$users_block")
|
||||||
users_block_needs_write=1
|
users_block_needs_write=1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -210,6 +210,25 @@ first_telemt_user_secret() {
|
|||||||
get_telemt_users_block "$config" | head -1 | sed 's/^[^=]*=[[:space:]]*//; s/^"//; s/".*$//' | tr -d ' '
|
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() {
|
replace_telemt_users_block() {
|
||||||
local users_block="$1"
|
local users_block="$1"
|
||||||
local config="${2:-$TELEMT_CONFIG}"
|
local config="${2:-$TELEMT_CONFIG}"
|
||||||
|
|||||||
@@ -243,6 +243,49 @@ class AdminFeatureTests(unittest.TestCase):
|
|||||||
self.assertEqual(result.returncode, 0, result.stderr)
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
self.assertEqual(result.stdout.strip(), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user