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