From 89022e59beeebb6e4510f47a24fe25a8964d975f Mon Sep 17 00:00:00 2001 From: Aidaho Date: Mon, 19 Aug 2024 11:49:19 +0300 Subject: [PATCH] v8.0: Add port scanner functionality and refactor codebase Integrated port scanner endpoints to manage and retrieve scanner configurations. Removed unused imports and redundant code blocks across multiple files for better performance and readability. Simplified error handling and consolidated port scanner settings operations. --- README.md | 1 - app/api/routes/routes.py | 5 + app/modules/common/common_classes.py | 1 + app/modules/db/portscanner.py | 50 +++----- app/modules/db/server.py | 5 +- app/modules/roxywi/class_models.py | 8 +- app/modules/tools/alerting.py | 12 +- app/routes/admin/routes.py | 1 - app/routes/channel/routes.py | 1 + app/routes/portscanner/routes.py | 20 +-- app/views/server/views.py | 4 +- app/views/tools/port_scanner_views.py | 176 ++++++++++++++++++++++++++ 12 files changed, 219 insertions(+), 65 deletions(-) create mode 100644 app/views/tools/port_scanner_views.py diff --git a/README.md b/README.md index b90bf7f3..99d5459d 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,6 @@ Web interface (user-friendly web GUI, alerting, monitoring and secure) for manag 35. Mobile-ready design 36. [SMON](https://roxy-wi.org/services/smon) (Check: Ping, TCP/UDP, HTTP(s), SSL expiry, HTTP body answer, DNS records, Status pages) 37. Backup HAProxy, Nginx, Apache and Keepalived config files through Roxy-WI -38. Managing OpenVPN3 as a client via Roxy-WI diff --git a/app/api/routes/routes.py b/app/api/routes/routes.py index 4f24da3f..268c9315 100644 --- a/app/api/routes/routes.py +++ b/app/api/routes/routes.py @@ -14,6 +14,7 @@ from app.views.user.views import UserView, UserGroupView, UserRoles from app.views.udp.views import UDPListener, UDPListeners, UDPListenerActionView from app.views.channel.views import ChannelView, ChannelsView from app.views.tools.views import CheckerView +from app.views.tools.port_scanner_views import PortScannerView, PortScannerPortsView from app.views.admin.views import SettingsView from app.modules.roxywi.class_models import LoginRequest import app.modules.roxywi.auth as roxywi_auth @@ -67,6 +68,10 @@ bp.add_url_rule('/server//ip', view_func=ServerIPView.as_view('server bp.add_url_rule('/server//ip', view_func=ServerIPView.as_view('server_ip'), methods=['GET']) register_api(CredView, 'cred', '/server/cred', 'cred_id') bp.add_url_rule('/server/creds', view_func=CredsView.as_view('creds'), methods=['GET']) +bp.add_url_rule('/server/portscanner/', view_func=PortScannerView.as_view('port_scanner_ip'), methods=['GET', 'POST']) +bp.add_url_rule('/server/portscanner/', view_func=PortScannerView.as_view('port_scanner'), methods=['GET', 'POST']) +bp.add_url_rule('/server/portscanner//ports', view_func=PortScannerPortsView.as_view('port_scanner_ports_ip'), methods=['GET']) +bp.add_url_rule('/server/portscanner//ports', view_func=PortScannerPortsView.as_view('port_scanner_ports'), methods=['GET']) bp.add_url_rule('/servers', view_func=ServersView.as_view('servers'), methods=['GET']) register_api(ServerGroupView, 'group', '/group', 'group_id') diff --git a/app/modules/common/common_classes.py b/app/modules/common/common_classes.py index 2e0b3182..1cb6710e 100644 --- a/app/modules/common/common_classes.py +++ b/app/modules/common/common_classes.py @@ -7,6 +7,7 @@ import app.modules.roxywi.common as roxywi_common from app.modules.roxywi.class_models import ServerRequest, GroupQuery, CredRequest, ChannelRequest from app.middleware import get_user_params + class SupportClass: def __init__(self, is_id=True): self.is_id = is_id diff --git a/app/modules/db/portscanner.py b/app/modules/db/portscanner.py index ce88bef5..39babf46 100644 --- a/app/modules/db/portscanner.py +++ b/app/modules/db/portscanner.py @@ -1,6 +1,7 @@ -from app.modules.db.db_model import connect, fn, PortScannerPorts, PortScannerSettings, PortScannerHistory +from app.modules.db.db_model import fn, PortScannerPorts, PortScannerSettings, PortScannerHistory from app.modules.db.common import out_error import app.modules.roxy_wi_tools as roxy_wi_tools +from app.modules.roxywi.exception import RoxywiResourceNotFound def delete_port_scanner_settings(server_id): @@ -25,14 +26,20 @@ def select_port_scanner_settings(user_group): return query_res -def select_port_scanner_settings_for_service(): - query = PortScannerSettings.select() +def get_port_scanner_settings(server_id: int) -> PortScannerSettings: try: - query_res = query.execute() + return PortScannerSettings.get(PortScannerSettings.server_id == server_id) + except PortScannerSettings.DoesNotExist: + raise RoxywiResourceNotFound + except Exception as e: + return out_error(e) + + +def select_port_scanner_settings_for_service(): + try: + return PortScannerSettings.select().execute() except Exception as e: out_error(e) - else: - return query_res def insert_port_scanner_port(serv, user_group_id, port, service_name): @@ -48,17 +55,10 @@ def insert_port_scanner_port(serv, user_group_id, port, service_name): def select_ports(serv): - conn = connect() - cursor = conn.cursor() - sql = """select port from port_scanner_ports where serv = '%s' """ % serv - try: - cursor.execute(sql) + return PortScannerPorts.select(PortScannerPorts.port).where(PortScannerPorts.serv == serv).execute() except Exception as e: out_error(e) - else: - conn.close() - return cursor.fetchall() def select_port_name(serv, port): @@ -74,9 +74,8 @@ def select_port_name(serv, port): def delete_ports(serv): - query = PortScannerPorts.delete().where(PortScannerPorts.serv == serv) try: - query.execute() + PortScannerPorts.delete().where(PortScannerPorts.serv == serv).execute() except Exception as e: out_error(e) @@ -96,23 +95,11 @@ def insert_port_scanner_settings(server_id, user_group_id, enabled, notify, hist try: PortScannerSettings.insert( server_id=server_id, user_group_id=user_group_id, enabled=enabled, notify=notify, history=history - ).execute() - return True - except Exception: - return False - - -def update_port_scanner_settings(server_id, user_group_id, enabled, notify, history): - query = PortScannerSettings.update( - user_group_id=user_group_id, enabled=enabled, notify=notify, history=history - ).where(PortScannerSettings.server_id == server_id) - try: - query.execute() + ).on_conflict('replace').execute() except Exception as e: out_error(e) - def select_count_opened_ports(serv): query = PortScannerPorts.select( PortScannerPorts.date, fn.Count(PortScannerPorts.port).alias('count') @@ -140,10 +127,7 @@ def delete_portscanner_history(keep_interval: int): def select_port_scanner_history(serv): - query = PortScannerHistory.select().where(PortScannerHistory.serv == serv) try: - query_res = query.execute() + return PortScannerHistory.select().where(PortScannerHistory.serv == serv).execute() except Exception as e: out_error(e) - else: - return query_res diff --git a/app/modules/db/server.py b/app/modules/db/server.py index 5d83c721..3de62292 100644 --- a/app/modules/db/server.py +++ b/app/modules/db/server.py @@ -111,12 +111,9 @@ def is_system_info(server_id): def select_os_info(server_id): try: - query_res = SystemInfo.get(SystemInfo.server_id == server_id).os_info + return SystemInfo.get(SystemInfo.server_id == server_id).os_info except Exception as e: out_error(e) - return - else: - return query_res def update_firewall(serv): diff --git a/app/modules/roxywi/class_models.py b/app/modules/roxywi/class_models.py index 815d7de8..4c2a976d 100644 --- a/app/modules/roxywi/class_models.py +++ b/app/modules/roxywi/class_models.py @@ -4,7 +4,7 @@ from typing import Optional, Annotated, Union, Literal, Any, Dict, List from shlex import quote from pydantic_core import CoreSchema, core_schema -from pydantic import BaseModel, Base64Str, StringConstraints, IPvAnyAddress, AnyUrl, root_validator, GetCoreSchemaHandler +from pydantic import BaseModel, Base64Str, StringConstraints, IPvAnyAddress, GetCoreSchemaHandler DomainName = Annotated[str, StringConstraints(pattern=r"^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z][a-z0-9-]{0,61}[a-z0-9]$")] @@ -263,3 +263,9 @@ class GitBackupRequest(BaseModel): time: Optional[EscapedString] = 'weekly' cred_id: Optional[int] = None description: Optional[EscapedString] = None + + +class PortScannerRequest(BaseModel): + enabled: Optional[bool] = 1 + history: Optional[bool] = 1 + notify: Optional[bool] = 1 diff --git a/app/modules/tools/alerting.py b/app/modules/tools/alerting.py index db9e26b5..83cfc182 100644 --- a/app/modules/tools/alerting.py +++ b/app/modules/tools/alerting.py @@ -5,7 +5,7 @@ import pdpyras import requests import telebot from telebot import apihelper -from flask import render_template, request, abort, g +from flask import render_template, abort, g import app.modules.db.sql as sql import app.modules.db.user as user_sql @@ -220,8 +220,7 @@ def slack_send_mess(mess, level, **kwargs): channel_name = slack.chanel_name if proxy is not None and proxy != '' and proxy != 'None': - proxies = dict(https=proxy, http=proxy) - client = WebClient(token=slack_token, proxies=proxies) + client = WebClient(token=slack_token, proxy=proxy) else: client = WebClient(token=slack_token) @@ -331,12 +330,7 @@ def mm_send_mess(mess, level, server_ip=None, service_id=None, alert_type=None, def check_rabbit_alert() -> None: try: - user_group_id = request.cookies.get('group') - except Exception as e: - raise Exception(f'Cannot get user group {e}') - - try: - json_for_sending = {"user_group": user_group_id, "message": 'info: Test message'} + json_for_sending = {"user_group": g.user_params['group_id'], "message": 'info: Test message'} send_message_to_rabbit(json.dumps(json_for_sending)) except Exception as e: raise Exception(f'Cannot send message {e}') diff --git a/app/routes/admin/routes.py b/app/routes/admin/routes.py index b6330c06..d8b9a490 100644 --- a/app/routes/admin/routes.py +++ b/app/routes/admin/routes.py @@ -5,7 +5,6 @@ from flask_jwt_extended import jwt_required from app import scheduler from app.routes.admin import bp import app.modules.db.sql as sql -import app.modules.db.cred as cred_sql import app.modules.db.user as user_sql import app.modules.db.group as group_sql import app.modules.db.server as server_sql diff --git a/app/routes/channel/routes.py b/app/routes/channel/routes.py index a7dd5fd9..47b74e0b 100644 --- a/app/routes/channel/routes.py +++ b/app/routes/channel/routes.py @@ -42,6 +42,7 @@ def load_channels(): @bp.post('/check') +@get_user_params() def check_sender(): json_data = request.get_json() sender = json_data.get('sender') diff --git a/app/routes/portscanner/routes.py b/app/routes/portscanner/routes.py index 5282953a..7119150e 100644 --- a/app/routes/portscanner/routes.py +++ b/app/routes/portscanner/routes.py @@ -5,7 +5,6 @@ from app.routes.portscanner import bp from app.middleware import get_user_params import app.modules.db.server as server_sql import app.modules.db.portscanner as ps_sql -import app.modules.common.common as common import app.modules.server.server as server_mod import app.modules.roxywi.common as roxywi_common import app.modules.tools.common as tools_common @@ -60,22 +59,17 @@ def portscanner_history(server_ip): @bp.post('/settings') def change_settings_portscanner(): - server_id = common.checkAjaxInput(request.form.get('server_id')) - enabled = common.checkAjaxInput(request.form.get('enabled')) - notify = common.checkAjaxInput(request.form.get('notify')) - history = common.checkAjaxInput(request.form.get('history')) - user_group_id = [server[3] for server in server_sql.select_servers(id=server_id)] + server_id = int(request.form.get('server_id')) + enabled = int(request.form.get('enabled')) + notify = int(request.form.get('notify')) + history = int(request.form.get('history')) + server = server_sql.get_server_by_id(server_id) try: - if ps_sql.insert_port_scanner_settings(server_id, user_group_id[0], enabled, notify, history): - return 'ok' - else: - if ps_sql.update_port_scanner_settings(server_id, user_group_id[0], enabled, notify, history): - return 'ok' + ps_sql.insert_port_scanner_settings(server_id, server.group_id, enabled, notify, history) + return 'ok' except Exception as e: return f'error: Cannot save settings: {e}' - else: - return 'ok' @bp.post('/scan') diff --git a/app/views/server/views.py b/app/views/server/views.py index 8a956d7c..6fffc670 100644 --- a/app/views/server/views.py +++ b/app/views/server/views.py @@ -12,9 +12,7 @@ import app.modules.roxywi.common as roxywi_common import app.modules.server.server as server_mod from app.middleware import get_user_params, page_for_admin, check_group from app.modules.roxywi.exception import RoxywiResourceNotFound -from app.modules.roxywi.class_models import ( - BaseResponse, IdResponse, IdDataResponse, ServerRequest, GroupQuery, GroupRequest, CredRequest, CredUploadRequest -) +from app.modules.roxywi.class_models import BaseResponse, IdResponse, IdDataResponse, ServerRequest, GroupQuery, GroupRequest from app.modules.common.common_classes import SupportClass diff --git a/app/views/tools/port_scanner_views.py b/app/views/tools/port_scanner_views.py new file mode 100644 index 00000000..ecb766e0 --- /dev/null +++ b/app/views/tools/port_scanner_views.py @@ -0,0 +1,176 @@ +from typing import Union + +from ansible.module_utils.common.text.converters import jsonify +from flask.views import MethodView +from flask_pydantic import validate +from flask_jwt_extended import jwt_required +from playhouse.shortcuts import model_to_dict + +import app.modules.db.server as server_sql +import app.modules.db.portscanner as ps_sql +import app.modules.roxywi.common as roxywi_common +from app.middleware import get_user_params, page_for_admin, check_group +from app.modules.roxywi.class_models import PortScannerRequest, BaseResponse +from app.modules.db.db_model import PortScannerSettings, PortScannerHistory +from app.modules.common.common_classes import SupportClass + + +class PortScannerView(MethodView): + methods = ['GET', 'POST'] + decorators = [jwt_required(), get_user_params(), page_for_admin(level=3), check_group()] + + @staticmethod + def get(server_id: Union[int, str]): + """ + Port scanner operations for managing and retrieving scanner configurations. + --- + tags: + - Port Scanner + parameters: + - in: path + name: server_id + type: integer + required: true + description: ID or IP address of the server. + responses: + 200: + description: A JSON object containing the port scanner settings. + schema: + type: object + properties: + enabled: + type: boolean + description: Indicates if the port scanner is enabled. + history: + type: boolean + description: Indicates if the port scanner keeps a history. + notify: + type: boolean + description: Indicates if notifications are enabled for the port scanner. + 404: + description: Server not found. + 500: + description: An error occurred while retrieving the port scanner settings. + """ + try: + server_id = SupportClass().return_server_ip_or_id(server_id) + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot find server') + + try: + ps_settings = ps_sql.get_port_scanner_settings(server_id) + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot get Portscanner settings') + + return jsonify(model_to_dict(ps_settings, recurse=False, exclude=[PortScannerSettings.user_group_id])) + + + @validate(body=PortScannerRequest) + def post(self, server_id: Union[int, str], body: PortScannerRequest): + """ + Updates the port scanner settings for a specific server. + --- + tags: + - Port Scanner + parameters: + - in: path + name: server_id + type: integer + required: true + description: The ID or IP of the server + - in: body + name: body + required: true + schema: + type: object + properties: + enabled: + type: boolean + description: Indicates if the port scanner is enabled + history: + type: boolean + description: Indicates if the port scanner keeps a history + notify: + type: boolean + description: Indicates if notifications are enabled for the port scanner + responses: + 201: + description: Successfully updated the port scanner settings + default: + description: Unexpected error + """ + try: + server_id = SupportClass().return_server_ip_or_id(server_id) + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot find server') + + try: + server = server_sql.get_server_by_id(server_id) + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot find server') + + try: + ps_sql.insert_port_scanner_settings(server_id, server.group_id, body.enabled, body.notify, body.history) + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot insert Portscanner settings') + + return BaseResponse().model_dump(), 201 + + +class PortScannerPortsView(MethodView): + methods = ['GET'] + decorators = [jwt_required(), get_user_params(), page_for_admin(level=3), check_group()] + + @staticmethod + def get(server_id: Union[int, str]): + """ + Port scanner ports information retrieval for a specific server. + --- + description: Retrieves the list of open ports and their details for a specific server. + tags: + - Port Scanner + parameters: + - in: path + name: server_id + type: integer + required: true + description: ID or IP address of the server. + responses: + 200: + description: A JSON array containing details of open ports. + schema: + type: array + items: + type: object + properties: + port: + type: integer + description: The port number. + status: + type: string + description: The status of the port, e.g., "opened". + service_name: + type: string + description: The name of the service running on the port. + date: + type: string + format: date-time + description: The date and time when the port was last checked. + 404: + description: Server not found. + 500: + description: An error occurred while retrieving the port scanner information. + """ + try: + server_ip = SupportClass(False).return_server_ip_or_id(server_id) + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot find server') + + try: + ports = ps_sql.select_port_scanner_history(server_ip) + ports_list = [] + for port in ports: + ports_list.append(model_to_dict(port, exclude=[PortScannerHistory.serv])) + return jsonify(ports_list) + except Exception as e: + return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot get Portscanner history')