Files
haproxy-wi/app/modules/server/server.py
Aidaho 04e39325c8 v7.2.4.0: Update date calculation to use UTC and increase version number
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'.
2024-04-17 17:03:42 +03:00

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}')