mirror of https://github.com/midoks/mdserver-web
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1140 lines
38 KiB
1140 lines
38 KiB
# coding:utf-8
|
|
|
|
import sys
|
|
import io
|
|
import os
|
|
import time
|
|
import json
|
|
import re
|
|
import psutil
|
|
from datetime import datetime
|
|
|
|
sys.dont_write_bytecode = True
|
|
sys.path.append(os.getcwd() + "/class/core")
|
|
import mw
|
|
|
|
app_debug = False
|
|
if mw.isAppleSystem():
|
|
app_debug = True
|
|
|
|
|
|
class App:
|
|
|
|
__deny = '/etc/hosts.deny'
|
|
__allow = '/etc/hosts.allow'
|
|
__state = {True: '开启', False: '关闭'}
|
|
__months = {'Jan': '01', 'Feb': '02', 'Mar': '03', 'Apr': '04', 'May': '05', 'Jun': '06',
|
|
'Jul': '07', 'Aug': '08', 'Sep': '09', 'Sept': '09', 'Oct': '10', 'Nov': '11', 'Dec': '12'}
|
|
__name = '系统加固'
|
|
__deny_list = None
|
|
|
|
__config = None
|
|
__log_file = None
|
|
__last_ssh_time = 0
|
|
__last_ssh_size = 0
|
|
|
|
def __init__(self):
|
|
if mw.isAppleSystem():
|
|
self.__deny = self.getServerDir() + '/hosts.deny'
|
|
self.__allow = self.getServerDir() + '/hosts.allow'
|
|
|
|
def getPluginName(self):
|
|
return 'system_safe'
|
|
|
|
def getPluginDir(self):
|
|
return mw.getPluginDir() + '/' + self.getPluginName()
|
|
|
|
def getServerDir(self):
|
|
return mw.getServerDir() + '/' + self.getPluginName()
|
|
|
|
def getInitDFile(self):
|
|
if app_debug:
|
|
return '/tmp/' + self.getPluginName()
|
|
return '/etc/init.d/' + self.getPluginName()
|
|
|
|
def getInitDTpl(self):
|
|
path = self.getPluginDir() + "/init.d/" + self.getPluginName() + ".tpl"
|
|
return path
|
|
|
|
def getArgs(self):
|
|
args = sys.argv[2:]
|
|
tmp = {}
|
|
args_len = len(args)
|
|
|
|
if args_len == 1:
|
|
t = args[0].strip('{').strip('}')
|
|
t = t.split(':')
|
|
tmp[t[0]] = t[1]
|
|
elif args_len > 1:
|
|
for i in range(len(args)):
|
|
t = args[i].split(':')
|
|
tmp[t[0]] = t[1]
|
|
|
|
return tmp
|
|
|
|
def checkArgs(self, data, ck=[]):
|
|
for i in range(len(ck)):
|
|
if not ck[i] in data:
|
|
return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!'))
|
|
return (True, mw.returnJson(True, 'ok'))
|
|
|
|
def getServerConfPath(self):
|
|
return self.getServerDir() + "/config.json"
|
|
|
|
def getConf(self):
|
|
cpath = self.getServerConfPath()
|
|
cpath_tpl = self.getPluginDir() + "/conf/config.json"
|
|
|
|
if not os.path.exists(cpath):
|
|
t_data = mw.readFile(cpath_tpl)
|
|
t_data = json.loads(t_data)
|
|
t = json.dumps(t_data)
|
|
mw.writeFile(cpath, t)
|
|
return t_data
|
|
|
|
t_data = mw.readFile(cpath)
|
|
t_data = json.loads(t_data)
|
|
return t_data
|
|
|
|
def writeConf(self, data):
|
|
cpath = self.getServerConfPath()
|
|
tmp_conf = json.dumps(data)
|
|
mw.writeFile(cpath, tmp_conf)
|
|
|
|
def initDreplace(self):
|
|
|
|
file_tpl = self.getInitDTpl()
|
|
service_path = self.getServerDir()
|
|
|
|
initD_path = service_path + '/init.d'
|
|
if not os.path.exists(initD_path):
|
|
os.mkdir(initD_path)
|
|
|
|
# init.d
|
|
file_bin = initD_path + '/' + self.getPluginName()
|
|
if not os.path.exists(file_bin):
|
|
# initd replace
|
|
content = mw.readFile(file_tpl)
|
|
content = content.replace('{$SERVER_PATH}', service_path)
|
|
mw.writeFile(file_bin, content)
|
|
mw.execShell('chmod +x ' + file_bin)
|
|
|
|
# systemd
|
|
# /usr/lib/systemd/system
|
|
systemDir = mw.systemdCfgDir()
|
|
systemService = systemDir + '/system_safe.service'
|
|
systemServiceTpl = self.getPluginDir() + '/init.d/system_safe.service.tpl'
|
|
if os.path.exists(systemDir) and not os.path.exists(systemService):
|
|
se_content = mw.readFile(systemServiceTpl)
|
|
se_content = se_content.replace('{$SERVER_PATH}', service_path)
|
|
mw.writeFile(systemService, se_content)
|
|
mw.execShell('systemctl daemon-reload')
|
|
|
|
return file_bin
|
|
|
|
def ssOp(self, method):
|
|
file = self.initDreplace()
|
|
|
|
if not mw.isAppleSystem():
|
|
data = mw.execShell('systemctl ' + method +
|
|
' ' + self.getPluginName())
|
|
if data[1] == '':
|
|
return 'ok'
|
|
return 'fail'
|
|
|
|
data = mw.execShell(file + ' ' + method)
|
|
if data[1] == '':
|
|
return 'ok'
|
|
return 'fail'
|
|
|
|
# def status(self):
|
|
# data = self.getConf()
|
|
# if not data['open']:
|
|
# return 'stop'
|
|
# return 'start'
|
|
|
|
def status(self):
|
|
data = mw.execShell(
|
|
'ps -ef|grep "system_safe/index.py bg_start" | grep -v grep | awk \'{print $2}\'')
|
|
if data[0] == '':
|
|
return 'stop'
|
|
return 'start'
|
|
|
|
def start(self):
|
|
return self.ssOp('start')
|
|
|
|
def writeLog(self, msg):
|
|
mw.writeDbLog(self.__name, msg)
|
|
|
|
def getDenyList(self):
|
|
deny_file = self.getServerDir() + '/deny.json'
|
|
if not os.path.exists(deny_file):
|
|
mw.writeFile(deny_file, '{}')
|
|
self.__deny_list = json.loads(mw.readFile(deny_file))
|
|
|
|
# 存deny_list
|
|
def saveDeayList(self):
|
|
deny_file = self.getServerDir() + '/deny.json'
|
|
mw.writeFile(deny_file, json.dumps(self.__deny_list))
|
|
|
|
def get_ssh_limit(self):
|
|
data = self.getSshLimit()
|
|
return mw.returnJson(True, 'ok!', data)
|
|
|
|
# 获取当前SSH禁止IP
|
|
def getSshLimit(self):
|
|
denyConf = mw.readFile(self.__deny)
|
|
if not denyConf:
|
|
mw.writeFile(self.__deny, '')
|
|
return []
|
|
data = re.findall(
|
|
r"sshd:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):deny", denyConf)
|
|
return data
|
|
|
|
def get_ssh_limit_info(self):
|
|
data = self.getSshLimitInfo()
|
|
return mw.returnJson(True, 'ok!', data)
|
|
|
|
# 获取deny信息
|
|
def getSshLimitInfo(self):
|
|
self.getDenyList()
|
|
conf_list = self.getSshLimit()
|
|
data = []
|
|
for c_ip in conf_list:
|
|
tmp = {}
|
|
tmp['address'] = c_ip
|
|
tmp['end'] = 0
|
|
if c_ip in self.__deny_list:
|
|
tmp['end'] = mw.getDataFromInt(self.__deny_list[c_ip])
|
|
data.append(tmp)
|
|
return data
|
|
|
|
def add_ssh_limit(self):
|
|
args = self.getArgs()
|
|
check = self.checkArgs(args, ['ip'])
|
|
if not check[0]:
|
|
return check[1]
|
|
ip = args['ip']
|
|
return self.addSshLimit(ip)
|
|
|
|
# 添加SSH目标IP
|
|
def addSshLimit(self, ip=None):
|
|
if ip == None:
|
|
ip = self.ip
|
|
|
|
if ip in self.getSshLimit():
|
|
return mw.returnJson(True, '指定IP黑名单已存在!')
|
|
denyConf = mw.readFile(self.__deny).strip()
|
|
while denyConf[-1:] == "\n" or denyConf[-1:] == " ":
|
|
denyConf = denyConf[:-1]
|
|
denyConf += "\nsshd:" + ip + ":deny\n"
|
|
mw.writeFile(self.__deny, denyConf)
|
|
if ip in self.getSshLimit():
|
|
msg = u'添加IP[%s]到SSH-IP黑名单' % ip
|
|
self.writeLog(msg)
|
|
self.getDenyList()
|
|
if not ip in self.__deny_list:
|
|
self.__deny_list[ip] = 0
|
|
self.saveDeayList()
|
|
return mw.returnJson(True, '添加成功!')
|
|
return mw.returnJson(False, '添加失败!')
|
|
|
|
def remove_ssh_limit(self):
|
|
args = self.getArgs()
|
|
check = self.checkArgs(args, ['ip'])
|
|
if not check[0]:
|
|
return check[1]
|
|
ip = args['ip']
|
|
return self.removeSshLimit(ip)
|
|
|
|
# 删除IP黑名单
|
|
def removeSshLimit(self, ip=None):
|
|
if ip == None:
|
|
ip = self.ip
|
|
|
|
if not self.__deny_list:
|
|
self.getDenyList()
|
|
if ip in self.__deny_list:
|
|
del(self.__deny_list[ip])
|
|
self.saveDeayList()
|
|
if not ip in self.getSshLimit():
|
|
return mw.returnJson(True, '指定IP黑名单不存在!')
|
|
|
|
denyConf = mw.readFile(self.__deny).strip()
|
|
while denyConf[-1:] == "\n" or denyConf[-1:] == " ":
|
|
denyConf = denyConf[:-1]
|
|
denyConf = re.sub("\n?sshd:" + ip + ":deny\n?", "\n", denyConf)
|
|
mw.writeFile(self.__deny, denyConf + "\n")
|
|
|
|
msg = '从SSH-IP黑名单中解封[%s]' % ip
|
|
self.writeLog(msg)
|
|
return mw.returnJson(True, '解封成功!')
|
|
|
|
def sshLoginTask(self):
|
|
if not self.__log_file:
|
|
log_file = '/var/log/secure'
|
|
if not os.path.exists(log_file):
|
|
log_file = '/var/log/auth.log'
|
|
if not os.path.exists(log_file):
|
|
return
|
|
self.__log_file = log_file
|
|
|
|
if not self.__log_file:
|
|
return
|
|
|
|
log_size = os.path.getsize(self.__log_file)
|
|
if self.__last_ssh_size == log_size:
|
|
return
|
|
self.__last_ssh_size = log_size
|
|
try:
|
|
config = self.__config
|
|
ssh_config = self.__config['ssh']
|
|
line_num = ssh_config['limit_count'] * 20
|
|
secure_logs = mw.getLastLine(
|
|
self.__log_file, line_num).split('\n')
|
|
|
|
total_time = '/dev/shm/ssh_total_time.pl'
|
|
if not os.path.exists(total_time):
|
|
mw.writeFile(total_time, str(int(time.time())))
|
|
|
|
last_total_time = int(mw.readFile(total_time))
|
|
now_total_time = int(time.time())
|
|
|
|
# print("last_total_time:", last_total_time)
|
|
# print("now_total_time:", now_total_time)
|
|
|
|
self.getDenyList()
|
|
deny_list = list(self.__deny_list.keys())
|
|
for i in range(len(deny_list)):
|
|
c_ip = deny_list[i]
|
|
if self.__deny_list[c_ip] > now_total_time or self.__deny_list[c_ip] == 0:
|
|
continue
|
|
self.ip = c_ip
|
|
self.removeSshLimit(None)
|
|
ip_total = {}
|
|
for log in secure_logs:
|
|
if log.find('Failed password for') != -1:
|
|
login_time = self.__to_date(
|
|
re.search(r'^\w+\s+\d+\s+\d+:\d+:\d+', log).group())
|
|
if now_total_time - login_time >= ssh_config['cycle']:
|
|
continue
|
|
|
|
client_ip = re.search(r'(\d+\.)+\d+', log).group()
|
|
if client_ip in self.__deny_list:
|
|
continue
|
|
if not client_ip in ip_total:
|
|
ip_total[client_ip] = 0
|
|
ip_total[client_ip] += 1
|
|
if ip_total[client_ip] <= ssh_config['limit_count']:
|
|
continue
|
|
self.__deny_list[
|
|
client_ip] = now_total_time + ssh_config['limit']
|
|
|
|
self.saveDeayList()
|
|
self.ip = client_ip
|
|
|
|
self.addSshLimit(None)
|
|
self.writeLog("[%s]在[%s]秒内连续[%s]次登录SSH失败,封锁[%s]秒" % (
|
|
client_ip, ssh_config['cycle'], ssh_config['limit_count'], ssh_config['limit']))
|
|
elif log.find('Accepted p') != -1:
|
|
login_time = self.__to_date(
|
|
re.search(r'^\w+\s+\d+\s+\d+:\d+:\d+', log).group())
|
|
if login_time < last_total_time:
|
|
continue
|
|
client_ip = re.search(r'(\d+\.)+\d+', log).group()
|
|
login_user = re.findall(r"(\w+)\s+from", log)[0]
|
|
self.writeLog("用户[%s]成功登录服务器,用户IP:[%s],登录时间:[%s]" % (
|
|
login_user, client_ip, time.strftime('%Y-%m-%d %X', time.localtime(login_time))))
|
|
mw.writeFile(total_time, str(int(time.time())))
|
|
except Exception as e:
|
|
print(mw.getTracebackInfo())
|
|
return 'success'
|
|
|
|
__limit = 30
|
|
__vmsize = 1048576 * 100
|
|
__wlist = None
|
|
__wslist = None
|
|
__elist = None
|
|
__last_pid_count = 0
|
|
__last_return_time = 0 # 上次返回结果的时间
|
|
__return_timeout = 360
|
|
|
|
def getSysProcess(self, get):
|
|
# 是否直接从属性中获取
|
|
stime = time.time()
|
|
if stime - self.__last_return_time < self.__return_timeout:
|
|
if self.__sys_process:
|
|
return True
|
|
self.__last_return_time = stime
|
|
|
|
# 本地是否存在系统进程白名单文件
|
|
config_file = self.getServerDir() + '/sys_process.json'
|
|
is_force = False
|
|
if not os.path.exists(config_file):
|
|
mw.writeFile(config_file, json.dumps([]))
|
|
is_force = True
|
|
|
|
data = json.loads(mw.readFile(config_file))
|
|
self.__sys_process = data
|
|
return True
|
|
|
|
# 取进程白名单列表
|
|
def getProcessWhite(self, get):
|
|
data = self.getConf()
|
|
return data['process']['process_white']
|
|
|
|
# 取进程关键词
|
|
def getProcessRule(self, get):
|
|
data = self.getConf()
|
|
return data['process']['process_white_rule']
|
|
|
|
# 取进程排除名单
|
|
def getProcessExclude(self, get):
|
|
data = self.getConf()
|
|
return data['process']['process_exclude']
|
|
|
|
# 检查白名单
|
|
def checkWhite(self, name):
|
|
if not self.__elist:
|
|
self.__elist = self.getProcessExclude(None)
|
|
if not self.__wlist:
|
|
self.__wlist = self.getProcessWhite(None)
|
|
if not self.__wslist:
|
|
self.__wslist = self.getProcessRule(None)
|
|
self.getSysProcess(None)
|
|
if name in ['mw', 'dnf', 'yum',
|
|
'apt', 'apt-get', 'grep',
|
|
'awk', 'python', 'node', 'php', 'mysqld',
|
|
'httpd', 'openresty', 'wget', 'curl', 'openssl',
|
|
'rspamd', 'supervisord', 'tlsmgr']:
|
|
return True
|
|
if name in self.__elist:
|
|
return True
|
|
if name in self.__sys_process:
|
|
return True
|
|
if name in self.__wlist:
|
|
return True
|
|
for key in self.__wslist:
|
|
if name.find(key) != -1:
|
|
return True
|
|
return False
|
|
|
|
def checkMainProccess(self, pid):
|
|
if pid < 1100:
|
|
return
|
|
fname = '/proc/' + str(pid) + '/comm'
|
|
if not os.path.exists(fname):
|
|
return
|
|
name = mw.readFile(fname).strip()
|
|
is_num_name = re.match(r"^\d+$", name)
|
|
if not is_num_name:
|
|
if self.checkWhite(name):
|
|
return
|
|
try:
|
|
p = psutil.Process(pid)
|
|
percent = p.cpu_percent(interval=0.1)
|
|
vm = p.memory_info().vms
|
|
if percent > self.__limit or vm > self.__vmsize:
|
|
cmdline = ' '.join(p.cmdline())
|
|
if cmdline.find('/www/server/cron') != -1:
|
|
return
|
|
if cmdline.find('/www/server') != -1:
|
|
return
|
|
if name.find('kworker') != -1 or name.find('mw_') == 0:
|
|
return
|
|
p.kill()
|
|
self.writeLog("已强制结束异常进程:[%s],PID:[%s],CPU:[%s],CMD:[%s]" % (
|
|
name, pid, percent, cmdline))
|
|
except:
|
|
print(mw.getTracebackInfo())
|
|
return
|
|
|
|
def checkMain(self):
|
|
pids = psutil.pids()
|
|
pid_count = len(pids)
|
|
if self.__last_pid_count == pid_count:
|
|
return
|
|
self.__last_pid_count = pid_count
|
|
|
|
try:
|
|
for pid in pids:
|
|
self.checkMainProccess(pid)
|
|
except Exception as e:
|
|
print(mw.getTracebackInfo())
|
|
|
|
def processTask(self):
|
|
|
|
if not self.__config:
|
|
self.__config = self.getConf()
|
|
if not self.__config:
|
|
return
|
|
|
|
self.setOpen(1)
|
|
is_open = 0
|
|
while True:
|
|
if self.__config['ssh']['open']:
|
|
is_open += 1
|
|
self.sshLoginTask()
|
|
if self.__config['process']['open']:
|
|
is_open += 1
|
|
self.checkMain()
|
|
|
|
if is_open > 60:
|
|
self.__config = self.getConf()
|
|
is_open = 0
|
|
time.sleep(1)
|
|
|
|
def bg_start(self):
|
|
try:
|
|
self.processTask()
|
|
except Exception as e:
|
|
print(mw.getTracebackInfo())
|
|
self.bg_stop()
|
|
self.writeLog('【{}】系统加固监控进程启动异常关闭'.format(mw.getDate()))
|
|
return 'ok'
|
|
|
|
def bg_stop(self):
|
|
try:
|
|
self.setOpen(0)
|
|
except Exception as e:
|
|
print(mw.getTracebackInfo())
|
|
print('【{}】系统加固监控进程停止异常关闭'.format(mw.getDate()))
|
|
return ''
|
|
|
|
def stop(self):
|
|
return self.ssOp('stop')
|
|
|
|
def restart(self):
|
|
return self.ssOp('restart')
|
|
|
|
def reload(self):
|
|
return self.ssOp('reload')
|
|
|
|
def initd_status(self):
|
|
if mw.isAppleSystem():
|
|
return "Apple Computer does not support"
|
|
shell_cmd = 'systemctl status %s | grep loaded | grep "enabled;"' % (
|
|
self.getPluginName())
|
|
data = mw.execShell(shell_cmd)
|
|
if data[0] == '':
|
|
return 'fail'
|
|
return 'ok'
|
|
|
|
def initd_install(self):
|
|
if mw.isAppleSystem():
|
|
return "Apple Computer does not support"
|
|
|
|
mw.execShell('systemctl enable ' + self.getPluginName())
|
|
return 'ok'
|
|
|
|
def initd_uninstall(self):
|
|
if mw.isAppleSystem():
|
|
return "Apple Computer does not support"
|
|
|
|
mw.execShell('systemctl disable ' + self.getPluginName())
|
|
return 'ok'
|
|
|
|
def __lock_path(self, info):
|
|
try:
|
|
if not os.path.exists(info['path']):
|
|
return False
|
|
if info['d_mode']:
|
|
os.chmod(info['path'], info['d_mode'])
|
|
if info['chattr']:
|
|
cmd = "chattr -R +%s %s" % (info['chattr'], info['path'])
|
|
mw.execShell(cmd)
|
|
return True
|
|
except Exception as e:
|
|
|
|
return False
|
|
|
|
def __unlock_path(self, info):
|
|
try:
|
|
if not os.path.exists(info['path']):
|
|
return False
|
|
if info['chattr']:
|
|
cmd = "chattr -R -%s %s" % (info['chattr'], info['path'])
|
|
mw.execShell(cmd)
|
|
|
|
if info['s_mode']:
|
|
os.chmod(info['path'], info['s_mode'])
|
|
return True
|
|
except Exception as e:
|
|
mw.getTracebackInfo()
|
|
return False
|
|
|
|
def __set_safe_state(self, paths, lock=False):
|
|
for path_info in paths:
|
|
if lock:
|
|
self.__lock_path(path_info)
|
|
else:
|
|
self.__unlock_path(path_info)
|
|
return True
|
|
|
|
# 转换时间格式
|
|
def __to_date(self, date_str):
|
|
tmp = re.split('\s+', date_str)
|
|
s_date = str(datetime.now().year) + '-' + \
|
|
self.__months.get(tmp[0]) + '-' + tmp[1] + ' ' + tmp[2]
|
|
time_array = time.strptime(s_date, "%Y-%m-%d %H:%M:%S")
|
|
time_stamp = int(time.mktime(time_array))
|
|
return time_stamp
|
|
|
|
def conf(self):
|
|
data = self.getConf()
|
|
return mw.returnJson(True, 'ok', data)
|
|
|
|
def setOpen(self, is_open=-1):
|
|
cpath = self.getServerConfPath()
|
|
data = self.getConf()
|
|
if is_open != -1:
|
|
if is_open == 0:
|
|
data['open'] = False
|
|
else:
|
|
data['open'] = True
|
|
|
|
for s_name in data.keys():
|
|
if type(data[s_name]) == bool:
|
|
continue
|
|
if not 'name' in data[s_name]:
|
|
continue
|
|
if not 'paths' in data[s_name]:
|
|
continue
|
|
s_name_status = False
|
|
if data['open']:
|
|
s_name_status = True
|
|
# print(data[s_name]['paths'], data[s_name]['open'])
|
|
self.__set_safe_state(data[s_name]['paths'], s_name_status)
|
|
msg = '已[%s]系统加固功能' % self.__state[data['open']]
|
|
self.writeLog(msg)
|
|
self.writeConf(data)
|
|
|
|
def set_safe_status(self):
|
|
args = self.getArgs()
|
|
data = self.checkArgs(args, ['tag', 'status'])
|
|
if not data[0]:
|
|
return data[1]
|
|
|
|
tag = args['tag']
|
|
status = args['status']
|
|
|
|
# 转换格式
|
|
if status == 'false':
|
|
status = False
|
|
if status == 'true':
|
|
status = True
|
|
|
|
if not tag in ['bin', 'service', 'home', 'user', 'bin', 'cron', 'ssh', 'process']:
|
|
return mw.returnJson(False, '不存在此配置[{}]!'.format(tag))
|
|
|
|
data = self.getConf()
|
|
data[tag]['open'] = status
|
|
self.writeConf(data)
|
|
return mw.returnJson(True, '设置成功')
|
|
|
|
# 取文件或目录锁定状态
|
|
def __get_path_state(self, path):
|
|
if not os.path.exists(path):
|
|
return 'i'
|
|
if os.path.isfile(path):
|
|
shell_cmd = "lsattr %s|awk '{print $1}'" % path
|
|
else:
|
|
shell_cmd = "lsattr {}/ |grep '{}$'|awk '{{print $1}}'".format(
|
|
os.path.dirname(path), path)
|
|
result = mw.execShell(shell_cmd)[0]
|
|
if result.find('-i-') != -1:
|
|
return 'i'
|
|
if result.find('-a-') != -1:
|
|
return 'a'
|
|
return False
|
|
|
|
# 遍历当前防护状态
|
|
def __list_safe_state(self, paths):
|
|
result = []
|
|
for i in range(len(paths)):
|
|
if not os.path.exists(paths[i]['path']):
|
|
continue
|
|
if os.path.islink(paths[i]['path']):
|
|
continue
|
|
mstate = self.__get_path_state(paths[i]['path'])
|
|
paths[i]['state'] = mstate == paths[i]['chattr']
|
|
paths[i]['s_mode'] = oct(paths[i]['s_mode'])
|
|
paths[i]['d_mode'] = oct(paths[i]['d_mode'])
|
|
result.append(paths[i])
|
|
return result
|
|
|
|
def get_safe_data(self):
|
|
|
|
args = self.getArgs()
|
|
check = self.checkArgs(args, ['tag'])
|
|
if not check[0]:
|
|
return check[1]
|
|
|
|
tag = args['tag']
|
|
if not tag in ['bin', 'service', 'home', 'user', 'bin', 'cron']:
|
|
return mw.returnJson(False, '不存在此配置[{}]!'.format(tag))
|
|
|
|
cpath = self.getServerConfPath()
|
|
data = self.getConf()
|
|
tmp = data[tag]
|
|
tmp['paths'] = self.__list_safe_state(tmp['paths'])
|
|
return mw.returnJson(True, {'open': data['open']}, tmp)
|
|
|
|
def get_ssh_data(self):
|
|
data = self.getConf()
|
|
tmp = data['ssh']
|
|
return mw.returnJson(True, {'open': data['open']}, tmp)
|
|
|
|
def get_process_data(self):
|
|
data = self.getConf()
|
|
tmp = data['process']
|
|
return mw.returnJson(True, {'open': data['open']}, tmp)
|
|
|
|
# 添加防护对象
|
|
def add_safe_path(self):
|
|
args = self.getArgs()
|
|
check = self.checkArgs(args, ['tag', 'path', 'chattr', 'd_mode'])
|
|
if not check[0]:
|
|
return check[1]
|
|
|
|
path = args['path']
|
|
tag = args['tag']
|
|
chattr = args['chattr']
|
|
d_mode = args['d_mode']
|
|
if path[-1] == '/':
|
|
path = path[:-1]
|
|
if not os.path.exists(path):
|
|
return mw.returnJson(False, '指定文件或目录不存在!')
|
|
data = self.getConf()
|
|
|
|
for m_path in data[tag]['paths']:
|
|
if path == m_path['path']:
|
|
return mw.returnJson(False, '指定文件或目录已经添加过了!')
|
|
|
|
path_info = {}
|
|
path_info['path'] = path
|
|
path_info['chattr'] = chattr
|
|
path_info['s_mode'] = int(oct(os.stat(path).st_mode)[-3:], 8)
|
|
if d_mode != '':
|
|
path_info['d_mode'] = int(d_mode, 8)
|
|
else:
|
|
path_info['d_mode'] = path_info['s_mode']
|
|
|
|
data[tag]['paths'].insert(0, path_info)
|
|
if 'paths' in data[tag]:
|
|
mw.execShell('chattr -R -%s %s' %
|
|
(path_info['chattr'], path_info['path']))
|
|
if data['open']:
|
|
self.__set_safe_state([path_info], data[tag]['open'])
|
|
msg = '添加防护对象[%s]到[%s]' % (path, data[tag]['name'])
|
|
self.writeLog(msg)
|
|
self.writeConf(data)
|
|
return mw.returnJson(True, msg)
|
|
|
|
def save_safe_ssh(self):
|
|
|
|
args = self.getArgs()
|
|
check = self.checkArgs(args, ['cycle', 'limit', 'limit_count'])
|
|
if not check[0]:
|
|
return check[1]
|
|
|
|
cycle = int(args['cycle'])
|
|
limit = int(args['limit'])
|
|
limit_count = int(args['limit_count'])
|
|
|
|
if cycle > limit:
|
|
return mw.returnJson(False, '封锁时间不能小于检测周期!')
|
|
if cycle < 30 or cycle > 1800:
|
|
return mw.returnJson(False, '检测周期的值必需在30 - 1800秒之间!')
|
|
if limit < 60:
|
|
return mw.returnJson(False, '封锁时间不能小于60秒')
|
|
if limit_count < 3 or limit_count > 100:
|
|
return mw.returnJson(False, '检测阈值必需在3 - 100秒之间!')
|
|
data = self.getConf()
|
|
data['ssh']['cycle'] = cycle
|
|
data['ssh']['limit'] = limit
|
|
data['ssh']['limit_count'] = limit_count
|
|
self.writeConf(data)
|
|
msg = '修改SSH策略: 在[%s]秒内,登录错误[%s]次,封锁[%s]秒' % (
|
|
data['ssh']['cycle'], data['ssh']['limit_count'], data['ssh']['limit'])
|
|
self.writeLog(msg)
|
|
self.restart()
|
|
return mw.returnJson(True, '配置已保存!')
|
|
|
|
# 添加进程白名单
|
|
def add_process_white(self):
|
|
args = self.getArgs()
|
|
check = self.checkArgs(args, ['process_name'])
|
|
if not check[0]:
|
|
return check[1]
|
|
|
|
data = self.getConf()
|
|
process_name = args['process_name']
|
|
if process_name in data['process']['process_white']:
|
|
return mw.returnJson(False, '指定进程名已在白名单')
|
|
data['process']['process_white'].insert(0, process_name)
|
|
self.writeConf(data)
|
|
msg = '添加进程名[%s]到进程白名单' % process_name
|
|
self.writeLog(msg)
|
|
self.restart()
|
|
return mw.returnJson(True, msg)
|
|
|
|
def del_safe_proccess_name(self):
|
|
args = self.getArgs()
|
|
check = self.checkArgs(args, ['tag', 'index'])
|
|
if not check[0]:
|
|
return check[1]
|
|
|
|
tag = args['tag']
|
|
index = int(args['index'])
|
|
|
|
cpath = self.getServerConfPath()
|
|
data = self.getConf()
|
|
|
|
del(data[tag]['process_white'][index])
|
|
t = json.dumps(data)
|
|
mw.writeFile(cpath, t)
|
|
return mw.returnJson(True, '删除成功')
|
|
|
|
def del_safe_path(self):
|
|
args = self.getArgs()
|
|
check = self.checkArgs(args, ['tag', 'index'])
|
|
if not check[0]:
|
|
return check[1]
|
|
|
|
tag = args['tag']
|
|
index = int(args['index'])
|
|
|
|
cpath = self.getServerConfPath()
|
|
data = self.getConf()
|
|
|
|
del(data[tag]['paths'][index])
|
|
t = json.dumps(data)
|
|
mw.writeFile(cpath, t)
|
|
return mw.returnJson(True, '删除成功')
|
|
|
|
def get_log_title(self, log_name):
|
|
log_name = log_name.replace('.1', '')
|
|
if log_name in ['auth.log', 'secure'] or log_name.find('auth.') == 0:
|
|
return '授权日志'
|
|
if log_name in ['dmesg'] or log_name.find('dmesg') == 0:
|
|
return '内核缓冲区日志'
|
|
if log_name in ['syslog'] or log_name.find('syslog') == 0:
|
|
return '系统警告/错误日志'
|
|
if log_name in ['btmp']:
|
|
return '失败的登录记录'
|
|
if log_name in ['utmp', 'wtmp']:
|
|
return '登录和重启记录'
|
|
if log_name in ['lastlog']:
|
|
return '用户最后登录'
|
|
if log_name in ['yum.log']:
|
|
return 'yum包管理器日志'
|
|
if log_name in ['anaconda.log']:
|
|
return 'Anaconda日志'
|
|
if log_name in ['dpkg.log']:
|
|
return 'dpkg日志'
|
|
if log_name in ['daemon.log']:
|
|
return '系统后台守护进程日志'
|
|
if log_name in ['boot.log']:
|
|
return '启动日志'
|
|
if log_name in ['kern.log']:
|
|
return '内核日志'
|
|
if log_name in ['maillog', 'mail.log']:
|
|
return '邮件日志'
|
|
if log_name.find('Xorg') == 0:
|
|
return 'Xorg日志'
|
|
if log_name in ['cron.log']:
|
|
return '定时任务日志'
|
|
if log_name in ['alternatives.log']:
|
|
return '更新替代信息'
|
|
if log_name in ['debug']:
|
|
return '调试信息'
|
|
if log_name.find('apt') == 0:
|
|
return 'apt-get相关日志'
|
|
if log_name.find('installer') == 0:
|
|
return '系统安装相关日志'
|
|
if log_name in ['messages']:
|
|
return '综合日志'
|
|
return '{}日志'.format(log_name.split('.')[0])
|
|
|
|
def get_sys_logfiles(self):
|
|
log_dir = '/var/log'
|
|
log_files = []
|
|
for log_file in os.listdir(log_dir):
|
|
|
|
log_suffix = log_file.split('.')[-1:]
|
|
if log_suffix[0] in ['gz', 'xz', 'bz2', 'asl']:
|
|
continue
|
|
|
|
if log_file in ['.', '..', 'faillog', 'fontconfig.log', 'unattended-upgrades', 'tallylog']:
|
|
continue
|
|
|
|
# print(log_suffix)
|
|
filename = os.path.join(log_dir, log_file)
|
|
if os.path.isfile(filename):
|
|
file_size = os.path.getsize(filename)
|
|
if not file_size:
|
|
continue
|
|
|
|
tmp = {
|
|
'name': log_file,
|
|
'size': file_size,
|
|
'log_file': filename,
|
|
'title': self.get_log_title(log_file),
|
|
'uptime': os.path.getmtime(filename)
|
|
}
|
|
|
|
log_files.append(tmp)
|
|
else:
|
|
for next_name in os.listdir(filename):
|
|
if next_name[-3:] in ['.gz', '.xz']:
|
|
continue
|
|
next_file = os.path.join(filename, next_name)
|
|
if not os.path.isfile(next_file):
|
|
continue
|
|
file_size = os.path.getsize(next_file)
|
|
if not file_size:
|
|
continue
|
|
log_name = '{}/{}'.format(log_file, next_name)
|
|
tmp = {
|
|
'name': log_name,
|
|
'size': file_size,
|
|
'log_file': next_file,
|
|
'title': self.get_log_title(log_name),
|
|
'uptime': os.path.getmtime(next_file)
|
|
}
|
|
log_files.append(tmp)
|
|
log_files = sorted(log_files, key=lambda x: x['name'], reverse=True)
|
|
return mw.returnJson(True, 'ok', log_files)
|
|
|
|
def get_last(self, log_name):
|
|
# 获取日志
|
|
cmd = '''LANG=en_US.UTF-8 last -n 200 -x -f {} |grep -v 127.0.0.1|grep -v " begins"'''.format(
|
|
'/var/log/' + log_name)
|
|
result = mw.execShell(cmd)
|
|
lastlog_list = []
|
|
for _line in result[0].split("\n"):
|
|
if not _line:
|
|
continue
|
|
tmp = {}
|
|
sp_arr = _line.split()
|
|
tmp['用户'] = sp_arr[0]
|
|
if sp_arr[0] == 'runlevel':
|
|
tmp['来源'] = sp_arr[4]
|
|
tmp['端口'] = ' '.join(sp_arr[1:4])
|
|
tmp['时间'] = self.__to_date3(
|
|
' '.join(sp_arr[5:])) + ' ' + ' '.join(sp_arr[-2:])
|
|
elif sp_arr[0] in ['reboot', 'shutdown']:
|
|
tmp['来源'] = sp_arr[3]
|
|
tmp['端口'] = ' '.join(sp_arr[1:3])
|
|
if sp_arr[-3] == '-':
|
|
tmp['时间'] = self.__to_date3(
|
|
' '.join(sp_arr[4:])) + ' ' + ' '.join(sp_arr[-3:])
|
|
else:
|
|
tmp['时间'] = self.__to_date3(
|
|
' '.join(sp_arr[4:])) + ' ' + ' '.join(sp_arr[-2:])
|
|
elif sp_arr[1] in ['tty1', 'tty', 'tty2', 'tty3', 'hvc0', 'hvc1', 'hvc2'] or len(sp_arr) == 9:
|
|
tmp['来源'] = ''
|
|
tmp['端口'] = sp_arr[1]
|
|
tmp['时间'] = self.__to_date3(
|
|
' '.join(sp_arr[2:])) + ' ' + ' '.join(sp_arr[-3:])
|
|
else:
|
|
tmp['来源'] = sp_arr[2]
|
|
tmp['端口'] = sp_arr[1]
|
|
tmp['时间'] = self.__to_date3(
|
|
' '.join(sp_arr[3:])) + ' ' + ' '.join(sp_arr[-3:])
|
|
|
|
# tmp['_line'] = _line
|
|
lastlog_list.append(tmp)
|
|
# lastlog_list = sorted(lastlog_list,key=lambda x:x['时间'],reverse=True)
|
|
return mw.returnJson(True, 'ok!', lastlog_list)
|
|
|
|
def get_lastlog(self):
|
|
cmd = '''LANG=en_US.UTF-8 lastlog|grep -v Username'''
|
|
result = mw.execShell(cmd)
|
|
lastlog_list = []
|
|
for _line in result[0].split("\n"):
|
|
if not _line:
|
|
continue
|
|
tmp = {}
|
|
sp_arr = _line.split()
|
|
tmp['用户'] = sp_arr[0]
|
|
# tmp['_line'] = _line
|
|
if _line.find('Never logged in') != -1:
|
|
tmp['最后登录时间'] = '0'
|
|
tmp['最后登录来源'] = '-'
|
|
tmp['最后登录端口'] = '-'
|
|
lastlog_list.append(tmp)
|
|
continue
|
|
tmp['最后登录来源'] = sp_arr[2]
|
|
tmp['最后登录端口'] = sp_arr[1]
|
|
tmp['最后登录时间'] = self.__to_date2(' '.join(sp_arr[3:]))
|
|
|
|
lastlog_list.append(tmp)
|
|
lastlog_list = sorted(lastlog_list, key=lambda x: x[
|
|
'最后登录时间'], reverse=True)
|
|
for i in range(len(lastlog_list)):
|
|
if lastlog_list[i]['最后登录时间'] == '0':
|
|
lastlog_list[i]['最后登录时间'] = '从未登录过'
|
|
return mw.returnJson(True, 'ok!', lastlog_list)
|
|
|
|
def get_sys_log_with_name(self, log_name):
|
|
if log_name in ['wtmp', 'btmp', 'utmp'] or log_name.find('wtmp') == 0 or log_name.find('btmp') == 0 or log_name.find('utmp') == 0:
|
|
return self.get_last(log_name)
|
|
|
|
if log_name.find('lastlog') == 0:
|
|
return self.get_lastlog()
|
|
|
|
if log_name.find('sa/sa') == 0:
|
|
if log_name.find('sa/sar') == -1:
|
|
return mw.execShell("sar -f /var/log/{}".format(log_name))[0]
|
|
log_dir = '/var/log'
|
|
log_file = log_dir + '/' + log_name
|
|
if not os.path.exists(log_file):
|
|
return mw.returnJson(False, '日志文件不存在!')
|
|
result = mw.getLastLine(log_file, 100)
|
|
log_list = []
|
|
is_string = True
|
|
for _line in result.split("\n"):
|
|
if not _line.strip():
|
|
continue
|
|
if log_name.find('sa/sa') == -1:
|
|
if _line[:3] in self.__months:
|
|
_msg = _line[16:]
|
|
_tmp = _msg.split(": ")
|
|
_act = ''
|
|
if len(_tmp) > 1:
|
|
_act = _tmp[0]
|
|
_msg = _tmp[1]
|
|
else:
|
|
_msg = _tmp[0]
|
|
_line = {
|
|
"时间": self.__to_date4(_line[:16].strip()),
|
|
"角色": _act,
|
|
"事件": _msg
|
|
}
|
|
is_string = False
|
|
elif _line[:2] in ['19', '20', '21', '22', '23', '24']:
|
|
_msg = _line[19:]
|
|
_tmp = _msg.split(" ")
|
|
_act = _tmp[1]
|
|
_msg = ' '.join(_tmp[2:])
|
|
_line = {
|
|
"时间": _line[:19].strip(),
|
|
"角色": _act,
|
|
"事件": _msg
|
|
}
|
|
is_string = False
|
|
elif log_name.find('alternatives') == 0:
|
|
_tmp = _line.split(": ")
|
|
_last = _tmp[0].split(" ")
|
|
_act = _last[0]
|
|
_msg = ' '.join(_tmp[1:])
|
|
_line = {
|
|
"时间": ' '.join(_last[1:]).strip(),
|
|
"角色": _act,
|
|
"事件": _msg
|
|
}
|
|
is_string = False
|
|
else:
|
|
if not is_string:
|
|
if type(_line) != dict:
|
|
continue
|
|
|
|
log_list.append(_line)
|
|
try:
|
|
# if len(log_list) > 1:
|
|
# if type(log_list[0]) != type(log_list[1]):
|
|
# del(log_list[0])
|
|
# log_list = sorted(log_list,key=lambda x:x['时间'],reverse=True)
|
|
# return log_list
|
|
|
|
_string = []
|
|
_dict = []
|
|
_list = []
|
|
for _line in log_list:
|
|
if isinstance(_line, str):
|
|
_string.append(_line.strip())
|
|
elif isinstance(_line, dict):
|
|
_dict.append(_line)
|
|
elif isinstance(_line, list):
|
|
_list.append(_line)
|
|
else:
|
|
continue
|
|
_str_len = len(_string)
|
|
_dict_len = len(_dict)
|
|
_list_len = len(_list)
|
|
if _str_len > _dict_len + _list_len:
|
|
return "\n".join(_string)
|
|
elif _dict_len > _str_len + _list_len:
|
|
return mw.returnJson(True, 'ok!', _dict)
|
|
else:
|
|
return mw.returnJson(True, 'ok!', _list)
|
|
|
|
except:
|
|
data = '\n'.join(log_list)
|
|
return mw.returnJson(True, 'ok!', data)
|
|
|
|
def get_sys_log(self):
|
|
args = self.getArgs()
|
|
check = self.checkArgs(args, ['log_name'])
|
|
if not check[0]:
|
|
return check[1]
|
|
|
|
log_name = args['log_name']
|
|
return self.get_sys_log_with_name(log_name)
|
|
|
|
def __to_date2(self, date_str):
|
|
tmp = date_str.split()
|
|
s_date = str(tmp[-1]) + '-' + self.__months.get(tmp[1],
|
|
tmp[1]) + '-' + tmp[2] + ' ' + tmp[3]
|
|
return s_date
|
|
|
|
def __to_date3(self, date_str):
|
|
tmp = date_str.split()
|
|
s_date = str(datetime.now().year) + '-' + \
|
|
self.__months.get(tmp[1], tmp[1]) + '-' + tmp[2] + ' ' + tmp[3]
|
|
return s_date
|
|
|
|
def __to_date4(self, date_str):
|
|
tmp = date_str.split()
|
|
s_date = str(datetime.now().year) + '-' + \
|
|
self.__months.get(tmp[0], tmp[0]) + '-' + tmp[1] + ' ' + tmp[2]
|
|
return s_date
|
|
|
|
def op_log(self):
|
|
args = self.getArgs()
|
|
check = self.checkArgs(args, ['p'])
|
|
if not check[0]:
|
|
return check[1]
|
|
|
|
p = int(args['p'])
|
|
limit = 10
|
|
start = (p - 1) * limit
|
|
|
|
_list = mw.M('logs').field(
|
|
'id,type,log,addtime').where('type=?', (self.__name,)).limit(str(start) + ',' + str(limit)).order('id desc').select()
|
|
data = {}
|
|
data['data'] = _list
|
|
count = mw.M('logs').where('type=?', (self.__name,)).count()
|
|
_page = {}
|
|
_page['count'] = count
|
|
_page['tojs'] = 'ssOpLogList'
|
|
_page['p'] = p
|
|
data['page'] = mw.getPage(_page)
|
|
return mw.returnJson(True, 'ok', data)
|
|
|
|
|
|
def get_sys_log(args):
|
|
classApp = App()
|
|
data = classApp.get_sys_log_with_name(args['log_name'])
|
|
return data
|
|
|
|
if __name__ == "__main__":
|
|
func = sys.argv[1]
|
|
classApp = App()
|
|
try:
|
|
data = eval("classApp." + func + "()")
|
|
print(data)
|
|
except Exception as e:
|
|
print(mw.getTracebackInfo())
|
|
|