mirror of
https://github.com/aaPanel/aaPanel.git
synced 2026-08-17 21:25:47 +02:00
615 lines
23 KiB
Python
615 lines
23 KiB
Python
import json
|
|
from typing import Tuple, Union, Optional, Dict, List
|
|
|
|
from . import WxAccountMsg
|
|
from .mods import TaskConfig
|
|
from .base_task import BaseTask, BaseTaskViewMsg
|
|
from .site_push import web_info
|
|
from .util import get_db_by_file, DB, GET_CLASS, read_file, write_file
|
|
from .manager import PushManager
|
|
from .system import push_by_task_keyword
|
|
|
|
|
|
|
|
class SiteMonitorViolationWordTask(BaseTask): # 网站违规词告警检查
|
|
DB_FILE = "/www/server/panel/class/projectModel/content/content.db"
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.source_name: str = 'site_monitor_violation_word'
|
|
self.title: str = 'Website violation keyword alert'
|
|
self.template_name: str = 'Website violation keyword alert'
|
|
|
|
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
|
"""
|
|
检查设置的告警参数(是否合理)
|
|
@param task_data: 传入的告警参数,提前会经过默认值处理(即没有的字段添加默认值)
|
|
@return: 当检查无误时,返回一个 dict 当做后续的添加和修改的数据,
|
|
当检查有误时, 直接返回错误信息的字符串
|
|
"""
|
|
if "interval" in task_data:
|
|
task_data.pop("interval")
|
|
if "mvw_id" in task_data and not task_data["mvw_id"]:
|
|
task_data.pop("mvw_id")
|
|
if "site_name" in task_data and not task_data["site_name"]:
|
|
task_data.pop("site_name")
|
|
return task_data
|
|
|
|
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
|
return None
|
|
|
|
def get_title(self, task_data: dict) -> str:
|
|
return "Violation keyword check on website {}".format(task_data["site_name"])
|
|
|
|
def get_keyword(self, task_data: dict) -> str:
|
|
return "site_mvw_{}".format(task_data.get("mvw_id", 0))
|
|
|
|
def filter_template(self, template: dict) -> Optional[dict]:
|
|
"""
|
|
过滤 和 更改模板中的信息, 返回空表是当前无法设置该任务
|
|
@param template: 任务的模板信息
|
|
@return:
|
|
"""
|
|
_, items_type = web_info(project_types=("PHP",))
|
|
template["field"][0]["items"].extend(items_type["PHP"])
|
|
return template
|
|
|
|
def task_config_create_hook(self, task: dict) -> Optional[str]:
|
|
task_data = task["task_data"]
|
|
if "mvw_id" not in task_data or not task_data["mvw_id"]:
|
|
return "No corresponding website violation word scanning task, unable to add alert"
|
|
if "site_name" not in task_data or not task_data["site_name"]:
|
|
return "No corresponding website violation word scanning task, unable to add alert"
|
|
|
|
def task_config_update_hook(self, task: dict) -> Optional[str]:
|
|
task_data = task["task_data"]
|
|
if "mvw_id" not in task_data or not task_data["mvw_id"]:
|
|
return "No corresponding website violation word scanning task, unable to add alert"
|
|
if "site_name" not in task_data or not task_data["site_name"]:
|
|
return "No corresponding website violation word scanning task, unable to add alert"
|
|
|
|
db_obj = get_db_by_file(self.DB_FILE)
|
|
if not db_obj:
|
|
return "Website violation word scanning task database file does not exist"
|
|
pdata = {
|
|
"send_msg": int(task["status"]),
|
|
"send_type": ",".join(task["sender"])
|
|
}
|
|
try:
|
|
db_obj.table("monitor_site").where("id=?", task_data["mvw_id"]).update(pdata)
|
|
db_obj.close()
|
|
except:
|
|
return "Website violation word scanning task update failed"
|
|
# 保障keyword的id正确
|
|
task["keyword"] = self.get_keyword(task_data)
|
|
|
|
def task_config_remove_hook(self, task: dict) -> None:
|
|
task_data = task["task_data"]
|
|
if "mvw_id" not in task_data or not task_data["mvw_id"]:
|
|
return
|
|
db_obj = get_db_by_file(self.DB_FILE)
|
|
if not db_obj:
|
|
return
|
|
try:
|
|
db_obj.table("monitor_site").where("id=?", task_data["mvw_id"]).update({"send_msg": 0})
|
|
db_obj.close()
|
|
except:
|
|
return
|
|
|
|
@classmethod
|
|
def set_push_task(cls, mvw_id: int, site_name: str, status: bool, sender: list):
|
|
task_conf = TaskConfig()
|
|
old_task = task_conf.get_by_keyword("site_monitor_violation_word", "site_mvw_{}".format(mvw_id))
|
|
if not old_task:
|
|
push_data = {
|
|
"template_id": "121",
|
|
"task_data": {
|
|
"status": True,
|
|
"sender": sender,
|
|
"task_data": {
|
|
"site_name": site_name,
|
|
"mvw_id": mvw_id,
|
|
}
|
|
}
|
|
}
|
|
from .manager import PushManager
|
|
PushManager().set_task_conf_data(push_data)
|
|
else:
|
|
old_task["sender"] = sender
|
|
old_task["status"] = status
|
|
task_conf.save_config()
|
|
|
|
@classmethod
|
|
def remove_push_task(cls, mvw_id: int):
|
|
task_conf = TaskConfig()
|
|
old_task = task_conf.get_by_keyword("site_monitor_violation_word", "site_mvw_{}".format(mvw_id))
|
|
if old_task:
|
|
task_conf.config.remove(old_task)
|
|
|
|
task_conf.save_config()
|
|
|
|
|
|
class VulnerabilityScanningTask(BaseTask):
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.source_name: str = 'vulnerability_scanning'
|
|
self.title: str = 'Website vulnerability alert'
|
|
self.template_name: str = 'Website vulnerability alert'
|
|
|
|
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
|
"""
|
|
检查设置的告警参数(是否合理)
|
|
@param task_data: 传入的告警参数,提前会经过默认值处理(即没有的字段添加默认值)
|
|
@return: 当检查无误时,返回一个 dict 当做后续的添加和修改的数据,
|
|
当检查有误时, 直接返回错误信息的字符串
|
|
"""
|
|
if "cycle" not in task_data or not task_data["cycle"] or not isinstance(task_data["cycle"], int):
|
|
return "Cycle cannot be empty"
|
|
|
|
return {"interval": 60*60*24 * (task_data["cycle"] + 1), "cycle": task_data["cycle"]}
|
|
|
|
def get_keyword(self, task_data: dict) -> str:
|
|
"""
|
|
返回一个关键字,用于后续查询或执行任务时使用, 例如:防篡改告警,可以根据其规则id生成一个关键字,
|
|
后续通过规则id和来源tamper 查询并使用
|
|
@param task_data: 通过check_args后生成的告警参数字典
|
|
@return: 返回一个关键词字符串
|
|
"""
|
|
return "vulnerability_scanning"
|
|
|
|
def get_title(self, task_data: dict) -> str:
|
|
"""
|
|
返回一个标题
|
|
@param task_data: 通过check_args后生成的告警参数字典
|
|
@return: 返回一个关键词字符串
|
|
"""
|
|
return 'Website vulnerability alert'
|
|
|
|
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
|
"""
|
|
判断这个任务是否需要返送
|
|
@param task_id: 任务id
|
|
@param task_data: 任务的告警参数
|
|
@return: 如果触发了告警,返回一个dict的原文,作为告警信息,否则应当返回None表示未触发
|
|
返回之中应当包含一个 msg_list 的键(值为List[str]类型),将主要的信息返回
|
|
用于以下信息的自动序列化包含[dingding, feishu, mail, weixin, web_hook]
|
|
短信和微信公众号由于长度问题,必须每个任务手动实现
|
|
"""
|
|
return None
|
|
|
|
def filter_template(self, template: dict) -> Optional[dict]:
|
|
"""
|
|
过滤 和 更改模板中的信息, 返回空表是当前无法设置该任务
|
|
@param template: 任务的模板信息
|
|
@return:
|
|
"""
|
|
return template
|
|
|
|
@classmethod
|
|
def set_push_task(cls, status: bool, day: int, sender: list):
|
|
push_data = {
|
|
"template_id": "122",
|
|
"task_data": {
|
|
"status": status,
|
|
"sender": sender,
|
|
"task_data": {
|
|
"cycle": day,
|
|
}
|
|
}
|
|
}
|
|
return PushManager().set_task_conf_data(push_data)
|
|
|
|
@staticmethod
|
|
def del_crontab():
|
|
"""
|
|
@name 删除项目定时清理任务
|
|
@auther hezhihong<2022-10-31>
|
|
@return
|
|
"""
|
|
cron_name = '[Do not delete] Vulnerability scanning scheduled task'
|
|
cron_list = DB('crontab').where("name=?", (cron_name,)).select()
|
|
|
|
if cron_list:
|
|
for i in cron_list:
|
|
if not i:
|
|
continue
|
|
args = {"id": i['id']}
|
|
import crontab
|
|
crontab.crontab().DelCrontab(args)
|
|
|
|
def add_crontab(self, day, channel):
|
|
"""
|
|
@name 构造计划任务
|
|
"""
|
|
cron_name = '[Do not delete] Vulnerability scanning scheduled task'
|
|
cron_list = DB('crontab').where("name=?", (cron_name,)).select()
|
|
if cron_list:
|
|
self.del_crontab()
|
|
if not DB('crontab').where('name=?',(cron_name,)).count():
|
|
args = {
|
|
"name": cron_name,
|
|
"type": 'day-n',
|
|
"where1": day,
|
|
"hour": '10',
|
|
"minute": '30',
|
|
"sName": "",
|
|
"sType": 'toShell',
|
|
"notice": '0',
|
|
"notice_channel": channel,
|
|
"save": '',
|
|
"save_local": '1',
|
|
"backupTo": '',
|
|
"sBody": 'btpython /www/server/panel/script/cron_scaning.py {}'.format(channel),
|
|
"urladdress": '',
|
|
"user": 'root'
|
|
}
|
|
import crontab
|
|
res = crontab.crontab().AddCrontab(args)
|
|
if res and "id" in res.keys():
|
|
return True
|
|
return False
|
|
return True
|
|
|
|
def task_config_create_hook(self, task: dict) -> Optional[str]:
|
|
return self.task_config_update_hook(task)
|
|
|
|
def task_config_update_hook(self, task: dict) -> Optional[str]:
|
|
if task["status"]:
|
|
day = task['task_data']['cycle']
|
|
channel = ",".join(task['sender'])
|
|
if self.add_crontab(day, channel):
|
|
return None
|
|
return "Failed to add scheduled task"
|
|
else:
|
|
self.del_crontab()
|
|
|
|
def task_config_remove_hook(self, task: dict) -> None:
|
|
self.del_crontab()
|
|
|
|
|
|
class FileDetectTask(BaseTask):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.source_name: str = 'file_detect'
|
|
self.title: str = 'System file integrity reminder'
|
|
self.template_name: str = 'System file integrity reminder'
|
|
|
|
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
|
"""
|
|
检查设置的告警参数(是否合理)
|
|
@param task_data: 传入的告警参数,提前会经过默认值处理(即没有的字段添加默认值)
|
|
@return: 当检查无误时,返回一个 dict 当做后续的添加和修改的数据,
|
|
当检查有误时, 直接返回错误信息的字符串
|
|
"""
|
|
if not isinstance(task_data["hour"], int) or not isinstance(task_data["minute"], int):
|
|
return "Hours and minutes must be integers"
|
|
|
|
if task_data["hour"] < 0 or task_data["hour"] > 23:
|
|
return "The hour must be an integer between 0 and 23"
|
|
|
|
if task_data["minute"] < 0 or task_data["minute"] > 59:
|
|
return "Minutes must be an integer between 0 and 59"
|
|
|
|
return {
|
|
"interval": 60 * 60 * 24,
|
|
"hour": task_data["hour"],
|
|
"minute": task_data["minute"],
|
|
}
|
|
|
|
def get_keyword(self, task_data: dict) -> str:
|
|
"""
|
|
返回一个关键字,用于后续查询或执行任务时使用, 例如:防篡改告警,可以根据其规则id生成一个关键字,
|
|
后续通过规则id和来源tamper 查询并使用
|
|
@param task_data: 通过check_args后生成的告警参数字典
|
|
@return: 返回一个关键词字符串
|
|
"""
|
|
return "file_detect"
|
|
|
|
def get_title(self, task_data: dict) -> str:
|
|
"""
|
|
返回一个标题
|
|
@param task_data: 通过check_args后生成的告警参数字典
|
|
@return: 返回一个关键词字符串
|
|
"""
|
|
return 'System file integrity reminder'
|
|
|
|
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
|
return None
|
|
|
|
def filter_template(self, template: dict) -> Optional[dict]:
|
|
"""
|
|
过滤 和 更改模板中的信息, 返回空表是当前无法设置该任务
|
|
@param template: 任务的模板信息
|
|
@return:
|
|
"""
|
|
return template
|
|
|
|
def add_crontab(self, hour, minute, channel):
|
|
"""
|
|
@name 构造计划任务
|
|
"""
|
|
cron_name = '[Do not delete] File integrity monitoring scheduled task'
|
|
cron_list = DB('crontab').where("name=?", (cron_name,)).select()
|
|
|
|
if cron_list:
|
|
self.del_crontab()
|
|
if not DB('crontab').where('name=?', (cron_name,)).count():
|
|
args = {
|
|
"name": cron_name,
|
|
"type": 'day',
|
|
"where1": '',
|
|
"hour": hour,
|
|
"minute": minute,
|
|
"sName": "",
|
|
"sType": 'toShell',
|
|
"notice": '0',
|
|
"notice_channel": channel,
|
|
"save": '',
|
|
"save_local": '1',
|
|
"backupTo": '',
|
|
"sBody": 'btpython /www/server/panel/script/cron_file.py {}'.format(channel),
|
|
"urladdress": ''
|
|
}
|
|
import crontab
|
|
res = crontab.crontab().AddCrontab(args)
|
|
if res and "id" in res.keys():
|
|
return True
|
|
return False
|
|
return True
|
|
|
|
# 删除项目定时清理任务
|
|
@staticmethod
|
|
def del_crontab():
|
|
cron_name = '[Do not delete] File integrity monitoring scheduled task'
|
|
cron_list = DB('crontab').where("name=?", (cron_name,)).select()
|
|
if cron_list:
|
|
for i in cron_list:
|
|
if not i: continue
|
|
args = {"id": i['id']}
|
|
import crontab
|
|
crontab.crontab().DelCrontab(args)
|
|
|
|
def task_config_create_hook(self, task: dict) -> Optional[str]:
|
|
return self.task_config_update_hook(task)
|
|
|
|
def task_config_update_hook(self, task: dict) -> Optional[str]:
|
|
if task["status"]:
|
|
hour = task['task_data']['hour']
|
|
minute = task['task_data']['minute']
|
|
channel = ",".join(task['sender'])
|
|
if self.add_crontab(hour, minute, channel):
|
|
return None
|
|
return "Failed to add scheduled task"
|
|
else:
|
|
self.del_crontab()
|
|
|
|
def task_config_remove_hook(self, task: dict) -> None:
|
|
self.del_crontab()
|
|
|
|
@classmethod
|
|
def set_push_task(cls, status: bool, hour: int, minute: int, sender: list):
|
|
push_data = {
|
|
"template_id": "123",
|
|
"task_data": {
|
|
"status": status,
|
|
"sender": sender,
|
|
"task_data": {
|
|
"hour": hour,
|
|
"minute": minute,
|
|
}
|
|
}
|
|
}
|
|
from .manager import PushManager
|
|
return PushManager().set_task_conf_data(push_data)
|
|
|
|
|
|
class SafeCloudTask(BaseTask):
|
|
_config_file = "/www/server/panel/data/safeCloud/config.json"
|
|
_all_safe_type = ("webshell", )
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.source_name = "safe_cloud_hinge"
|
|
self.title = "Aapanel Cloud Security Center Alarm"
|
|
self.template_name = "Aapanel Cloud Security Center Alarm"
|
|
|
|
self._safe_cloud_conf: Optional[dict] = None
|
|
|
|
@property
|
|
def safe_cloud_conf(self) -> Optional[dict]:
|
|
"""
|
|
获取云安全配置
|
|
:return: 云安全配置
|
|
"""
|
|
if self._safe_cloud_conf and isinstance(self._safe_cloud_conf, dict):
|
|
return self._safe_cloud_conf
|
|
try:
|
|
self._safe_cloud_conf = json.loads(read_file(self._config_file))
|
|
return self._safe_cloud_conf
|
|
except:
|
|
self._init_config()
|
|
try:
|
|
self._safe_cloud_conf = json.loads(read_file(self._config_file))
|
|
return self._safe_cloud_conf
|
|
except:
|
|
pass
|
|
return None
|
|
|
|
|
|
def filter_template(self, template: dict) -> Optional[dict]:
|
|
"""
|
|
过滤模板
|
|
:param template: 模板
|
|
:return: 过滤后的模板
|
|
"""
|
|
return template
|
|
|
|
def save_safe_cloud_conf(self):
|
|
"""
|
|
保存云安全配置
|
|
"""
|
|
write_file(self._config_file, json.dumps(self._safe_cloud_conf))
|
|
|
|
def check_task_data(self, task_data: dict) -> Union[dict, str]:
|
|
"""
|
|
检查任务数据
|
|
:param task_data: 任务数据
|
|
:return: 检查后的任务数据
|
|
"""
|
|
if "safe_type" in task_data:
|
|
for i in task_data["safe_type"]:
|
|
if i not in self._all_safe_type:
|
|
return "Security type error"
|
|
else:
|
|
task_data["safe_type"] = ["webshell"]
|
|
|
|
task_data["interval"] = 60 * 60 * 3
|
|
return task_data
|
|
|
|
def check_num_rule(self, num_rule: dict) -> Union[dict, str]:
|
|
"""
|
|
检查告警数量规则,一天只能告警20次
|
|
:param num_rule: 告警数量规则
|
|
:return: 检查后的告警数量规则
|
|
"""
|
|
num_rule["day_num"] = 20
|
|
return num_rule
|
|
|
|
def check_time_rule(self, time_rule: dict) -> Union[dict, str]:
|
|
"""
|
|
检查告警时间规则[写死]
|
|
:param time_rule: 告警时间规则
|
|
:return: 检查后的告警时间规则
|
|
"""
|
|
# 测试数据为1秒 ,正常数据为 1200 20*60 20分钟告警一次
|
|
time_rule["send_interval"] = 1200
|
|
return time_rule
|
|
|
|
def get_keyword(self, task_data: dict) -> str:
|
|
"""
|
|
返回一个关键字,用于后续查询或执行任务时使用, 例如:防篡改告警,可以根据其规则id生成一个关键字,
|
|
后续通过规则id和来源tamper 查询并使用
|
|
@param task_data: 通过check_args后生成的告警参数字典
|
|
@return: 返回一个关键词字符串
|
|
"""
|
|
return "safe_cloud_hinge"
|
|
|
|
def get_title(self, task_data: dict) -> str:
|
|
"""
|
|
返回一个标题
|
|
@param task_data: 通过check_args后生成的告警参数字典
|
|
@return: 返回一个关键词字符串
|
|
"""
|
|
return 'Aapanel Cloud Security Center Alarm'
|
|
|
|
def get_push_data(self, task_id: str, task_data: dict) -> Optional[dict]:
|
|
"""
|
|
判断这个任务是否需要返送
|
|
@param task_id: 任务id
|
|
@param task_data: 任务的告警参数
|
|
@return: 如果触发了告警,返回一个dict的原文,作为告警信息,否则应当返回None表示未触发
|
|
返回之中应当包含一个 msg_list 的键(值为List[str]类型),将主要的信息返回
|
|
用于以下信息的自动序列化包含[dingding, feishu, mail, weixin, web_hook]
|
|
短信和微信公众号由于长度问题,必须每个任务手动实现
|
|
"""
|
|
|
|
return None
|
|
|
|
def task_config_create_hook(self, task: dict) -> Optional[str]:
|
|
return self.task_config_update_hook(task)
|
|
|
|
def task_config_update_hook(self, task: dict) -> Optional[str]:
|
|
"""
|
|
更新告警配置
|
|
:param task: 任务
|
|
:return: 更新后的任务
|
|
"""
|
|
if not self.safe_cloud_conf:
|
|
return "Failed to initialize configuration file, unable to add"
|
|
|
|
alert_data = self.safe_cloud_conf["alertable"]
|
|
alert_data["safe_type"] = task["task_data"].get("safe_type", ["webshell"])
|
|
alert_data["interval"] = task["task_data"].get("interval", 60*60*3)
|
|
alert_data["status"] = task["status"]
|
|
alert_data["sender"] = task["sender"]
|
|
alert_data["time_rule"] = task["time_rule"]
|
|
alert_data["number_rule"] = task["number_rule"]
|
|
self.save_safe_cloud_conf()
|
|
|
|
def task_config_remove_hook(self, task: dict) -> Optional[str]:
|
|
"""
|
|
删除告警配置
|
|
:param task: 任务
|
|
:return: 删除后的任务
|
|
"""
|
|
if not self.safe_cloud_conf:
|
|
return None
|
|
|
|
alert_data = self.safe_cloud_conf["alertable"]
|
|
alert_data["safe_type"] = task["task_data"].get("safe_type", ["webshell"])
|
|
alert_data["interval"] = task["task_data"].get("interval", 60*60*3)
|
|
alert_data["status"] = False
|
|
alert_data["sender"] = []
|
|
alert_data["time_rule"] = task["time_rule"]
|
|
alert_data["number_rule"] = task["number_rule"]
|
|
self.save_safe_cloud_conf()
|
|
|
|
# 更新告警配置
|
|
@staticmethod
|
|
def set_push_conf(alert_data: dict) -> Optional[str]:
|
|
"""
|
|
将告警信息设置到告警任务中去
|
|
:param alert_data:
|
|
:return:
|
|
"""
|
|
pm = PushManager()
|
|
p_data = {
|
|
"template_id": "124",
|
|
"task_data": {
|
|
"status": alert_data.get("status", True),
|
|
"sender": alert_data.get("sender", []),
|
|
"task_data": {
|
|
"safe_type": alert_data.get("safe_type", ["webshell"]),
|
|
"interval": alert_data.get("interval", 60*60*3),
|
|
},
|
|
"time_rule": alert_data.get("time_rule", {}),
|
|
"number_rule": alert_data.get("number_rule", {}),
|
|
}
|
|
}
|
|
return pm.set_task_conf_data(p_data)
|
|
|
|
|
|
@staticmethod
|
|
def _init_config():
|
|
"""
|
|
初始化配置
|
|
"""
|
|
try:
|
|
import PluginLoader
|
|
|
|
args = GET_CLASS()
|
|
args.model_index = 'project'
|
|
PluginLoader.module_run("safecloud", "init_config", args)
|
|
except:
|
|
pass
|
|
|
|
|
|
|
|
class ViewMsgFormat(BaseTaskViewMsg):
|
|
|
|
def get_msg(self, task: dict) -> Optional[str]:
|
|
if task["template_id"] == "122":
|
|
return "Every {} days, identify and scan for vulnerabilities in common open-source CMS programs across all websites and send alerts".format(task["task_data"]["cycle"])
|
|
if task["template_id"] == "123":
|
|
return "Scan the critical executable files in the system daily at 【{}:{}】, and send alerts when changes are detected".format(
|
|
task["task_data"]["hour"], task["task_data"]["minute"]
|
|
)
|
|
if task["template_id"] == "124":
|
|
return "Every {} hours, scan the server files to identify abnormal situations such as high resource consumption and malicious control of the server, and send alerts".format(
|
|
int(task["task_data"]["interval"] / 3600)
|
|
)
|
|
return None
|
|
|
|
|
|
SiteMonitorViolationWordTask.VIEW_MSG = VulnerabilityScanningTask.VIEW_MSG = FileDetectTask.VIEW_MSG = SafeCloudTask.VIEW_MSG = ViewMsgFormat |