mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-08-17 21:25:47 +02:00
165 lines
6.1 KiB
Python
165 lines
6.1 KiB
Python
import os
|
|
import shlex
|
|
import subprocess
|
|
import sys
|
|
from datetime import datetime
|
|
if "/www/server/panel/class" not in sys.path:
|
|
sys.path.insert(0, "/www/server/panel/class")
|
|
|
|
os.chdir("/www/server/panel")
|
|
import public
|
|
|
|
from mod.project.ssh.base import SSHbase
|
|
|
|
|
|
class JournalctlManage(SSHbase):
|
|
def __init__(self):
|
|
super(JournalctlManage, self).__init__()
|
|
|
|
def use_journalctl_logs(self):
|
|
try:
|
|
if not self.journalctl_system():
|
|
return False
|
|
log_path = getattr(self, 'ssh_log_path', '')
|
|
return log_path.startswith('/var/log/message') or not self.has_secure_log_files()
|
|
except Exception:
|
|
return False
|
|
|
|
def get_secure_logs(self, login_type, pagesize=10, page=1, query=''):
|
|
if self.use_journalctl_logs():
|
|
return self.get_journalctl_login_logs(login_type, pagesize, page, query)
|
|
return super(JournalctlManage, self).get_secure_logs(login_type, pagesize, page, query)
|
|
|
|
def get_secure_log_count(self, login_type, query=''):
|
|
if self.use_journalctl_logs():
|
|
return self.get_journalctl_login_count(login_type, query)
|
|
return super(JournalctlManage, self).get_secure_log_count(login_type, query)
|
|
|
|
def get_journalctl_since_option(self):
|
|
try:
|
|
res, err = public.ExecShell("journalctl --disk-usage")
|
|
total_bytes = public.parse_journal_disk_usage(res)
|
|
if total_bytes > 5 * 1024 * 1024 * 1024:
|
|
return " --since '30 days ago'"
|
|
except Exception:
|
|
pass
|
|
return ""
|
|
|
|
def get_journalctl_login_command(self, login_type):
|
|
grep_pattern = shlex.quote("({})".format(login_type))
|
|
return "LC_ALL=C journalctl -q -u ssh -u sshd{} --no-pager --output=short-iso --grep={}".format(
|
|
self.get_journalctl_since_option(),
|
|
grep_pattern
|
|
)
|
|
|
|
def get_journalctl_query_pipe(self, query):
|
|
query = (query or '').strip()
|
|
if not query:
|
|
return ""
|
|
return " | grep -aiF -- {}".format(shlex.quote(query))
|
|
|
|
def exec_journalctl_count_page(self, command):
|
|
try:
|
|
result = subprocess.run(
|
|
command,
|
|
shell=True,
|
|
text=True,
|
|
capture_output=True,
|
|
executable="/bin/bash",
|
|
timeout=30
|
|
)
|
|
count = int((result.stdout or "0").strip() or 0)
|
|
datas = result.stderr.strip().split("\n") if result.stderr.strip() else []
|
|
except subprocess.TimeoutExpired:
|
|
public.print_log("SSH journalctl query timed out")
|
|
count = 0
|
|
datas = []
|
|
except Exception:
|
|
count = 0
|
|
datas = []
|
|
return count, datas
|
|
|
|
def exec_journalctl_count(self, command):
|
|
try:
|
|
result = subprocess.run(
|
|
command,
|
|
shell=True,
|
|
text=True,
|
|
capture_output=True,
|
|
executable="/bin/bash",
|
|
timeout=30
|
|
)
|
|
return int((result.stdout or "0").strip() or 0)
|
|
except subprocess.TimeoutExpired:
|
|
public.print_log("SSH journalctl count query timed out")
|
|
return 0
|
|
except Exception:
|
|
return 0
|
|
|
|
def get_journalctl_login_logs(self, login_type, pagesize=10, page=1, query=''):
|
|
new_logins = []
|
|
end = pagesize * page
|
|
command = "{}{} | tee >(tail -n {} | head -n {} | tac >&2) | wc -l".format(
|
|
self.get_journalctl_login_command(login_type),
|
|
self.get_journalctl_query_pipe(query),
|
|
end,
|
|
pagesize
|
|
)
|
|
total, lines = self.exec_journalctl_count_page(command)
|
|
year = datetime.now().year
|
|
for line in lines:
|
|
entry = self.parse_login_entry(line.split(), year)
|
|
if entry:
|
|
entry["log_file"] = "journalctl"
|
|
new_logins.append(entry)
|
|
return total, new_logins
|
|
|
|
def get_journalctl_login_count(self, login_type, query=''):
|
|
command = "{}{} | wc -l".format(
|
|
self.get_journalctl_login_command(login_type),
|
|
self.get_journalctl_query_pipe(query)
|
|
)
|
|
return self.exec_journalctl_count(command)
|
|
|
|
def get_journalctl_logs(self, file_positions):
|
|
'''
|
|
获取 systemd journalctl 的 SSH 登录日志
|
|
return 日志,游标位置
|
|
'''
|
|
new_logins = []
|
|
current_positions = ""
|
|
|
|
command_list = [
|
|
"journalctl -u ssh --no-pager --show-cursor --grep='Accepted|Failed password for|Accepted publickey'", # 全量获取
|
|
"journalctl -u ssh --since '30 days ago' --no-pager --show-cursor --grep='Accepted|Failed password for|Accepted publickey'", # 30天
|
|
"journalctl -u ssh --no-pager --show-cursor --grep='Accepted|Failed password for|Accepted publickey' --cursor='{}'".format(file_positions) # 从记录的游标开始读取
|
|
]
|
|
|
|
if not file_positions:
|
|
# 获取systemd日志所占用的空间
|
|
res, err = public.ExecShell("journalctl --disk-usage")
|
|
total_bytes = public.parse_journal_disk_usage(res)
|
|
limit_bytes = 5 * 1024 * 1024 * 1024
|
|
# 大于5G 取30天的数据量
|
|
command = command_list[1] if total_bytes > limit_bytes else command_list[0]
|
|
content = public.ExecShell(command)[0].strip()
|
|
else:
|
|
content = public.ExecShell(command_list[2])[0].strip()
|
|
|
|
lines = content.split('\n')
|
|
if lines:
|
|
# 处理去除多余游标字符
|
|
current_positions = lines[-1].replace("-- cursor: ", "")
|
|
|
|
for line in lines[:-1]:
|
|
if "No entries" in line:break
|
|
|
|
if any(keyword in line for keyword in ["Accepted password", "Failed password", "Accepted publickey"]):
|
|
parts = line.split()
|
|
year = datetime.now().year
|
|
entry = self.parse_login_entry(parts, year)
|
|
if entry:
|
|
entry["log_file"] = "journalctl"
|
|
new_logins.append(entry)
|
|
return new_logins, current_positions
|