From 2fa880578a90b75c168de71c2805600e06984c5e Mon Sep 17 00:00:00 2001 From: Aidaho Date: Sun, 20 Oct 2024 12:13:37 +0300 Subject: [PATCH] v8.1.0.1: Fix numeration and refactor code for consistency Correct feature list numeration in README.md and improve JavaScript variable naming and data type consistency. Also enhance HAProxy section handling in templates and API endpoints to ensure better error handling and data processing. --- README.md | 70 +++++++++---------- app/modules/roxywi/class_models.py | 28 ++++---- app/modules/service/installation.py | 3 +- .../haproxy_section/templates/section.j2 | 26 +++---- app/static/js/add.js | 2 +- app/static/js/edit_config.js | 30 ++++---- app/views/service/haproxy_section_views.py | 70 ++++++++++--------- 7 files changed, 117 insertions(+), 112 deletions(-) diff --git a/README.md b/README.md index 4b516d2..57fd79d 100644 --- a/README.md +++ b/README.md @@ -12,39 +12,39 @@ Web interface (user-friendly web GUI, alerting, monitoring and secure) for manag # Features: 1. Installing and updating HAProxy, Nginx, Apache and Keepalived with Roxy-WI as a system service 2. Installing and updating HAProxy and Nginx with Roxy-WI as a Docker service -4. Installing and updating HAProxy, Nginx, Apache, Keepalived and Node exporters with Roxy-WI -6. Downloading, updating and formatting GeoIP to the acceptable format for HAProxy with Roxy-WI -7. Dynamic change of Maxconn, Black/white lists, add, edit or delete backend's IP address and port with saving changes to the config file -8. Configuring HAProxy, Nginx, Apache and Keepalived in a jiffy with Roxy-WI -9. Viewing and analysing the status of all Frontend/backend servers via Roxy-WI from a single control panel -10. Enabling/disabling servers through stats page without rebooting HAProxy -11. Viewing/Analysing HAProxy, Nginx, Apache and Keepalived logs right from the Roxy-WI web interface -12. Creating and visualizing the HAProxy workflow from Web Ui -13. Pushing Your changes to your HAProxy, Nginx, Apache and Keepalived servers with a single click via the web interface -14. Getting info on past changes, evaluating your config files and restoring the previous stable config at any time with a single click right from Web interface -15. Adding/Editing Frontend or backend servers via the web interface with a click -16. Editing the config of HAProxy, Nginx, Apache and Keepalived and push interchanges to All Master/Slave servers by a single click -17. Adding Multiple servers to ensure the Config Sync between servers -18. Managing the ports assigned to Frontend automatically -19. Evaluating the changes of recent configs pushed to HAProxy, Nginx, Apache and Keepalived instances right from the Web UI -20. Multiple User Roles support for privileged based Viewing and editing of Config -21. Creating Groups and adding/removing servers to ensure the proper identification for your HAProxy, Nginx and Apache Clusters -22. Sending notifications from Roxy-WI via Telegram, Slack, Email, PageDuty and via the web interface -23. Supporting high Availability to ensure uptime to all Master slave servers configured -24. Support of SSL (including Let's Encrypt) -25. Support of SSH Key for managing multiple HAProxy, Nginx, Apache and Keepalived Servers straight from Roxy-WI -26. SYN flood protect -27. Alerting about changes of the state of HAProxy backends, about approaching the limit of Maxconn -28. Alerting about the state of HAProxy, Nginx, Apache and Keepalived service -29. Gathering metrics for incoming connections -30. Web acceleration settings -31. Firewall for web application (WAF) -32. LDAP support -33. Keep active HAProxy, Nginx, Apache and Keepalived services -34. Possibility to hide parts of the config with tags for users with "guest" role: "HideBlockStart" and "HideBlockEnd" -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 +3. Installing and updating HAProxy, Nginx, Apache, Keepalived and Node exporters with Roxy-WI +4. Downloading, updating and formatting GeoIP to the acceptable format for HAProxy with Roxy-WI +5. Dynamic change of Maxconn, Black/white lists, add, edit or delete backend's IP address and port with saving changes to the config file +6. Configuring HAProxy, Nginx, Apache and Keepalived in a jiffy with Roxy-WI +7. Viewing and analysing the status of all Frontend/backend servers via Roxy-WI from a single control panel +8. Enabling/disabling servers through stats page without rebooting HAProxy +9. Viewing/Analysing HAProxy, Nginx, Apache and Keepalived logs right from the Roxy-WI web interface +10. Creating and visualizing the HAProxy workflow from Web Ui +11. Pushing Your changes to your HAProxy, Nginx, Apache and Keepalived servers with a single click via the web interface +12. Getting info on past changes, evaluating your config files and restoring the previous stable config at any time with a single click right from Web interface +13. Adding/Editing Frontend or backend servers via the web interface with a click +14. Editing the config of HAProxy, Nginx, Apache and Keepalived and push interchanges to All Master/Slave servers by a single click +15. Adding Multiple servers to ensure the Config Sync between servers +16. Managing the ports assigned to Frontend automatically +17. Evaluating the changes of recent configs pushed to HAProxy, Nginx, Apache and Keepalived instances right from the Web UI +18. Multiple User Roles support for privileged based Viewing and editing of Config +19. Creating Groups and adding/removing servers to ensure the proper identification for your HAProxy, Nginx and Apache Clusters +20. Sending notifications from Roxy-WI via Telegram, Slack, Email, PageDuty and via the web interface +21. Supporting high Availability to ensure uptime to all Master slave servers configured +22. Support of SSL (including Let's Encrypt) +23. Support of SSH Key for managing multiple HAProxy, Nginx, Apache and Keepalived Servers straight from Roxy-WI +24. SYN flood protect +25. Alerting about changes of the state of HAProxy backends, about approaching the limit of Maxconn +26. Alerting about the state of HAProxy, Nginx, Apache and Keepalived service +27. Gathering metrics for incoming connections +28. Web acceleration settings +29. Firewall for web application (WAF) +30. LDAP support +31. Keep active HAProxy, Nginx, Apache and Keepalived services +32. Possibility to hide parts of the config with tags for users with "guest" role: "HideBlockStart" and "HideBlockEnd" +33. Mobile-ready design +34. [SMON](https://roxy-wi.org/services/smon) (Check: Ping, TCP/UDP, HTTP(s), SSL expiry, HTTP body answer, DNS records, Status pages) +35. Backup HAProxy, Nginx, Apache and Keepalived config files through Roxy-WI @@ -60,10 +60,6 @@ Web interface (user-friendly web GUI, alerting, monitoring and secure) for manag ### Read instruction on the official [site](https://roxy-wi.org/installation#deb) -## Manual install - -### Read instruction on the official [site](https://roxy-wi.org/installation#manual) - # OS support Roxy-WI supports the following OSes: 1. EL7(RPM installation and manual installation). It must be "Infrastructure Server" at least. x86_64 only diff --git a/app/modules/roxywi/class_models.py b/app/modules/roxywi/class_models.py index 1b9e002..0039be6 100644 --- a/app/modules/roxywi/class_models.py +++ b/app/modules/roxywi/class_models.py @@ -331,7 +331,7 @@ class HaproxyBackendServer(BaseModel): class HaproxyCookie(BaseModel): dynamic: str - dynamicKey: str + dynamic_key: str domain: Optional[str] = None name: Optional[str] = None nocache: Optional[str] = None @@ -371,25 +371,25 @@ class HaproxyConfigRequest(BaseModel): name: EscapedString option: Optional[str] = None maxconn: Optional[int] = 2000 - waf: Optional[bool] = 0 - binds: List[HaproxyBinds] - headers: List[HaproxyHeaders] = None - acls: List[HaproxyAcls] = None - backend_servers: List[HaproxyBackendServer] = None + waf: Optional[bool] = False + binds: Optional[List[HaproxyBinds]] = None + headers: Optional[List[HaproxyHeaders]] = None + acls: Optional[List[HaproxyAcls]] = None + backend_servers: Optional[List[HaproxyBackendServer]] = None blacklist: Optional[str] = '' whitelist: Optional[str] = '' ssl: Optional[HaproxySSL] = None - cache: Optional[bool] = 0 - compression: Optional[bool] = 0 + cache: Optional[bool] = False + compression: Optional[bool] = False cookie: Optional[HaproxyCookie] = None health_check: Optional[HaproxyHealthCheck] = None servers_check: Optional[HaproxyServersCheck] = None - ssl_offloading: Optional[bool] = 0 - redispatch: Optional[bool] = 0 - forward_for: Optional[bool] = 0 - slow_attack: Optional[bool] = 0 - ddos: Optional[bool] = 0 - antibot: Optional[bool] = 0 + ssl_offloading: Optional[bool] = False + redispatch: Optional[bool] = False + forward_for: Optional[bool] = False + slow_attack: Optional[bool] = False + ddos: Optional[bool] = False + antibot: Optional[bool] = False backends: Optional[str] = None circuit_breaking: Optional[HaproxyCircuitBreaking] = None action: Optional[Literal['save', 'test', 'reload', 'restart']] = "save" diff --git a/app/modules/service/installation.py b/app/modules/service/installation.py index aab1083..1a90caa 100644 --- a/app/modules/service/installation.py +++ b/app/modules/service/installation.py @@ -1,5 +1,6 @@ import os import json +import random from typing import Union from packaging import version @@ -328,7 +329,7 @@ def run_ansible(inv: dict, server_ips: list, ansible_role: str) -> dict: def run_ansible_locally(inv: dict, ansible_role: str) -> dict: inventory_path = '/var/www/haproxy-wi/app/scripts/ansible/inventory' - inventory = f'{inventory_path}/{ansible_role}.json' + inventory = f'{inventory_path}/{ansible_role}-{random.randint(0, 35)}.json' # proxy = sql.get_setting('proxy') # proxy_serv = '' diff --git a/app/scripts/ansible/roles/haproxy_section/templates/section.j2 b/app/scripts/ansible/roles/haproxy_section/templates/section.j2 index cc97d40..e9030cd 100644 --- a/app/scripts/ansible/roles/haproxy_section/templates/section.j2 +++ b/app/scripts/ansible/roles/haproxy_section/templates/section.j2 @@ -1,8 +1,10 @@ {{ config.type }} {{ config.name }} + {% if config.binds != 'None' -%} {% for bind in config.binds -%} bind {{ bind.ip }}:{{ bind.port }} {% if config.ssl != 'None' and config.mode == 'http' and config.ssl.cert %} ssl crt {{cert_path}}/{{ config.ssl.cert }}{% endif %} {% endfor %} + {% endif %} mode {{ config.mode }} {% if config.balance != 'None' -%} @@ -24,16 +26,16 @@ {% endfor %} {% endif %} - {% if config.whitelist -%} - acl white_list_{{ whitelist }} src -f {{ haproxy_dir }}/white/{{ config.whitelist }} - tcp-request content accept if white_list_{{ whitelist }} + {% if config.whitelist and config.whitelist != 'None' -%} + acl white_list_{{ config.whitelist }} src -f {{ haproxy_dir }}/white/{{ config.whitelist }} + tcp-request content accept if white_list_{{ config.whitelist }} tcp-request content reject {% endif %} - {% if config.blacklist -%} + {% if config.blacklist and config.blacklist != 'None' -%} tcp-request connection reject if { src -f {{ haproxy_dir }}/white/{{ config.blacklist }} } {% endif %} - {% if config.acls -%} + {% if config.acls != 'None' -%} {% for acl in config.acls -%} {% if acl.acl_if in (1, 2) -%} {% if config.mode == 'tcp' -%} @@ -66,7 +68,7 @@ {% if config.redispatch -%} {{ redispatch }} {% endif -%} - {% if config.ssl_offloading -%} + {% if config.ssl_offloading and config.ssl_offloading != 'None' -%} {{ ssl_offloading }} {% endif -%} @@ -93,8 +95,8 @@ {% if config.cookie != 'None' -%} cookie {{ config.cookie.name }} {% if config.cookie.domain != 'None' %} {{ config.cookie.domain }}{% endif %} {{ config.cookie.rewrite }} {{ config.cookie.prefix }} {{ config.cookie.nocache }} {{ config.cookie.postonly }} {{ config.cookie.dynamic }} - {% if config.cookie.dynamicKey -%} - dynamic-cookie-key {{ config.cookie.dynamicKey }} + {% if config.cookie.dynamic_key -%} + dynamic-cookie-key {{ config.cookie.dynamic_key }} {% endif -%} {% endif -%} @@ -110,12 +112,10 @@ {% if config.servers_check != 'None' and config.servers_check.check_enabled -%} {%- set check_option = ' check inter ' + config.servers_check.inter|string() + ' rise ' + config.servers_check.rise|string() + ' fall ' + config.servers_check.fall|string() %} {% else -%} - {% if config.servers_check != 'None' and config.servers_check.check_enabled == 'None' -%} - {% set check_option = '' -%} - {% endif -%} + {% set check_option = '' -%} {% endif -%} - {% if config.option != '' -%} + {% if config.option != '' and config.option != 'None' -%} {% for o in config.option.split('\\r\\n') -%} {{ o }} {% endfor -%} @@ -130,10 +130,12 @@ default-server observe {{ config.circuit_breaking.observe }} error-limit {{ config.circuit_breaking.error_limit }} on-error {{ config.circuit_breaking.on_error }} {% endif -%} + {% if config.backend_servers != 'None' -%} {% for backend in config.backend_servers -%} server {{ backend.server }} {{ backend.server }}:{{ backend.port }} port {{ backend.port_check }} {{ check_option }} {{ ssl_check_option }} maxconn {{ backend.maxconn }}{% if backend.send_proxy %} send-proxy{% endif %}{% if backend.backup %} backup {% endif %} {% endfor -%} + {% endif -%} {% if config.backends and config.backends != 'None' -%} use_backend {{ config.backends }} diff --git a/app/static/js/add.js b/app/static/js/add.js index 5a05494..78aed3d 100644 --- a/app/static/js/add.js +++ b/app/static/js/add.js @@ -697,7 +697,7 @@ $( function() { } for (let section_type of ['listen', 'backend']) { $("#" + section_type + "_checks").on('selectmenuchange', function () { - let health_check_val = health_check_val; + let health_check_val = $("#" + section_type + "_checks").val(); if (health_check_val === "tcp-check") { $("#" + section_type + "_checks_note").html(tcp_note) } diff --git a/app/static/js/edit_config.js b/app/static/js/edit_config.js index 6951ef6..ec79faf 100644 --- a/app/static/js/edit_config.js +++ b/app/static/js/edit_config.js @@ -430,12 +430,12 @@ function getFormData($form, form_name) { let name = $('input[name="cookie_name"]').val(); let domain = $('input[name="cookie_domain"]').val(); let dynamic = $('input[name="dynamic"]').val(); - let dynamicKey = $('input[name="dynamic-cookie-key"]').val(); + let dynamic_key = $('input[name="dynamic-cookie-key"]').val(); let nocache = $('input[name="nocache"]').val(); let postonly = $('input[name="postonly"]').val(); let rewrite = $('select[name="rewrite"] option:selected').val(); let prefix = $('input[name="prefix"]').val(); - indexed_array['cookie'] = {name, domain, dynamic, dynamicKey, nocache, postonly, rewrite, prefix} + indexed_array['cookie'] = {name, domain, dynamic, dynamic_key, nocache, postonly, rewrite, prefix} } } else if (n['name'] === 'whitelist_checkbox') { if ($('input[name="whitelist_checkbox"]').is(':checked')) { @@ -444,11 +444,11 @@ function getFormData($form, form_name) { } else if (n['name'] === 'ssl') { if ($('input[name="ssl"]').is(':checked')) { let cert = $('input[name="cert"]').val(); - let ssl_check_backend = 1; + let ssl_check_backend = true; if ($('input[name="ssl-check"]').is(':checked')) { ssl_check_backend = 0; } else { - ssl_check_backend = 1; + ssl_check_backend = true; } indexed_array['ssl'] = {cert, ssl_check_backend}; } @@ -466,31 +466,31 @@ function getFormData($form, form_name) { } } else if (n['name'] === 'ssl_offloading') { if ($('input[name="ssl_offloading"]').is(':checked')) { - indexed_array['ssl_offloading'] = 1; + indexed_array['ssl_offloading'] = true; } } else if (n['name'] === 'forward_for') { if ($('input[name="forward_for"]').is(':checked')) { - indexed_array['forward_for'] = 1; + indexed_array['forward_for'] = true; } } else if (n['name'] === 'redispatch') { if ($('input[name="redispatch"]').is(':checked')) { - indexed_array['redispatch'] = 1; + indexed_array['redispatch'] = true; } } else if (n['name'] === 'slow_attack') { if ($('input[name="slow_attack"]').is(':checked')) { - indexed_array['slow_attack'] = 1; + indexed_array['slow_attack'] = true; } } else if (n['name'] === 'ddos') { if ($('input[name="ddos"]').is(':checked')) { - indexed_array['ddos'] = 1; + indexed_array['ddos'] = true; } } else if (n['name'] === 'antibot') { if ($('input[name="antibot"]').is(':checked')) { - indexed_array['antibot'] = 1; + indexed_array['antibot'] = true; } } else if (n['name'] === 'cache') { if ($('input[name="cache"]').is(':checked')) { - indexed_array['cache'] = 1; + indexed_array['cache'] = true; } } else if (n['name'] === 'circuit_breaking') { if ($('input[name="circuit_breaking"]').is(':checked')) { @@ -501,7 +501,7 @@ function getFormData($form, form_name) { } } else if (n['name'] === 'check-servers') { if ($('input[name="check-servers"]').is(':checked')) { - let check_enabled = 1; + let check_enabled = true; let inter = $('select[name="inter"] option:selected').val(); let rise = $('select[name="rise"] option:selected').val(); let fall = $('select[name="fall"] option:selected').val(); @@ -534,7 +534,7 @@ function getFormData($form, form_name) { } } else if (n['name'] === 'daemon') { if ($('input[name="daemon"]').is(':checked')) { - indexed_array['daemon'] = 1; + indexed_array['daemon'] = true; } } else { indexed_array[n['name']] = n['value']; @@ -607,10 +607,10 @@ function getFormData($form, form_name) { let send_proxy = 0; let backup = 0; if ($(this).children().children('input[name="send_proxy"]').is(':checked')) { - send_proxy = 1; + send_proxy = true; } if ($(this).children().children('input[name="backup"]').is(':checked')) { - backup = 1; + backup = true; } let test_var = {server, port, port_check, maxconn, send_proxy, backup}; indexed_array['backend_servers'].push(test_var); diff --git a/app/views/service/haproxy_section_views.py b/app/views/service/haproxy_section_views.py index aa2cf8b..50632c7 100644 --- a/app/views/service/haproxy_section_views.py +++ b/app/views/service/haproxy_section_views.py @@ -16,7 +16,8 @@ import app.modules.roxywi.common as roxywi_common from app.middleware import get_user_params, page_for_admin, check_group, check_services from app.modules.db.db_model import Server from app.modules.roxywi.class_models import BaseResponse, DataStrResponse, HaproxyConfigRequest, GenerateConfigRequest, \ - HaproxyUserListRequest, HaproxyPeersRequest, HaproxyGlobalRequest, HaproxyDefaultsRequest, IdDataStrResponse + HaproxyUserListRequest, HaproxyPeersRequest, HaproxyGlobalRequest, HaproxyDefaultsRequest, IdDataStrResponse, \ + ErrorResponse from app.modules.common.common_classes import SupportClass @@ -84,14 +85,15 @@ class HaproxySectionView(MethodView): except Exception as e: return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot create HAProxy section') + if 'Fatal' in output or 'error' in output: + return ErrorResponse(error=output).model_dump(mode='json'), 500 + try: add_sql.insert_new_section(server_id, section_type, body.name, body) except Exception as e: return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot add HAProxy section') - res = IdDataStrResponse(data=output, id=f'{server_id}-{body.name}').model_dump(mode='json') - print(res) - return res, 201 + return IdDataStrResponse(data=output, id=f'{server_id}-{body.name}').model_dump(mode='json'), 201 def put(self, service: Literal['haproxy'], @@ -116,7 +118,7 @@ class HaproxySectionView(MethodView): return roxywi_common.handler_exceptions_for_json_data(e, 'Cannot create HAProxy section') if 'Fatal' in output or 'error' in output: - return DataStrResponse(data=output).model_dump(mode='json'), 201 + return ErrorResponse(error=output).model_dump(mode='json'), 500 else: try: if section_name in ('global', 'defaults'): @@ -168,7 +170,13 @@ class HaproxySectionView(MethodView): if len(output['failures']) > 0 or len(output['dark']) > 0: raise Exception('Cannot create HAProxy section. Check Apache error log') - output = config_mod.master_slave_upload_and_restart(server.ip, cfg, str(body.action), 'haproxy') + if body: + if body.action: + action = str(body.action) + else: + action = 'save' + + output = config_mod.master_slave_upload_and_restart(server.ip, cfg, action, 'haproxy') return output @@ -285,8 +293,6 @@ class ListenSectionView(HaproxySectionView): type: string redispatch: type: integer - server: - type: integer servers_check: type: object slow_attack: @@ -297,6 +303,10 @@ class ListenSectionView(HaproxySectionView): type: string waf: type: integer + antibot: + type: integer + ddos: + type: integer whitelist: type: string id: @@ -403,9 +413,9 @@ class ListenSectionView(HaproxySectionView): server: type: string port: - type: string + type: integer port_check: - type: string + type: integer maxconn: type: string send_proxy: @@ -414,8 +424,6 @@ class ListenSectionView(HaproxySectionView): type: integer type: type: string - server: - type: string name: type: string mode: @@ -456,8 +464,10 @@ class ListenSectionView(HaproxySectionView): type: string forward_for: type: integer - force_close: - type: string + antibot: + type: integer + ddos: + type: integer cookie: type: object properties: @@ -467,7 +477,7 @@ class ListenSectionView(HaproxySectionView): type: string dynamic: type: string - dynamicKey: + dynamic_key: type: string nocache: type: string @@ -481,13 +491,11 @@ class ListenSectionView(HaproxySectionView): type: object properties: inter: - type: string + type: integer rise: - type: string + type: integer fall: - type: string - inter: - type: string + type: integer circuit_breaking: type: object properties: @@ -592,19 +600,17 @@ class ListenSectionView(HaproxySectionView): server: type: string port: - type: string + type: integer port_check: type: string maxconn: - type: string + type: integer send_proxy: type: integer backup: type: integer type: type: string - server: - type: string name: type: string mode: @@ -645,8 +651,10 @@ class ListenSectionView(HaproxySectionView): type: string forward_for: type: integer - force_close: - type: string + antibot: + type: integer + ddos: + type: integer cookie: type: object properties: @@ -656,7 +664,7 @@ class ListenSectionView(HaproxySectionView): type: string dynamic: type: string - dynamicKey: + dynamic_key: type: string nocache: type: string @@ -670,13 +678,11 @@ class ListenSectionView(HaproxySectionView): type: object properties: inter: - type: string + type: integer rise: - type: string + type: integer fall: - type: string - inter: - type: string + type: integer circuit_breaking: type: object properties: