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: