diff --git a/app/modules/db/migrations/v8_2_2_update_version.py b/app/modules/db/migrations/v8_2_2_update_version.py new file mode 100644 index 00000000..c61fe9c3 --- /dev/null +++ b/app/modules/db/migrations/v8_2_2_update_version.py @@ -0,0 +1,22 @@ +from playhouse.migrate import * +from app.modules.db.db_model import connect, Version + +migrator = connect(get_migrator=1) + + +def up(): + """Apply the migration.""" + try: + Version.update(version='8.2.2').execute() + except Exception as e: + print(f"Error updating version: {str(e)}") + raise e + + +def down(): + """Roll back the migration.""" + try: + Version.update(version='8.2.1').execute() + except Exception as e: + print(f"Error rolling back migration: {str(e)}") + raise e diff --git a/app/modules/roxywi/class_models.py b/app/modules/roxywi/class_models.py index 21a9db20..32968ab3 100644 --- a/app/modules/roxywi/class_models.py +++ b/app/modules/roxywi/class_models.py @@ -475,6 +475,7 @@ class HaproxyConfigRequest(BaseModel): backends: Optional[str] = None circuit_breaking: Optional[HaproxyCircuitBreaking] = None action: Optional[Literal['save', 'test', 'reload', 'restart']] = "save" + http2: Optional[bool] = False class HaproxyUserListUser(BaseModel): @@ -645,6 +646,11 @@ class NginxLocationRequest(BaseModel): return values +class NginxProxyPassSecurity(BaseModel): + security_headers: bool = False + hide_server_tokens: bool = False + + class NginxProxyPassRequest(BaseModel): locations: List[NginxLocationRequest] name: Union[IPvAnyAddress, DomainName] @@ -655,9 +661,11 @@ class NginxProxyPassRequest(BaseModel): ssl_crt: Optional[str] = None ssl_key: Optional[str] = None ssl_offloading: Optional[bool] = False + hsts: Optional[bool] = False http2: Optional[bool] = False action: Optional[Literal['save', 'test', 'reload', 'restart']] = 'reload' compression: bool = False compression_level: Annotated[int, Gt(0), Le(10)] = 6 compression_min_length: Optional[int] = 1024 compression_types: Optional[str] = 'text/plain text/css application/json application/javascript text/xml' + security: Optional[NginxProxyPassSecurity] diff --git a/app/scripts/ansible/roles/haproxy_section/templates/section.j2 b/app/scripts/ansible/roles/haproxy_section/templates/section.j2 index 4bf5eaf9..d632c928 100644 --- a/app/scripts/ansible/roles/haproxy_section/templates/section.j2 +++ b/app/scripts/ansible/roles/haproxy_section/templates/section.j2 @@ -1,7 +1,7 @@ {{ 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 %} + bind {{ bind.ip }}:{{ bind.port }} {% if config.ssl != 'None' and config.mode == 'http' and config.ssl.cert %} ssl crt {{cert_path}}/{{ config.ssl.cert }} {% if config.http2 %}alpn h2,http/1.1{% endif %}{% endif %} {% endfor %} {% endif %} @@ -35,6 +35,14 @@ tcp-request connection reject if { src -f {{ service_dir }}/black/{{ config.blacklist }} } {% endif %} + {% if config.ddos -%} + {{ ddos }} + acl abuse sc1_http_req_rate({{ config.name }}) ge 100 + acl flag_abuser sc1_inc_gpc0({{ config.name }}) + tcp-request content reject if abuse flag_abuser + # End config for DDOS + {% endif -%} + {% if config.acls != 'None' -%} {% for acl in config.acls -%} {% if acl.acl_if in (1, 2) -%} @@ -72,14 +80,6 @@ {{ ssl_offloading }} {% endif -%} - {% if config.ddos -%} - {{ ddos }} - acl abuse sc1_http_req_rate({{ config.name }}) ge 100 - acl flag_abuser sc1_inc_gpc0({{ config.name }}) - tcp-request content reject if abuse flag_abuser - # End config for DDOS - {% endif -%} - {% if config.waf -%} filter spoe engine modsecurity config {{ service_dir }}/waf.conf http-request deny if { var(txn.modsec.code) -m int gt 0 } @@ -102,7 +102,7 @@ {% if config.ssl != 'None' and config.mode == 'http' -%} {%- if config.ssl.ssl_check_backend -%} - {%- set ssl_check_option = 'ssl verify' -%} + {%- set ssl_check_option = 'ssl verify required' -%} {%- else -%} {%- set ssl_check_option = 'ssl verify none' -%} {%- endif -%} @@ -130,12 +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' and config.servers_template == 'None' -%} - {% for backend in config.backend_servers -%} + {%- if config.backend_servers != 'None' and config.servers_template == '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 -%} + {%- endif -%} {% if config.servers_template != 'None' -%} server-template {{ config.servers_template.prefix }} {{ config.servers_template.count }} {{ config.servers_template.servers }}: {{ config.servers_template.port }} {{ check_option }} diff --git a/app/scripts/ansible/roles/nginx_section/templates/proxy_pass.j2 b/app/scripts/ansible/roles/nginx_section/templates/proxy_pass.j2 index 71746355..4371006b 100644 --- a/app/scripts/ansible/roles/nginx_section/templates/proxy_pass.j2 +++ b/app/scripts/ansible/roles/nginx_section/templates/proxy_pass.j2 @@ -4,7 +4,7 @@ server { listen {{ config.port }} ssl{% if config.http2 %} http2{% endif %}; ssl_certificate {{ config.ssl_crt }}; ssl_certificate_key {{ config.ssl_key }}; - {% if nginx_proxy.security.hsts %} + {% if config.hsts %} add_header Strict-Transport-Security "max-age={{ nginx_proxy.security.hsts_max_age }}; includeSubDomains" always; {% endif %} {% else %} @@ -29,11 +29,11 @@ server { {% endif -%} - {% if nginx_proxy.security.hide_server_tokens %} + {% if config.security.hide_server_tokens %} server_tokens off; {% endif %} - {% if nginx_proxy.security.security_headers %} + {% if config.security.security_headers %} # Security headers add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; @@ -150,7 +150,7 @@ proxy_cache_path {{ nginx_proxy.caching.zones[0].path }} inactive={{ nginx_proxy.caching.zones[0].inactive }}; {% endif %} -{% if config.ssl_offloading -%} +{% if config.ssl_offloading and config.scheme == 'https' -%} # HTTP to HTTPS redirect server { listen 80; diff --git a/app/scripts/ansible/roles/nginx_section/vars/main.yml b/app/scripts/ansible/roles/nginx_section/vars/main.yml index 55b669b7..f18ed33b 100644 --- a/app/scripts/ansible/roles/nginx_section/vars/main.yml +++ b/app/scripts/ansible/roles/nginx_section/vars/main.yml @@ -9,10 +9,7 @@ nginx_proxy: # Security configurations security: - hide_server_tokens: true # Hide Nginx version in headers hide_backend_headers: true # Hide backend server headers (e.g., X-Powered-By) - security_headers: true # Enable security headers (X-Content-Type-Options, etc.) - hsts: true # Enable HTTP Strict Transport Security hsts_max_age: 15768000 # HSTS duration in seconds (6 months) content_security_policy: "default-src 'self'" # Content Security Policy rules diff --git a/app/static/js/add_nginx.js b/app/static/js/add_nginx.js index 0ce62992..cdc77b35 100644 --- a/app/static/js/add_nginx.js +++ b/app/static/js/add_nginx.js @@ -214,6 +214,7 @@ function getNginxFormData($form, form_name) { indexed_array['locations'] = []; indexed_array['backend_servers'] = []; indexed_array['name_aliases'] = []; + indexed_array['security'] = {}; let headers = []; $.map(unindexed_array, function (n, i) { @@ -244,6 +245,12 @@ function getNginxFormData($form, form_name) { } else { indexed_array['ssl_offloading'] = false; } + } else if (n['name'] === 'hsts') { + if ($('input[name="hsts"]').is(':checked')) { + indexed_array['hsts'] = true; + } else { + indexed_array['hsts'] = false; + } } else if (n['name'] === 'http2') { if ($('input[name="http2"]').is(':checked')) { indexed_array['http2'] = true; @@ -261,6 +268,15 @@ function getNginxFormData($form, form_name) { } indexed_array['name_aliases'].push(name); }); + let hide_server_tokens = false; + let security_headers = false; + if ($('#hide_server_tokens').is(':checked')) { + hide_server_tokens = true; + } + if ($('#security_headers').is(':checked')) { + security_headers = true; + } + indexed_array['security'] = {'hide_server_tokens': hide_server_tokens, 'security_headers': security_headers}; $('#'+form_name+' span[name="add_servers"] p').each(function (){ let server = $(this).children("input[name='servers']").val(); if (server === undefined || server === '') { @@ -274,7 +290,7 @@ function getNginxFormData($form, form_name) { }); let elementsForDelete = [ 'servers', 'server_port', 'max_fails', 'fail_timeout', 'proxy_connect_timeout', 'proxy_read_timeout', 'proxy_send_timeout', - 'headers_res', 'header_name', 'header_value', 'upstream', 'server', 'name_alias' + 'headers_res', 'header_name', 'header_value', 'upstream', 'server', 'name_alias', 'hide_server_tokens', 'security_headers' ] for (let element of elementsForDelete) { delete indexed_array[element] diff --git a/app/static/js/edit_config.js b/app/static/js/edit_config.js index 0d1f6783..4dbe5886 100644 --- a/app/static/js/edit_config.js +++ b/app/static/js/edit_config.js @@ -105,6 +105,14 @@ function openSection(section) { } else { $('#'+section_type+'_whitelist_checkbox').prop("checked", false); } + if (data.ssl) { + if ($("#https-" + section_type).is(':checked')) { + $("#https-hide-" + section_type).show("fast"); + $("#path-cert-" + section_type).val(data.ssl['cert']); + } else { + $("#https-hide-" + section_type).hide("fast"); + } + } } if (section_type === 'listen' || section_type === 'backend') { if (data.config.backend_servers) { @@ -460,12 +468,10 @@ 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 = true; - if ($('input[name="ssl-check"]').is(':checked')) { - ssl_check_backend = 0; - } else { - ssl_check_backend = true; + let cert = $form.find('input[name="cert"]').val(); + if ($form.find('input[name="ssl-check"]').is(':checked')) { + ssl_check_backend = false; } indexed_array['ssl'] = {cert, ssl_check_backend}; } @@ -509,6 +515,10 @@ function getFormData($form, form_name) { if ($('input[name="cache"]').is(':checked')) { indexed_array['cache'] = true; } + } else if (n['name'] === 'http2') { + if ($('input[name="http2"]').is(':checked')) { + indexed_array['http2'] = true; + } } else if (n['name'] === 'circuit_breaking') { if ($('input[name="circuit_breaking"]').is(':checked')) { let observe = $('select[name="circuit_breaking_observe"] option:selected').val(); @@ -805,6 +815,23 @@ function openNginxSection(section) { $("#scheme").selectmenu(); $("#scheme").selectmenu('refresh'); } + if (data.security) { + if (data.security.hide_server_tokens) { + $('#hide_server_tokens').prop("checked", true); + } else { + $('#hide_server_tokens').prop("checked", false); + } + if (data.security.security_headers) { + $('#security_headers').prop("checked", true); + } else { + $('#security_headers').prop("checked", false); + } + } + if (data.hsts) { + $('#hsts').prop("checked", true); + } else { + $('#hsts').prop("checked", false); + } } $(section_id + ' select[name="server"]').selectmenu(); $(section_id + ' select[name="server"]').selectmenu('refresh'); diff --git a/app/templates/include/add/frontend.html b/app/templates/include/add/frontend.html index 407c4077..f54f33e5 100644 --- a/app/templates/include/add/frontend.html +++ b/app/templates/include/add/frontend.html @@ -48,6 +48,7 @@ diff --git a/app/templates/include/add/listen.html b/app/templates/include/add/listen.html index 6208309c..986c0d51 100644 --- a/app/templates/include/add/listen.html +++ b/app/templates/include/add/listen.html @@ -50,6 +50,7 @@
{{lang.words.enter2|title()}} {{lang.words.name}} {{lang.words.of}} pem {{lang.words.file2}}, {{lang.add_page.desc.press_down}}:
{{ input('path-cert-listen', name="cert", placeholder="some_cert.pem", size='39') }}
+

{{ checkbox('listen-http2', name='http2', title=lang.add_page.desc.enable_http2, desc='Enable HTTP2') }}

diff --git a/app/templates/include/add_nginx/proxy_pass.html b/app/templates/include/add_nginx/proxy_pass.html index 7a50c96f..3b938e6f 100644 --- a/app/templates/include/add_nginx/proxy_pass.html +++ b/app/templates/include/add_nginx/proxy_pass.html @@ -58,7 +58,9 @@ Timeouts: Timeouts (proxy_connect_timeout).