You've already forked haproxy-wi
mirror of
https://github.com/roxy-wi/roxy-wi.git
synced 2025-12-18 12:04:07 +08:00
The commit modifies the date calculation in the database time_range checks to use the UTC timezone instead of the system's local time. This makes the time range checks more uniform and independent of the server's configuration. Additionally, it increases the version number in 'create_db.py' from '7.2.3.0' to '7.2.4.0'.
562 lines
18 KiB
Python
562 lines
18 KiB
Python
import json
|
|
|
|
from flask import render_template
|
|
|
|
import app.modules.db.sql as sql
|
|
import app.modules.db.waf as waf_sql
|
|
import app.modules.db.server as server_sql
|
|
import app.modules.db.backup as backup_sql
|
|
import app.modules.db.checker as checker_sql
|
|
import app.modules.db.service as service_sql
|
|
import app.modules.db.history as history_sql
|
|
import app.modules.db.portscanner as ps_sql
|
|
import app.modules.server.ssh as mod_ssh
|
|
import app.modules.common.common as common
|
|
import app.modules.roxywi.auth as roxywi_auth
|
|
import app.modules.roxywi.common as roxywi_common
|
|
|
|
|
|
def ssh_command(server_ip: str, commands: str, **kwargs):
|
|
if server_ip == '':
|
|
raise Exception('error: IP cannot be empty')
|
|
if kwargs.get('timeout'):
|
|
timeout = kwargs.get('timeout')
|
|
else:
|
|
timeout = 2
|
|
try:
|
|
with mod_ssh.ssh_connect(server_ip) as ssh:
|
|
if isinstance(commands, list):
|
|
command = commands[0]
|
|
else:
|
|
command = commands
|
|
try:
|
|
stdin, stdout, stderr = ssh.run_command(command, timeout=timeout)
|
|
stdin.close()
|
|
except Exception as e:
|
|
roxywi_common.handle_exceptions(e, server_ip, 'Something wrong with SSH connection. Probably sudo with password', roxywi=1)
|
|
|
|
if stderr:
|
|
for line in stderr.readlines():
|
|
if line:
|
|
roxywi_common.handle_exceptions(line, server_ip, line, roxywi=1)
|
|
|
|
if stdout.channel.recv_exit_status() and kwargs.get('rc'):
|
|
roxywi_common.handle_exceptions(stdout.read().decode('utf-8'), server_ip, f'Cannot perform SSH command: {command} ', roxywi=1)
|
|
|
|
if kwargs.get('raw'):
|
|
return stdout.readlines()
|
|
elif kwargs.get("show_log") == "1":
|
|
import app.modules.roxywi.logs as roxywi_logs
|
|
return roxywi_logs.show_log(stdout, grep=kwargs.get("grep"))
|
|
else:
|
|
return stdout.read().decode(encoding='UTF-8')
|
|
except Exception as e:
|
|
roxywi_common.handle_exceptions(e, server_ip, '', roxywi=1)
|
|
|
|
|
|
def subprocess_execute(cmd):
|
|
import subprocess
|
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, universal_newlines=True)
|
|
stdout, stderr = p.communicate()
|
|
output = stdout.splitlines()
|
|
return output, stderr
|
|
|
|
|
|
def subprocess_execute_stream(cmd):
|
|
import subprocess
|
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True, universal_newlines=True)
|
|
for line in iter(p.stdout.readline, ''):
|
|
yield line
|
|
|
|
|
|
def subprocess_execute_with_rc(cmd):
|
|
import subprocess
|
|
|
|
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True, universal_newlines=True)
|
|
stdout, stderr = p.communicate()
|
|
output = stdout.splitlines()
|
|
rc = p.returncode
|
|
|
|
return_out = {'output': output, 'error': stderr, 'rc': rc}
|
|
|
|
return return_out
|
|
|
|
|
|
def is_file_exists(server_ip: str, file: str) -> bool:
|
|
cmd = f'[ -f {file} ] && echo yes || echo no'
|
|
|
|
out = ssh_command(server_ip, cmd)
|
|
return True if 'yes' in out else False
|
|
|
|
|
|
def is_service_active(server_ip: str, service_name: str) -> bool:
|
|
cmd = f'systemctl is-active {service_name}'
|
|
|
|
out = ssh_command(server_ip, cmd)
|
|
out = out.strip()
|
|
return True if 'active' == out else False
|
|
|
|
|
|
def get_remote_files(server_ip: str, config_dir: str, file_format: str):
|
|
config_dir = common.return_nice_path(config_dir)
|
|
if file_format == 'conf':
|
|
command = f'sudo ls {config_dir}*/*.{file_format}'
|
|
else:
|
|
command = f'sudo ls {config_dir}|grep {file_format}$'
|
|
config_files = ssh_command(server_ip, command)
|
|
|
|
return config_files
|
|
|
|
|
|
def get_system_info(server_ip: str) -> str:
|
|
server_ip = common.is_ip_or_dns(server_ip)
|
|
if server_ip == '':
|
|
return 'error: IP cannot be empty'
|
|
|
|
server_id = server_sql.select_server_id_by_ip(server_ip)
|
|
command = "sudo lshw -quiet -json"
|
|
command1 = 'sudo hostnamectl |grep "Operating System"|awk -F":" \'{print $2}\''
|
|
|
|
try:
|
|
sys_info_returned = ssh_command(server_ip, command, timeout=5)
|
|
except Exception as e:
|
|
raise Exception(e)
|
|
|
|
if 'not found' in sys_info_returned:
|
|
raise Exception(f'You should install lshw on the server {server_ip}. Update System info after installation.')
|
|
|
|
try:
|
|
os_info = ssh_command(server_ip, command1)
|
|
except Exception as e:
|
|
raise Exception(e)
|
|
|
|
os_info = os_info.strip()
|
|
system_info = json.loads(sys_info_returned)
|
|
|
|
sys_info = {'hostname': system_info['id'], 'family': ''}
|
|
cpu = {'cpu_model': '', 'cpu_core': 0, 'cpu_thread': 0, 'hz': 0}
|
|
network = {}
|
|
ram = {'slots': 0, 'size': 0}
|
|
disks = {}
|
|
|
|
try:
|
|
sys_info['family'] = system_info['configuration']['family']
|
|
except Exception:
|
|
pass
|
|
|
|
for i in system_info['children']:
|
|
if i['class'] == 'network':
|
|
try:
|
|
ip = i['configuration']['ip']
|
|
except Exception:
|
|
ip = ''
|
|
network[i['logicalname']] = {
|
|
'description': i['description'],
|
|
'mac': i['serial'],
|
|
'ip': ip,
|
|
'up': i['configuration']['link']
|
|
}
|
|
for k, j in i.items():
|
|
if isinstance(j, list):
|
|
for b in j:
|
|
try:
|
|
if b['class'] == 'processor':
|
|
cpu['cpu_model'] = b['product']
|
|
cpu['cpu_core'] += 1
|
|
cpu['hz'] = round(int(b['capacity']) / 1000000)
|
|
try:
|
|
cpu['cpu_thread'] += int(b['configuration']['threads'])
|
|
except Exception:
|
|
cpu['cpu_thread'] = 1
|
|
except Exception:
|
|
pass
|
|
|
|
try:
|
|
if b['id'] == 'memory':
|
|
ram['size'] = round(b['size'] / 1073741824)
|
|
ram['slots'] = len(b['children'])
|
|
except Exception:
|
|
pass
|
|
|
|
try:
|
|
if b['class'] == 'storage':
|
|
for p, pval in b.items():
|
|
if isinstance(pval, list):
|
|
for disks_info in pval:
|
|
for volume_info in disks_info['children']:
|
|
if isinstance(volume_info['logicalname'], list):
|
|
volume_name = volume_info['logicalname'][0]
|
|
mount_point = volume_info['logicalname'][1]
|
|
size = round(volume_info['capacity'] / 1073741824)
|
|
size = str(size) + 'Gb'
|
|
fs = volume_info['configuration']['mount.fstype']
|
|
state = volume_info['configuration']['state']
|
|
disks[volume_name] = {
|
|
'mount_point': mount_point,
|
|
'size': size,
|
|
'fs': fs,
|
|
'state': state
|
|
}
|
|
except Exception:
|
|
pass
|
|
|
|
try:
|
|
if b['class'] == 'bridge':
|
|
if 'children' in b:
|
|
for s in b['children']:
|
|
if s['class'] == 'network':
|
|
if 'children' in s:
|
|
for net in s['children']:
|
|
network[net['logicalname']] = {
|
|
'description': net['description'],
|
|
'mac': net['serial']
|
|
}
|
|
if s['class'] == 'storage':
|
|
for p, pval in s.items():
|
|
if isinstance(pval, list):
|
|
for disks_info in pval:
|
|
if 'children' in disks_info:
|
|
for volume_info in disks_info['children']:
|
|
if isinstance(volume_info['logicalname'], dict):
|
|
volume_name = volume_info['logicalname'][0]
|
|
mount_point = volume_info['logicalname'][1]
|
|
size = round(volume_info['size'] / 1073741824)
|
|
size = str(size) + 'Gb'
|
|
fs = volume_info['configuration']['mount.fstype']
|
|
state = volume_info['configuration']['state']
|
|
disks[volume_name] = {
|
|
'mount_point': mount_point,
|
|
'size': size,
|
|
'fs': fs,
|
|
'state': state
|
|
}
|
|
for z, n in s.items():
|
|
if isinstance(n, list):
|
|
for y in n:
|
|
if y['class'] == 'network':
|
|
try:
|
|
for q in y['children']:
|
|
try:
|
|
ip = q['configuration']['ip']
|
|
except Exception:
|
|
ip = ''
|
|
network[q['logicalname']] = {
|
|
'description': q['description'],
|
|
'mac': q['serial'],
|
|
'ip': ip,
|
|
'up': q['configuration']['link']}
|
|
except Exception:
|
|
try:
|
|
network[y['logicalname']] = {
|
|
'description': y['description'],
|
|
'mac': y['serial'],
|
|
'ip': y['configuration']['ip'],
|
|
'up': y['configuration']['link']}
|
|
except Exception:
|
|
pass
|
|
if y['class'] == 'disk':
|
|
try:
|
|
for q in y['children']:
|
|
try:
|
|
if isinstance(q['logicalname'], list):
|
|
volume_name = q['logicalname'][0]
|
|
mount_point = q['logicalname'][1]
|
|
size = round(q['capacity'] / 1073741824)
|
|
size = str(size) + 'Gb'
|
|
fs = q['configuration']['mount.fstype']
|
|
state = q['configuration']['state']
|
|
disks[volume_name] = {
|
|
'mount_point': mount_point,
|
|
'size': size,
|
|
'fs': fs,
|
|
'state': state
|
|
}
|
|
except Exception as e:
|
|
print(e)
|
|
except Exception:
|
|
pass
|
|
if y['class'] == 'storage' or y['class'] == 'generic':
|
|
try:
|
|
for q in y['children']:
|
|
for o in q['children']:
|
|
try:
|
|
volume_name = o['logicalname']
|
|
mount_point = ''
|
|
size = round(o['size'] / 1073741824)
|
|
size = str(size) + 'Gb'
|
|
fs = ''
|
|
state = ''
|
|
disks[volume_name] = {
|
|
'mount_point': mount_point,
|
|
'size': size,
|
|
'fs': fs,
|
|
'state': state
|
|
}
|
|
except Exception:
|
|
pass
|
|
for w in o['children']:
|
|
try:
|
|
if isinstance(w['logicalname'], list):
|
|
volume_name = w['logicalname'][0]
|
|
mount_point = w['logicalname'][1]
|
|
try:
|
|
size = round(w['size'] / 1073741824)
|
|
size = str(size) + 'Gb'
|
|
except Exception:
|
|
size = ''
|
|
fs = w['configuration']['mount.fstype']
|
|
state = w['configuration']['state']
|
|
disks[volume_name] = {
|
|
'mount_point': mount_point,
|
|
'size': size,
|
|
'fs': fs,
|
|
'state': state
|
|
}
|
|
except Exception:
|
|
pass
|
|
except Exception:
|
|
pass
|
|
try:
|
|
for q, qval in y.items():
|
|
if isinstance(qval, list):
|
|
for o in qval:
|
|
for w in o['children']:
|
|
if isinstance(w['logicalname'], list):
|
|
volume_name = w['logicalname'][0]
|
|
mount_point = w['logicalname'][1]
|
|
size = round(w['size'] / 1073741824)
|
|
size = str(size) + 'Gb'
|
|
fs = w['configuration']['mount.fstype']
|
|
state = w['configuration']['state']
|
|
disks[volume_name] = {
|
|
'mount_point': mount_point,
|
|
'size': size,
|
|
'fs': fs,
|
|
'state': state
|
|
}
|
|
except Exception:
|
|
pass
|
|
except Exception:
|
|
pass
|
|
|
|
try:
|
|
server_sql.insert_system_info(server_id, os_info, sys_info, cpu, ram, network, disks)
|
|
except Exception as e:
|
|
raise e
|
|
|
|
|
|
def show_system_info(server_ip: str, server_id: int) -> str:
|
|
if not server_sql.is_system_info(server_id):
|
|
try:
|
|
get_system_info(server_ip)
|
|
except Exception as e:
|
|
return f'error: Cannot get system info: {e}'
|
|
try:
|
|
system_info = server_sql.select_one_system_info(server_id)
|
|
except Exception as e:
|
|
return f'Cannot update server info: {e}'
|
|
else:
|
|
system_info = server_sql.select_one_system_info(server_id)
|
|
return render_template('ajax/show_system_info.html', system_info=system_info, server_ip=server_ip, server_id=server_id)
|
|
|
|
|
|
def update_system_info(server_ip: str, server_id: int) -> str:
|
|
server_sql.delete_system_info(server_id)
|
|
|
|
try:
|
|
get_system_info(server_ip)
|
|
system_info = server_sql.select_one_system_info(server_id)
|
|
|
|
return render_template('ajax/show_system_info.html', system_info=system_info, server_ip=server_ip, server_id=server_id)
|
|
except Exception as e:
|
|
return f'error: Cannot update server info: {e}'
|
|
|
|
|
|
def show_firewalld_rules(server_ip) -> str:
|
|
input_chain2 = []
|
|
cmd = "sudo iptables -L INPUT -n --line-numbers|sed 's/ */ /g'|grep -v -E 'Chain|target'"
|
|
cmd1 = "sudo iptables -L IN_public_allow -n --line-numbers|sed 's/ */ /g'|grep -v -E 'Chain|target'"
|
|
cmd2 = "sudo iptables -L OUTPUT -n --line-numbers|sed 's/ */ /g'|grep -v -E 'Chain|target'"
|
|
|
|
try:
|
|
input_chain = ssh_command(server_ip, cmd, raw=1)
|
|
except Exception as e:
|
|
roxywi_common.logging(server_ip, f'error: Cannot get Iptables Input chain: {e}')
|
|
return 'error: Cannot get Iptables Input chain'
|
|
|
|
try:
|
|
in_public_allow = ssh_command(server_ip, cmd1, raw=1)
|
|
except Exception as e:
|
|
roxywi_common.logging(server_ip, f'error: Cannot get Iptables IN_public_allow chain: {e}')
|
|
return 'error: Cannot get Iptables IN_public_allow chain'
|
|
|
|
try:
|
|
output_chain = ssh_command(server_ip, cmd2, raw=1)
|
|
except Exception as e:
|
|
roxywi_common.logging(server_ip, f'error: Cannot get Iptables OUTPUT chain: {e}')
|
|
return 'error: Cannot get Iptables OUTPUT chain'
|
|
|
|
for each_line in input_chain:
|
|
input_chain2.append(each_line.strip('\n'))
|
|
|
|
lang = roxywi_common.get_user_lang_for_flask()
|
|
return render_template('ajax/firewall_rules.html', input_chain=input_chain2, IN_public_allow=in_public_allow, output_chain=output_chain, lang=lang)
|
|
|
|
|
|
def create_server(hostname, ip, group, typeip, enable, master, cred, port, desc, haproxy, nginx, apache, firewall, **kwargs) -> bool:
|
|
if not roxywi_auth.is_admin(level=2, role_id=kwargs.get('role_id')):
|
|
raise Exception('error: not enough permission')
|
|
|
|
if server_sql.add_server(hostname, ip, group, typeip, enable, master, cred, port, desc, haproxy, nginx, apache, firewall):
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
def update_server_after_creating(hostname: str, ip: str, scan_server: int) -> str:
|
|
try:
|
|
checker_sql.insert_new_checker_setting_for_server(ip)
|
|
except Exception as e:
|
|
roxywi_common.logging(f'Cannot insert Checker settings for {hostname}', str(e), roxywi=1)
|
|
raise Exception(f'error: Cannot insert Checker settings for {hostname} {e}')
|
|
|
|
try:
|
|
if scan_server == '1':
|
|
nginx_config_path = sql.get_setting('nginx_config_path')
|
|
haproxy_config_path = sql.get_setting('haproxy_config_path')
|
|
haproxy_dir = sql.get_setting('haproxy_dir')
|
|
apache_config_path = sql.get_setting('apache_config_path')
|
|
keepalived_config_path = sql.get_setting('keepalived_config_path')
|
|
|
|
if is_file_exists(ip, nginx_config_path):
|
|
service_sql.update_nginx(ip)
|
|
|
|
if is_file_exists(ip, haproxy_config_path):
|
|
service_sql.update_haproxy(ip)
|
|
|
|
if is_file_exists(ip, keepalived_config_path):
|
|
service_sql.update_keepalived(ip)
|
|
|
|
if is_file_exists(ip, apache_config_path):
|
|
service_sql.update_apache(ip)
|
|
|
|
if is_file_exists(ip, haproxy_dir + '/waf/bin/modsecurity'):
|
|
waf_sql.insert_waf_metrics_enable(ip, "0")
|
|
waf_sql.insert_waf_rules(ip)
|
|
|
|
if is_service_active(ip, 'firewalld'):
|
|
server_sql.update_firewall(ip)
|
|
|
|
except Exception as e:
|
|
roxywi_common.logging(f'Cannot scan a new server {hostname}', str(e), roxywi=1)
|
|
raise Exception(f'error: Cannot scan a new server {hostname} {e}')
|
|
|
|
try:
|
|
get_system_info(ip)
|
|
except Exception as e:
|
|
roxywi_common.logging(f'Cannot get information from {hostname}', str(e), roxywi=1, login=1)
|
|
raise Exception(f'error: Cannot get information from {hostname} {e}')
|
|
|
|
return 'ok'
|
|
|
|
|
|
def delete_server(server_id: int) -> str:
|
|
server = server_sql.select_servers(id=server_id)
|
|
server_ip = ''
|
|
hostname = ''
|
|
|
|
for s in server:
|
|
hostname = s[1]
|
|
server_ip = s[2]
|
|
|
|
if backup_sql.check_exists_backup(server_ip):
|
|
return 'warning: Delete the backup first'
|
|
if backup_sql.check_exists_s3_backup(server_ip):
|
|
return 'warning: Delete the S3 backup first'
|
|
if server_sql.delete_server(server_id):
|
|
waf_sql.delete_waf_server(server_id)
|
|
ps_sql.delete_port_scanner_settings(server_id)
|
|
waf_sql.delete_waf_rules(server_ip)
|
|
history_sql.delete_action_history(server_id)
|
|
server_sql.delete_system_info(server_id)
|
|
service_sql.delete_service_settings(server_id)
|
|
roxywi_common.logging(server_ip, f'The server {hostname} has been deleted', roxywi=1, login=1)
|
|
return 'Ok'
|
|
|
|
|
|
def server_is_up(server_ip: str) -> str:
|
|
cmd = f'if ping -c 1 -W 1 {server_ip} >> /dev/null; then echo up; else echo down; fi'
|
|
server_status, stderr = subprocess_execute(cmd)
|
|
return server_status[0]
|
|
|
|
|
|
def show_server_services(server_id: int) -> str:
|
|
server = server_sql.select_servers(id=server_id)
|
|
lang = roxywi_common.get_user_lang_for_flask()
|
|
return render_template('ajax/show_server_services.html', server=server, lang=lang)
|
|
|
|
|
|
def change_server_services(server_id: int, server_name: str, server_services: dict) -> str:
|
|
services = service_sql.select_services()
|
|
services_status = {}
|
|
|
|
for k, v in server_services.items():
|
|
for service in services:
|
|
if service.service_id == int(k):
|
|
services_status[service.service_id] = v
|
|
|
|
try:
|
|
if service_sql.update_server_services(server_id, services_status[1], services_status[2], services_status[4], services_status[3]):
|
|
roxywi_common.logging('Roxy-WI server', f'Active services have been updated for {server_name}', roxywi=1, login=1)
|
|
return 'ok'
|
|
except Exception as e:
|
|
return f'error: {e}'
|
|
|
|
|
|
def start_ssh_agent() -> dict:
|
|
"""
|
|
Start SSH agent
|
|
:return: Dict of SSH agent socket and pid
|
|
"""
|
|
agent_settings = {}
|
|
cmd = "ssh-agent -s"
|
|
output, stderr = subprocess_execute(cmd)
|
|
|
|
for out in output:
|
|
if 'SSH_AUTH_SOCK=' in out:
|
|
agent_settings.setdefault('socket', out.split('=')[1].split(';')[0])
|
|
if 'SSH_AGENT_PID=' in out:
|
|
agent_settings.setdefault('pid', out.split('=')[1].split(';')[0])
|
|
if 'error' in stderr:
|
|
raise Exception(f'error: Cannot start SSH agent: {stderr}')
|
|
return agent_settings
|
|
|
|
|
|
def add_key_to_agent(ssh_settings: dict, agent_pid: dict) -> None:
|
|
"""
|
|
Add key to SSH agent
|
|
:return: None
|
|
"""
|
|
cmd = f'export SSH_AGENT_PID={agent_pid["pid"]} && export SSH_AUTH_SOCK={agent_pid["socket"]} && '
|
|
|
|
if ssh_settings['passphrase']:
|
|
cmd += f"{{ sleep .1; echo {ssh_settings['passphrase']}; }} | script -q /dev/null -c 'ssh-add {ssh_settings['key']}'"
|
|
else:
|
|
cmd += f'ssh-add {ssh_settings["key"]}'
|
|
output, stderr = subprocess_execute(cmd)
|
|
if 'error' in stderr:
|
|
raise Exception(f'error: Cannot add the key {ssh_settings["key"]} to SSH agent: {stderr}')
|
|
|
|
|
|
def stop_ssh_agent(agent_pid: dict) -> None:
|
|
"""
|
|
Stop SSH agent
|
|
:return: None
|
|
"""
|
|
|
|
cmd = f'export SSH_AGENT_PID={agent_pid["pid"]} && ssh-agent -k'
|
|
output, stderr = subprocess_execute(cmd)
|
|
|
|
if 'error' in stderr:
|
|
raise Exception(f'error: Cannot stop SSH agent: {stderr}')
|