diff --git a/www/nginx/pkg-descr b/www/nginx/pkg-descr index 60d89e3b8..661bb49eb 100644 --- a/www/nginx/pkg-descr +++ b/www/nginx/pkg-descr @@ -10,6 +10,18 @@ WWW: https://nginx.org/ Plugin Changelog ================ +1.33 + +* Add the "resolver" directive support +* Add config preview/test/copy to GUI +* Add gzip disabling support +* Move bot UA settings to GUI +* Add auto-ban logging support +* Add Upstream keepalive support +* Add Stream proxy_connect_timeout and proxy_timeout directives support +* Convert epoch time to readable format on "Banned" page +* Cosmetic: add table header borders on Traffic Statistic page for better reading + 1.32.2 * Add columns select button to the log viewer (contributed by kulikov-a) diff --git a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/Api/LogsController.php b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/Api/LogsController.php index b33246006..f0b417079 100644 --- a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/Api/LogsController.php +++ b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/Api/LogsController.php @@ -40,7 +40,7 @@ class LogsController extends ApiControllerBase /** * "/" -> list of access logs * "/uuid" -> conent of access log - * @param null|string $uuid log uuid of the HTTP server from which the error log should be returned + * @param null|string $uuid log uuid of the HTTP server from which the access log should be returned * @param $fileno int number of logfile to retrieve * @param $page int pagination page to retrieve * @param $perPage int number of entries per page @@ -53,7 +53,12 @@ class LogsController extends ApiControllerBase $this->nginx = new Nginx(); if (!isset($uuid)) { // emulate REST API -> /accesses delivers a list of servers with access logs - return $this->list_vhosts(); + // attach special vhost for perm_ban log if needed + $data = $this->list_vhosts(); + if ((string)$this->nginx->http->log_perm_ban == "1") { + $data[] = array('id' => 'perm_ban', 'server_name' => 'Auto-ban'); + } + return $data; } elseif (!isset($fileno)) { return $this->list_logfiles('access', $uuid); } else { @@ -161,7 +166,7 @@ class LogsController extends ApiControllerBase */ private function get_logs($type, $uuid, $fileno, $page, $perPage, $query) { - if (!($this->vhost_exists($uuid) || $uuid == 'global')) { + if (!($this->vhost_exists($uuid) || $uuid == 'global' || $uuid == 'perm_ban')) { return $this->response->setStatusCode(404, "Not Found"); } @@ -182,7 +187,7 @@ class LogsController extends ApiControllerBase */ private function list_logfiles($type, $uuid) { - if (!($this->vhost_exists($uuid) || $uuid == 'global')) { + if (!($this->vhost_exists($uuid) || $uuid == 'global' || $uuid == 'perm_ban')) { return $this->response->setStatusCode(404, "Not Found"); } diff --git a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/Api/SettingsController.php b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/Api/SettingsController.php index 4a0849cf5..c6d77d395 100644 --- a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/Api/SettingsController.php +++ b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/Api/SettingsController.php @@ -229,6 +229,33 @@ class SettingsController extends ApiMutableModelControllerBase return $this->setBase('custompolicy', 'custom_policy', $uuid); } + // Resolver + public function searchresolverAction() + { + return $this->searchBase('resolver', array('uuid', 'description', 'address', 'valid', 'timeout')); + } + + public function getresolverAction($uuid = null) + { + $this->sessionClose(); + return $this->getBase('resolver', 'resolver', $uuid); + } + + public function addresolverAction() + { + return $this->addBase('resolver', 'resolver'); + } + + public function delresolverAction($uuid) + { + return $this->delBase('resolver', $uuid); + } + + public function setresolverAction($uuid) + { + return $this->setBase('resolver', 'resolver', $uuid); + } + // http server public function searchhttpserverAction() { @@ -818,4 +845,18 @@ class SettingsController extends ApiMutableModelControllerBase { return $this->setBase('syslog_target', 'syslog_target', $uuid); } + + public function showconfigAction() + { + $backend = new Backend(); + $response = json_decode($backend->configdRun("nginx show_config"), true); + return $response; + } + + public function testconfigAction() + { + $backend = new Backend(); + $response = trim($backend->configdRun("nginx test_config")); + return array("response" => $response); + } } diff --git a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/IndexController.php b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/IndexController.php index 410e04989..a30d036d1 100644 --- a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/IndexController.php +++ b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/IndexController.php @@ -63,6 +63,7 @@ class IndexController extends \OPNsense\Base\IndexController $this->view->ipacl = $this->getForm("ipacl"); $this->view->errorpage = $this->getForm("errorpage"); $this->view->tls_fingerprint = $this->getForm("tls_fingerprint"); + $this->view->resolver = $this->getForm("resolver"); $this->view->syslog_target = $this->getForm("syslog_target"); $nginx = new Nginx(); $this->view->show_naxsi_download_button = diff --git a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/httpserver.xml b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/httpserver.xml index 73e6aff5c..4c6339d32 100644 --- a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/httpserver.xml +++ b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/httpserver.xml @@ -187,6 +187,13 @@ true Prefers server ciphers over client ciphers. + + httpserver.resolver + + dropdown + true + + httpserver.ocsp_stapling @@ -208,6 +215,13 @@ true Blocks files like .htaccess files or other files not intended for the public. + + httpserver.disable_gzip + + checkbox + true + Disables responses gzipping. Makes sense if TLS is used and this method of BREACH attack protection is preferred. + httpserver.disable_bot_protection diff --git a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/location.xml b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/location.xml index e228f3505..f5b832c19 100644 --- a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/location.xml +++ b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/location.xml @@ -241,7 +241,14 @@ checkbox true - If you enable the WebSocket Support option, nginx will pass the upgrade header to the backed server. + If you enable the WebSocket Support option, nginx will pass the upgrade header to the backed server. Mutually exclusive with Keepalive support. + + + location.upstream_keepalive + + checkbox + true + Adds the directives required for upstream keepalive to work (enables HTTP 1.1 and clears the Connection header). Mutually exclusive with WebSocket support. Keepalive parameters must be set in Upstream settings too. location.proxy_read_timeout diff --git a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/resolver.xml b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/resolver.xml new file mode 100644 index 000000000..e22432609 --- /dev/null +++ b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/resolver.xml @@ -0,0 +1,44 @@ +
+ + resolver.description + + text + Brief description for reference. + + + resolver.address + + true + + select_multiple + Enter a list of IP addresses and ports (optional). Uses port 53 if omitted. Like "8.8.8.8, [2001:4860:4860::8888], 8.8.8.8:5353". + + + resolver.ipv4_off + + checkbox + Do not request IPv4 addresses. + true + + + resolver.ipv6_off + + checkbox + Do not request IPv6 addresses. + true + + + resolver.valid + + text + Override response TTL in seconds. + true + + + resolver.timeout + + text + Resolver time out in seconds. NGINX default (if empty) is 30 seconds. + true + +
diff --git a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/settings.xml b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/settings.xml index e5d55040d..8ac01921c 100644 --- a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/settings.xml +++ b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/settings.xml @@ -66,6 +66,20 @@ text true
+ + nginx.http.bots_ua + + select_multiple + + true + List of bot user agents that will be used in bot protection. Uses a well known list by default. + + + nginx.http.log_perm_ban + + checkbox + Log requests that led to auto-blocking (bots and honeypots). + nginx.http.ban_response diff --git a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/streamserver.xml b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/streamserver.xml index a243578dd..4f7052836 100644 --- a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/streamserver.xml +++ b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/streamserver.xml @@ -41,6 +41,20 @@ true Due to the nature of UDP, nginx cannot know, when the communication ends and this helps as it tells nginx the number of datagrams the communication is expected to last on server side and it is expected to be closed afterwards. If you enter 0, it is expected, that the server never responds to a datagram. If nginx gets a datagram, it will still get forwarded to the client. Setting this option might be useful in (mostly) unidirectional communication as well. + + streamserver.proxy_connect_timeout + + text + true + Defines a timeout (in seconds) for establishing a connection with a proxied server. + + + streamserver.proxy_timeout + + text + true + Sets the timeout (in seconds) between two successive read or write operations on client or proxied server connections. + streamserver.certificate diff --git a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/upstream.xml b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/upstream.xml index c879b9883..3f9bd6af5 100644 --- a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/upstream.xml +++ b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/upstream.xml @@ -23,6 +23,26 @@ true If you enable the proxy protocol, an upstream proxy or server will get the client IP and the server port before the real traffic is sent. + + upstream.keepalive + + text + Activates the connections cache for upstream server and sets the max number of idle connections that are preserved in the cache of each worker process. Leave blank or set to 0 to disable. Keepalive support should be enabled at Location settings also. + + + upstream.keepalive_requests + + text + true + Sets the maximum number of requests that can be served through one keepalive connection. NGINX default is 1000. + + + upstream.keepalive_timeout + + text + true + Sets a timeout in seconds during which an idle keepalive connection to an upstream server will stay open. NGINX default is 75s. + upstream.host_port diff --git a/www/nginx/src/opnsense/mvc/app/models/OPNsense/Nginx/Nginx.xml b/www/nginx/src/opnsense/mvc/app/models/OPNsense/Nginx/Nginx.xml index a55a4539c..81f686682 100644 --- a/www/nginx/src/opnsense/mvc/app/models/OPNsense/Nginx/Nginx.xml +++ b/www/nginx/src/opnsense/mvc/app/models/OPNsense/Nginx/Nginx.xml @@ -1,6 +1,6 @@ //OPNsense/Nginx - 1.32.1 + 1.33 nginx web server, reverse proxy and waf @@ -65,6 +65,14 @@ Y 403 + + 0 + Y + + + Y + Python-urllib,Nmap,python-requests,libwww-perl,MJ12bot,Jorgee,fasthttp,libwww,Telesphoreo,A6-Indexer,ltx71,okhttp,ZmEu,sqlmap,LMAO/2.0,l9explore,l9tcpid,Masscan,zgrab,Ronin/2.0,Hakai/2.0,Indy\sLibrary,^Mozilla/[\d\.]+$,Morfeus\sFucking\sScanner,MSIE\s[0-6]\.\d+ + N @@ -120,6 +128,18 @@ Weighted Round Robin N + + 0 + N + + + 1 + N + + + 1 + N + 1 N @@ -420,7 +440,25 @@ Y 0 + + + WebSocket and Upstream Keepalive support can not be combined. + SingleSelectConstraint + + upstream_keepalive + + + + + Y + 0 + + + websocket.check001 + + + N 1 @@ -863,6 +901,18 @@ 1 Y + + + + + Selected resolver not found + N + N + 0 Y @@ -879,6 +929,10 @@ 0 Y + + 0 + Y + N /^((?:\d+\.){3,3}\d+|[a-f0-9\:]+)(?:\/\d+)?(,?(?:(?:(\d+\.){3,3}\d+|[a-f0-9\:]+)(?:\/\d+)?))*$/i @@ -1121,6 +1175,14 @@ N 0 + + N + 0 + + + N + 0 + @@ -1199,6 +1261,47 @@ + + + Y + /^[^" \t]+$/i + +
+ Y + Y + /^(?:(?:(?:\d{1,3}(?:\.\d{1,3}){3})|(?:\[[a-f0-9:]{1,4}(?::[a-f0-9:]{0,4}){1,7}\]))(:\d+|:?\d+)*)(?:\s*,\s*(?:(?:(?:\d{1,3}(?:\.\d{1,3}){3})|(?:\[[a-f0-9:]{1,4}(?::[a-f0-9:]{0,4}){1,7}\]))(:\d+|:?\d+)*))*$/i + Please provide a valid resolver address, i.e. 8.8.8.8, [2001:4860:4860::8888], 8.8.8.8:5353. +
+ + 1 + N + + + N + + + Can not disable all record types. + SingleSelectConstraint + + ipv6_off + + + + + + N + + + ipv4_off.check001 + + + + + 1 + N + +
+ Y diff --git a/www/nginx/src/opnsense/mvc/app/views/OPNsense/Nginx/ban.volt b/www/nginx/src/opnsense/mvc/app/views/OPNsense/Nginx/ban.volt index 45284c742..f5830cc43 100644 --- a/www/nginx/src/opnsense/mvc/app/views/OPNsense/Nginx/ban.volt +++ b/www/nginx/src/opnsense/mvc/app/views/OPNsense/Nginx/ban.volt @@ -46,6 +46,11 @@ $(function () { 'options': { selection:false, multiSelect:false, + converters: { + timestamp: { + to: function (value) { return (new Date(value*1000)).toLocaleString(); } + } + }, formatters: { "delbtn": function (column, row) { return ``; diff --git a/www/nginx/src/opnsense/mvc/app/views/OPNsense/Nginx/index.volt b/www/nginx/src/opnsense/mvc/app/views/OPNsense/Nginx/index.volt index 475ff2159..5c715e748 100644 --- a/www/nginx/src/opnsense/mvc/app/views/OPNsense/Nginx/index.volt +++ b/www/nginx/src/opnsense/mvc/app/views/OPNsense/Nginx/index.volt @@ -26,6 +26,10 @@ #} - - +
  • {{ lang._('SYSLOG Targets')}}
  • +
  • + {{ lang._('Config Preview')}} +
  • @@ -671,6 +739,29 @@ +
    + + + + + + + + + + + + + + + + + +
    {{ lang._('ID') }}{{ lang._('Description') }}{{ lang._('Address') }}{{ lang._('Commands') }}
    + + +
    +
    @@ -695,9 +786,31 @@
    +
    +
    + + +
    + + + + + + +
    + +
    + + +
    +
    +
    +
    - {{ partial("layout_partials/base_dialog",['fields': upstream,'id':'upstreamdlg', 'label':lang._('Edit Upstream')]) }} {{ partial("layout_partials/base_dialog",['fields': upstream_server,'id':'upstreamserverdlg', 'label':lang._('Edit Upstream')]) }} {{ partial("layout_partials/base_dialog",['fields': location,'id':'locationdlg', 'label':lang._('Edit Location')]) }} @@ -716,4 +829,5 @@ {{ partial("layout_partials/base_dialog",['fields': ipacl,'id':'ipacl_dlg', 'label':lang._('Edit IP ACL')]) }} {{ partial("layout_partials/base_dialog",['fields': errorpage,'id':'errorpage_dlg', 'label':lang._('Edit Error Page')]) }} {{ partial("layout_partials/base_dialog",['fields': tls_fingerprint,'id':'tls_fingerprint_dlg', 'label':lang._('Edit TLS Fingerprint')]) }} +{{ partial("layout_partials/base_dialog",['fields': resolver,'id':'resolverdlg', 'label':lang._('Edit Resolver')]) }} {{ partial("layout_partials/base_dialog",['fields': syslog_target,'id':'syslog_target_dlg', 'label':lang._('Edit SYSLOG Target')]) }} diff --git a/www/nginx/src/opnsense/scripts/nginx/list_logs.php b/www/nginx/src/opnsense/scripts/nginx/list_logs.php index 93d78207c..93c951c34 100755 --- a/www/nginx/src/opnsense/scripts/nginx/list_logs.php +++ b/www/nginx/src/opnsense/scripts/nginx/list_logs.php @@ -68,9 +68,11 @@ $server = $_SERVER['argv'][2]; $nginx = new Nginx(); $result = []; -// special case: the global error log +// special cases: the global error log and the perm_ban access log if ($server == 'global') { $result = list_logfiles('error.log'); +} elseif ($server == 'perm_ban') { + $result = list_logfiles('perm_ban.access.log'); } else { switch ($mode) { case 'error': diff --git a/www/nginx/src/opnsense/scripts/nginx/ngx_showConfig.py b/www/nginx/src/opnsense/scripts/nginx/ngx_showConfig.py new file mode 100644 index 000000000..e96374a7e --- /dev/null +++ b/www/nginx/src/opnsense/scripts/nginx/ngx_showConfig.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 + +# nginx -T shows the config only if the test succeeds +# grab nginx config from file(s) and send to stdout + +import os.path +import glob +import ujson + +result = dict() +nginx_config = [] +nginx_config_root = '/usr/local/etc/nginx/' +nginx_config_file = nginx_config_root + 'nginx.conf' + +def load_config_file(config_path): + """ load config with all inclusions + """ + config_incs = [] + # mimic 'nginx -T' syntax for config files references + nginx_config.append('# configuration file ' + config_path + ':') + for line in open(config_path, 'r').read().split('\n'): + nginx_config.append(line.rstrip()) + line = line.strip() + if line.startswith('include '): + # only '*' mask is supported/used in plugin + if '*' not in line: + # it's a file relative path + incfilepath = nginx_config_root + line.split(' ')[-1][:-1] + if os.path.isfile(incfilepath): + config_incs.append(incfilepath) + else: + # it's a path with a file mask + incdir = nginx_config_root + line.split(' ')[-1][:-1] + for incfilepath in glob.glob(incdir): + config_incs.append(incfilepath) + for inc in list(dict.fromkeys(config_incs)): + load_config_file(inc) + +if os.path.isfile(nginx_config_file): + result['time'] = os.path.getmtime(nginx_config_file) + load_config_file(nginx_config_file) + result['config'] = nginx_config +print(ujson.dumps(result)) + diff --git a/www/nginx/src/opnsense/scripts/nginx/ngx_testConfig.sh b/www/nginx/src/opnsense/scripts/nginx/ngx_testConfig.sh new file mode 100644 index 000000000..8b085a1f0 --- /dev/null +++ b/www/nginx/src/opnsense/scripts/nginx/ngx_testConfig.sh @@ -0,0 +1,10 @@ +#!/bin/sh + +# run nginx config test. return error text if any. always exit with 0 +if conf_test_errors=$(nginx -t -q 2>&1); then + echo "config is ok" +else + echo "$conf_test_errors" +fi + +exit 0 diff --git a/www/nginx/src/opnsense/scripts/nginx/read_log.php b/www/nginx/src/opnsense/scripts/nginx/read_log.php index 65f288d60..ac11b9f1e 100755 --- a/www/nginx/src/opnsense/scripts/nginx/read_log.php +++ b/www/nginx/src/opnsense/scripts/nginx/read_log.php @@ -68,6 +68,8 @@ $result = []; // special case: the global error log if ($server == 'global') { $logparser = new ErrorLogParser($log_prefix . 'error' . $log_suffix, $page, $per_page, $query); +} elseif ($server == 'perm_ban') { + $logparser = new AccessLogParser($log_prefix . 'perm_ban.access' . $log_suffix, $page, $per_page, $query); } else { switch ($mode) { case 'error': diff --git a/www/nginx/src/opnsense/service/conf/actions.d/actions_nginx.conf b/www/nginx/src/opnsense/service/conf/actions.d/actions_nginx.conf index 2107624ea..ce44adf64 100644 --- a/www/nginx/src/opnsense/service/conf/actions.d/actions_nginx.conf +++ b/www/nginx/src/opnsense/service/conf/actions.d/actions_nginx.conf @@ -67,3 +67,13 @@ type:script command:/usr/local/opnsense/scripts/nginx/vts.php parameters: type:script_output + +[test_config] +command:/usr/local/opnsense/scripts/nginx/ngx_testConfig.sh +parameters: +type:script_output + +[show_config] +command:/usr/local/opnsense/scripts/nginx/ngx_showConfig.py +parameters: +type:script_output diff --git a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf index a5470e6da..86b1586ab 100644 --- a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf +++ b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/http.conf @@ -14,6 +14,11 @@ log_format main_ext '$remote_addr - $remote_user [$time_local] "$request" ' 'ua="$upstream_addr" us="$upstream_status" ' 'ut="$upstream_response_time" ul="$upstream_response_length" ' 'cs=$upstream_cache_status'; +{% if OPNsense.Nginx.http.log_perm_ban is defined and OPNsense.Nginx.http.log_perm_ban == '1' %} +log_format main_ban '$remote_addr - $remote_user [$time_local] "$scheme://$host$request_uri" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; +{% endif %} log_format handshake '"$http_user_agent" "$ssl_ciphers" "$ssl_curves"'; log_format anonymized ':: - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' @@ -146,7 +151,19 @@ server { {% endif %} {% endif %} {% endif %} +{% if server.resolver is defined and server.resolver != '' %} +{% set resolver = helpers.getUUID(server.resolver) %} +{% if resolver is defined %} + resolver {{ resolver.address.replace(',', ' ') }}{% if resolver.valid is defined and resolver.valid != '' %} valid={{ resolver.valid}}s{% endif %}{% if resolver.ipv4_off is defined and resolver.ipv4_off == '1' %} ipv4=off{% endif %}{% if resolver.ipv6_off is defined and resolver.ipv6_off == '1' %} ipv6=off{% endif %}; +{% if resolver.timeout is defined and resolver.timeout !='' %} + resolver_timeout {{ resolver.timeout }}s; +{% endif %} +{% endif %} +{% endif %} +{% if server.disable_gzip is defined and server.disable_gzip == '1' %} + gzip off; +{% endif %} sendfile {% if server.sendfile is defined and server.sendfile == '1' %}On{% else %}Off{% endif %}; server_name {{ server.servername.replace(',', ' ') }}; {% if server.real_ip_source is defined and server.real_ip_source != '' %} @@ -241,6 +258,9 @@ server { set $naxsi_extensive_log {% if server.naxsi_extensive_log is defined and server.naxsi_extensive_log == '1' %}1{% else %}0{% endif %}; location @permanentban { access_log /var/log/nginx/permanentban.access.log main; +{% if OPNsense.Nginx.http.log_perm_ban is defined and OPNsense.Nginx.http.log_perm_ban == '1' %} + access_log /var/log/nginx/perm_ban.access.log main_ban; +{% endif %} internal; add_header "Content-Type" "text/plain; charset=UTF-8" always; return {% if OPNsense.Nginx.http.ban_response is defined and OPNsense.Nginx.http.ban_response != '403' %}{{OPNsense.Nginx.http.ban_response}}{% else %}403 "You got banned permanently from this server."{% endif %}; @@ -261,19 +281,12 @@ server { } {% endif %} {% if server.disable_bot_protection is not defined or server.disable_bot_protection != '1' %} - # block based on User Agents - stuff I have found over the years in my server log - if ($http_user_agent ~* Python-urllib|Nmap|python-requests|libwww-perl|MJ12bot|Jorgee|fasthttp|libwww|Telesphoreo|A6-Indexer|ltx71|okhttp|ZmEu|sqlmap|LMAO/2.0|l9explore|l9tcpid|Masscan|zgrab|Ronin/2.0|Hakai/2.0) { - return 418; - } - {# MSIE 7 cannot be blocked - used for compatibility mode - https://blogs.msdn.microsoft.com/ieinternals/2013/09/21/internet-explorer-11s-many-user-agent-strings/ #} - if ($http_user_agent ~ "Indy\sLibrary|Morfeus Fucking Scanner|MSIE [0-6]\.\d+") - { - return 418; - } - if ($http_user_agent ~ ^Mozilla/[\d\.]+$) - { - return 418; +{% if OPNsense.Nginx.http.bots_ua is defined and OPNsense.Nginx.http.bots_ua|default("") != "" %} + # block based on User Agents defined in global http settings + if ($http_user_agent ~* {{ OPNsense.Nginx.http.bots_ua|replace(',','|') }}) { + return 418; } +{% endif %} {% endif %} {% if server.ip_acl is defined %} {% set ip_acl = server.ip_acl %} diff --git a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/location.conf b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/location.conf index 35145c75b..1d0adc6dc 100644 --- a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/location.conf +++ b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/location.conf @@ -126,6 +126,10 @@ location {{ location.matchtype }} {{ location.urlpattern }} { proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; {% endif %} +{% if location.upstream_keepalive is defined and location.upstream_keepalive == '1' %} + proxy_http_version 1.1; + proxy_set_header Connection ""; +{% endif %} {% if location.proxy_buffer_size is defined and location.proxy_buffer_size != '' %} proxy_buffer_size {{ location.proxy_buffer_size }}k; {% endif %} diff --git a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/streams.conf b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/streams.conf index 8915f3265..9b696220f 100644 --- a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/streams.conf +++ b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/streams.conf @@ -104,6 +104,12 @@ {% if server.proxy_responses is defined and server.proxy_responses != '' %} proxy_responses {{ server.proxy_responses }}; {% endif%} +{% if server.proxy_connect_timeout is defined and server.proxy_connect_timeout != '' %} + proxy_connect_timeout {{ server.proxy_connect_timeout }}s; +{% endif%} +{% if server.proxy_timeout is defined and server.proxy_timeout != '' %} + proxy_timeout {{ server.proxy_timeout }}s; +{% endif%} {% if server.trusted_proxies is defined and server.trusted_proxies != '' and server.proxy_protocol is defined and server.proxy_protocol == '1' %} {% for trusted_proxy in server.trusted_proxies.split(',') %} set_real_ip_from {{ trusted_proxy }}; diff --git a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/upstream.conf b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/upstream.conf index b5226fa90..3f07f3717 100644 --- a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/upstream.conf +++ b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/upstream.conf @@ -12,6 +12,15 @@ upstream upstream{{ upstream_uuid.replace('-','') }} { ip_hash; {% endif %} {% endif %} +{% if upstream.keepalive is defined and upstream.keepalive|int > 0 %} +keepalive {{ upstream.keepalive }}; +{% if upstream.keepalive_requests is defined and upstream.keepalive_requests != '' %} +keepalive_requests {{ upstream.keepalive_requests }}; +{% endif %} +{% if upstream.keepalive_timeout is defined and upstream.keepalive_timeout != '' %} +keepalive_timeout {{ upstream.keepalive_timeout }}s; +{% endif %} +{% endif %} {% for upstream_serveruuid in upstream.serverentries.split(',') %} {% set upstream_server = helpers.getUUID(upstream_serveruuid) %} server {% if ':' in upstream_server.server %}[{% endif %}{{ upstream_server.server }}{% if ':' in upstream_server.server %}]{% endif diff --git a/www/nginx/src/opnsense/www/css/nginx/index.css b/www/nginx/src/opnsense/www/css/nginx/index.css new file mode 100644 index 000000000..a680bfef0 --- /dev/null +++ b/www/nginx/src/opnsense/www/css/nginx/index.css @@ -0,0 +1,88 @@ +/* +* Copyright (C) 2023 A. Kulikov +* Copyright (C) 2017-2018 Fabian Franz +* All rights reserved. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* +* 1. Redistributions of source code must retain the above copyright notice, +* this list of conditions and the following disclaimer. +* +* 2. Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* +* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, +* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +* AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +*/ + +#frm_sni_hostname_mapdlg .col-md-4, +#frm_ipacl_dlg .col-md-4 { + width: 50%; +} +#frm_sni_hostname_mapdlg td > input[type="text"], +#frm_ipacl_dlg td > input[type="text"] { + width: 100%; + max-width: 100%; +} +#frm_sni_hostname_mapdlg .col-md-5, +#frm_ipacl_dlg .col-md-5 { + width: 25%; +} +#row_snihostname\.data .row, +#row_ipacl\.data .row { + padding-top: 5px; +} +#row_snihostname\.data .row div, +#row_ipacl\.data .row div { + padding: 0; +} +#sni_hostname_mapdlg .bootstrap-select, +#frm_ipacl_dlg .bootstrap-select { + width: 100% !important; +} +#nginx_conf_container { + overflow-x: auto; +} +.ngx_conf_table { + white-space: pre-wrap; + color: #333333; + background-color: #f5f5f5; + word-break: break-all; + word-wrap: break-word; +} +.ngx_conf_table_body { + display: grid; + height: 400px; + overflow-y: auto; + font-family: ui-monospace,monospace; + font-size: 13px; +} +.l-number { + position: relative; + width: 1%; + min-width: 50px; + padding-right: 20px; + padding-left: 1px; + text-align: right; + white-space: nowrap; + vertical-align: top; + user-select: none; + filter: brightness(2.0); + filter: contrast(0.3); +} +.placeholdertd { + padding: 10px; +} +#nginx_config_copy { + cursor: pointer; +} diff --git a/www/nginx/src/opnsense/www/css/nginx/vts.css b/www/nginx/src/opnsense/www/css/nginx/vts.css index a63c5e1b2..60f08183a 100644 --- a/www/nginx/src/opnsense/www/css/nginx/vts.css +++ b/www/nginx/src/opnsense/www/css/nginx/vts.css @@ -44,6 +44,7 @@ #monitor thead th { font-size: 1em; padding: .1em .3em; + border: 1px solid; } #monitor tbody th { text-align: left; diff --git a/www/nginx/src/opnsense/www/js/nginx/dist/configuration.min.js b/www/nginx/src/opnsense/www/js/nginx/dist/configuration.min.js index e7c57eb22..3ef498d00 100644 --- a/www/nginx/src/opnsense/www/js/nginx/dist/configuration.min.js +++ b/www/nginx/src/opnsense/www/js/nginx/dist/configuration.min.js @@ -1 +1 @@ -!function(t){var e={};function n(i){if(e[i])return e[i].exports;var s=e[i]={i:i,l:!1,exports:{}};return t[i].call(s.exports,s,s.exports,n),s.l=!0,s.exports}n.m=t,n.c=e,n.d=function(t,e,i){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:i})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var s in t)n.d(i,s,function(e){return t[e]}.bind(null,s));return i},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=27)}({27:function(t,e,n){"use strict";n.r(e);var i=Backbone.View.extend({tagName:"div",attributes:{class:"container-fluid"},child_views:[],createModel:null,upstreamCollection:null,initialize:function(t){this.dataField=$(t.dataField),this.entryclass=t.entryclass,this.createModel=t.createModel,this.upstreamCollection=t.upstreamCollection,this.listenTo(this.collection,"add remove reset",this.render),this.listenTo(this.collection,"change",this.update),this.dataField.after(this.$el)},events:{"click .add":"addEntry"},render:function(){this.child_views.forEach(t=>t.remove()),this.$el.html(""),this.child_views=[],this.update(),this.collection.each(t=>{const e=new this.entryclass({model:t,collection:this.collection,upstreamCollection:this.upstreamCollection});this.child_views.push(e),this.$el.append(e.$el),e.render()}),this.$el.append($('\n
    \n \n
    '))},update:function(){this.dataField.data("data",this.collection.toJSON())},addEntry:function(t){t.preventDefault(),this.collection.add(this.createModel())}});var s=Backbone.Collection.extend({url:"/api/nginx/settings/searchupstream",parse:function(t){return t.rows}});const a=Backbone.View.extend({tagName:"div",attributes:{class:"row"},events:{"keyup .key":function(){this.model.set("hostname",this.key.value)},"change .value":function(){this.model.set("upstream",this.value.value)},"click .delete":"deleteEntry"},key:null,value:null,delBtn:null,first:null,second:null,third:null,upstreamCollection:null,initialize:function(t){this.upstreamCollection=t.upstreamCollection,this.listenTo(this.upstreamCollection,"update reset add remove",this.regenerate_list),this.first=document.createElement("div"),this.first.classList.add("col-sm-5"),this.key=document.createElement("input"),this.first.append(this.key),this.key.type="text",this.key.classList.add("key"),this.key.value=this.model.get("hostname"),this.second=document.createElement("div"),this.second.classList.add("col-sm-5"),this.value=document.createElement("select"),this.second.append(this.value),this.value.classList.add("value"),this.value.classList.add("form-control"),this.value.value=this.model.get("upstream"),this.third=document.createElement("div"),this.third.classList.add("col-sm-2"),this.third.style.textAlign="right",this.delBtn=document.createElement("button"),this.delBtn.classList.add("delete"),this.delBtn.classList.add("btn"),this.delBtn.innerHTML='',this.third.append(this.delBtn),this.model.has("upstream")&&0!==this.upstreamCollection.where({uuid:this.model.get("upstream")}).length||this.upstreamCollection.length>0&&this.model.set("upstream",this.upstreamCollection.at(0).get("uuid")),this.$el.append(this.first).append(this.second).append(this.third)},render:function(){$(this.key).val(this.model.get("hostname")),this.regenerate_list(),$(this.value).val(this.model.get("upstream"))},deleteEntry:function(t){t.preventDefault(),this.collection.remove(this.model)},regenerate_list:function(){const t=$(this.value);t.html(""),this.upstreamCollection.each(e=>t.append(``)),t.val(this.model.get("upstream")),t.selectpicker("refresh")}}),l=Backbone.View.extend({tagName:"div",attributes:{class:"row"},events:{"keyup .key":function(){this.model.set("network",this.key.value)},"change .value":function(){this.model.set("action",this.value.value)},"click .delete":"deleteEntry"},key:null,value:null,delBtn:null,first:null,second:null,third:null,upstreamCollection:null,initialize:function(t){this.upstreamCollection=t.upstreamCollection,this.listenTo(this.upstreamCollection,"update reset add remove",this.regenerate_list),this.first=document.createElement("div"),this.first.classList.add("col-sm-5"),this.key=document.createElement("input"),this.first.append(this.key),this.key.type="text",this.key.classList.add("key"),this.key.value=this.model.get("network"),this.second=document.createElement("div"),this.second.classList.add("col-sm-5"),this.value=document.createElement("select"),this.second.append(this.value),this.value.classList.add("value"),this.value.classList.add("form-control"),this.value.value=this.model.get("action"),this.third=document.createElement("div"),this.third.classList.add("col-sm-2"),this.third.style.textAlign="right",this.delBtn=document.createElement("button"),this.delBtn.classList.add("delete"),this.delBtn.classList.add("btn"),this.delBtn.innerHTML='',this.third.append(this.delBtn),this.$el.append(this.first).append(this.second).append(this.third)},render:function(){$(this.key).val(this.model.get("network")),this.regenerate_list(),$(this.value).val(this.model.get("action"))},deleteEntry:function(t){t.preventDefault(),this.collection.remove(this.model)},regenerate_list:function(){const t=$(this.value);t.html(""),this.upstreamCollection.each(e=>t.append(``)),t.val(this.model.get("action")),t.selectpicker("refresh")}});var o=Backbone.Collection.extend({initialize:function(){let t=this;$("#snihostname\\.data").change(function(){t.regenerateFromView()})},regenerateFromView:function(){let t=$("#snihostname\\.data").data("data");_.isArray(t)||(t=[]),this.reset(t)}}),r=Backbone.Model.extend({}),d=Backbone.Model.extend({}),c=Backbone.Collection.extend({initialize:function(){let t=this;$("#ipacl\\.data").change(function(){t.regenerateFromView()})},regenerateFromView:function(){let t=$("#ipacl\\.data").data("data");_.isArray(t)||(t=[]),this.reset(t)}});const u=new s,h=new Backbone.Collection([{name:"Deny",value:"deny"},{name:"Allow",value:"allow"}]);$(document).ready(function(){mapDataToFormUI({frm_nginx:"/api/nginx/settings/get"}).done(function(){formatTokenizersUI(),$('select[data-allownew="false"]').selectpicker("refresh"),updateServiceControlUI("nginx")}),""!==window.location.hash&&$('a[href="'+window.location.hash+'"]').click(),$(".nav-tabs a").on("shown.bs.tab",function(t){history.pushState(null,null,t.target.hash)}),$(".reload_btn").click(function(){$(".reloadAct_progress").addClass("fa-spin"),ajaxCall("/api/nginx/service/reconfigure",{},function(){$(".reloadAct_progress").removeClass("fa-spin")})}),$('[id*="save_"]').each(function(){$(this).click(function(){let t=$(this).closest("form").attr("id"),e=$(this).closest("form").attr("data-title");saveFormToEndpoint("/api/nginx/settings/set",t,function(){$("#"+t+"_progress").addClass("fa fa-spinner fa-pulse"),ajaxCall("/api/nginx/service/reconfigure",{},function(n,i){$("#"+t+"_progress").removeClass("fa fa-spinner fa-pulse"),void 0===n||"success"===i&&"ok"===n.status?updateServiceControlUI("nginx"):BootstrapDialog.show({type:BootstrapDialog.TYPE_WARNING,title:e,message:JSON.stringify(n),draggable:!0})})})})}),["upstream","upstreamserver","location","credential","userlist","httpserver","streamserver","httprewrite","custompolicy","security_header","ipacl","limit_zone","cache_path","limit_request_connection","snifwd","errorpage","tls_fingerprint","syslog_target","naxsirule"].forEach(function(t){$("#grid-"+t).UIBootgrid({search:"/api/nginx/settings/search"+t,get:"/api/nginx/settings/get"+t+"/",set:"/api/nginx/settings/set"+t+"/",add:"/api/nginx/settings/add"+t+"/",del:"/api/nginx/settings/del"+t+"/",commands:{copy_uuid:{method:function(t){navigator.clipboard.writeText($(this).data("row-id"))}}},options:{selection:!1,multiSelect:!1,formatters:{commands:function(t,e){return' '},response:function(t,e){return"none"==e.response?"unchanged":e.response},statuscodes:function(t,e){const n=[],i=e.statuscodes.split(",");for(let t of i)n.push(t.substr(0,3));return n.join(", ")}}}})}),bind_naxsi_rule_dl_button(),function(){let t=new i({dataField:document.getElementById("snihostname.data"),upstreamCollection:u,entryclass:a,collection:new o,createModel:function(){return new r({hostname:"localhost"})}});window.snifield=t,t.render(),$("#grid-upstream").on("loaded.rs.jquery.bootgrid",function(){u.fetch()}),u.fetch()}();let t=new i({dataField:document.getElementById("ipacl.data"),upstreamCollection:h,entryclass:l,collection:new c,createModel:function(){return new d({network:"::",action:"deny"})}});window.ipaclfield=t,t.render()})}}); \ No newline at end of file +!function(t){var e={};function n(i){if(e[i])return e[i].exports;var s=e[i]={i:i,l:!1,exports:{}};return t[i].call(s.exports,s,s.exports,n),s.l=!0,s.exports}n.m=t,n.c=e,n.d=function(t,e,i){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:i})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var s in t)n.d(i,s,function(e){return t[e]}.bind(null,s));return i},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=25)}({25:function(t,e,n){"use strict";n.r(e);var i=Backbone.View.extend({tagName:"div",attributes:{class:"container-fluid"},child_views:[],createModel:null,upstreamCollection:null,initialize:function(t){this.dataField=$(t.dataField),this.entryclass=t.entryclass,this.createModel=t.createModel,this.upstreamCollection=t.upstreamCollection,this.listenTo(this.collection,"add remove reset",this.render),this.listenTo(this.collection,"change",this.update),this.dataField.after(this.$el)},events:{"click .add":"addEntry"},render:function(){this.child_views.forEach(t=>t.remove()),this.$el.html(""),this.child_views=[],this.update(),this.collection.each(t=>{const e=new this.entryclass({model:t,collection:this.collection,upstreamCollection:this.upstreamCollection});this.child_views.push(e),this.$el.append(e.$el),e.render()}),this.$el.append($('\n
    \n \n
    '))},update:function(){this.dataField.data("data",this.collection.toJSON())},addEntry:function(t){t.preventDefault(),this.collection.add(this.createModel())}});var s=Backbone.Collection.extend({url:"/api/nginx/settings/searchupstream",parse:function(t){return t.rows}});const a=Backbone.View.extend({tagName:"div",attributes:{class:"row"},events:{"keyup .key":function(){this.model.set("hostname",this.key.value)},"change .value":function(){this.model.set("upstream",this.value.value)},"click .delete":"deleteEntry"},key:null,value:null,delBtn:null,first:null,second:null,third:null,upstreamCollection:null,initialize:function(t){this.upstreamCollection=t.upstreamCollection,this.listenTo(this.upstreamCollection,"update reset add remove",this.regenerate_list),this.first=document.createElement("div"),this.first.classList.add("col-sm-5"),this.key=document.createElement("input"),this.first.append(this.key),this.key.type="text",this.key.classList.add("key"),this.key.value=this.model.get("hostname"),this.second=document.createElement("div"),this.second.classList.add("col-sm-5"),this.value=document.createElement("select"),this.second.append(this.value),this.value.classList.add("value"),this.value.classList.add("form-control"),this.value.value=this.model.get("upstream"),this.third=document.createElement("div"),this.third.classList.add("col-sm-2"),this.third.style.textAlign="right",this.delBtn=document.createElement("button"),this.delBtn.classList.add("delete"),this.delBtn.classList.add("btn"),this.delBtn.innerHTML='',this.third.append(this.delBtn),this.model.has("upstream")&&0!==this.upstreamCollection.where({uuid:this.model.get("upstream")}).length||this.upstreamCollection.length>0&&this.model.set("upstream",this.upstreamCollection.at(0).get("uuid")),this.$el.append(this.first).append(this.second).append(this.third)},render:function(){$(this.key).val(this.model.get("hostname")),this.regenerate_list(),$(this.value).val(this.model.get("upstream"))},deleteEntry:function(t){t.preventDefault(),this.collection.remove(this.model)},regenerate_list:function(){const t=$(this.value);t.html(""),this.upstreamCollection.each(e=>t.append(``)),t.val(this.model.get("upstream")),t.selectpicker("refresh")}}),l=Backbone.View.extend({tagName:"div",attributes:{class:"row"},events:{"keyup .key":function(){this.model.set("network",this.key.value)},"change .value":function(){this.model.set("action",this.value.value)},"click .delete":"deleteEntry"},key:null,value:null,delBtn:null,first:null,second:null,third:null,upstreamCollection:null,initialize:function(t){this.upstreamCollection=t.upstreamCollection,this.listenTo(this.upstreamCollection,"update reset add remove",this.regenerate_list),this.first=document.createElement("div"),this.first.classList.add("col-sm-5"),this.key=document.createElement("input"),this.first.append(this.key),this.key.type="text",this.key.classList.add("key"),this.key.value=this.model.get("network"),this.second=document.createElement("div"),this.second.classList.add("col-sm-5"),this.value=document.createElement("select"),this.second.append(this.value),this.value.classList.add("value"),this.value.classList.add("form-control"),this.value.value=this.model.get("action"),this.third=document.createElement("div"),this.third.classList.add("col-sm-2"),this.third.style.textAlign="right",this.delBtn=document.createElement("button"),this.delBtn.classList.add("delete"),this.delBtn.classList.add("btn"),this.delBtn.innerHTML='',this.third.append(this.delBtn),this.$el.append(this.first).append(this.second).append(this.third)},render:function(){$(this.key).val(this.model.get("network")),this.regenerate_list(),$(this.value).val(this.model.get("action"))},deleteEntry:function(t){t.preventDefault(),this.collection.remove(this.model)},regenerate_list:function(){const t=$(this.value);t.html(""),this.upstreamCollection.each(e=>t.append(``)),t.val(this.model.get("action")),t.selectpicker("refresh")}});var o=Backbone.Collection.extend({initialize:function(){let t=this;$("#snihostname\\.data").change(function(){t.regenerateFromView()})},regenerateFromView:function(){let t=$("#snihostname\\.data").data("data");_.isArray(t)||(t=[]),this.reset(t)}}),r=Backbone.Model.extend({}),d=Backbone.Model.extend({}),c=Backbone.Collection.extend({initialize:function(){let t=this;$("#ipacl\\.data").change(function(){t.regenerateFromView()})},regenerateFromView:function(){let t=$("#ipacl\\.data").data("data");_.isArray(t)||(t=[]),this.reset(t)}});const u=new s,h=new Backbone.Collection([{name:"Deny",value:"deny"},{name:"Allow",value:"allow"}]);$(document).ready(function(){mapDataToFormUI({frm_nginx:"/api/nginx/settings/get"}).done(function(){formatTokenizersUI(),$('select[data-allownew="false"]').selectpicker("refresh"),updateServiceControlUI("nginx")}),""!==window.location.hash&&$('a[href="'+window.location.hash+'"]').click(),$(".nav-tabs a").on("shown.bs.tab",function(t){history.pushState(null,null,t.target.hash)}),$(".reload_btn").click(function(){$(".reloadAct_progress").addClass("fa-spin"),ajaxCall("/api/nginx/service/reconfigure",{},function(){$(".reloadAct_progress").removeClass("fa-spin")})}),$('[id*="save_"]').each(function(){$(this).click(function(){let t=$(this).closest("form").attr("id"),e=$(this).closest("form").attr("data-title");saveFormToEndpoint("/api/nginx/settings/set",t,function(){$("#"+t+"_progress").addClass("fa fa-spinner fa-pulse"),ajaxCall("/api/nginx/service/reconfigure",{},function(n,i){$("#"+t+"_progress").removeClass("fa fa-spinner fa-pulse"),void 0===n||"success"===i&&"ok"===n.status?updateServiceControlUI("nginx"):BootstrapDialog.show({type:BootstrapDialog.TYPE_WARNING,title:e,message:JSON.stringify(n),draggable:!0})})})})}),["upstream","upstreamserver","location","credential","userlist","httpserver","streamserver","httprewrite","custompolicy","security_header","ipacl","limit_zone","cache_path","limit_request_connection","snifwd","errorpage","tls_fingerprint","resolver","syslog_target","naxsirule"].forEach(function(t){$("#grid-"+t).UIBootgrid({search:"/api/nginx/settings/search"+t,get:"/api/nginx/settings/get"+t+"/",set:"/api/nginx/settings/set"+t+"/",add:"/api/nginx/settings/add"+t+"/",del:"/api/nginx/settings/del"+t+"/",commands:{copy_uuid:{method:function(t){navigator.clipboard.writeText($(this).data("row-id"))}}},options:{selection:!1,multiSelect:!1,formatters:{commands:function(t,e){return' '},response:function(t,e){return"none"==e.response?"unchanged":e.response},statuscodes:function(t,e){const n=[],i=e.statuscodes.split(",");for(let t of i)n.push(t.substr(0,3));return n.join(", ")}}}})}),bind_naxsi_rule_dl_button(),function(){let t=new i({dataField:document.getElementById("snihostname.data"),upstreamCollection:u,entryclass:a,collection:new o,createModel:function(){return new r({hostname:"localhost"})}});window.snifield=t,t.render(),$("#grid-upstream").on("loaded.rs.jquery.bootgrid",function(){u.fetch()}),u.fetch()}();let t=new i({dataField:document.getElementById("ipacl.data"),upstreamCollection:h,entryclass:l,collection:new c,createModel:function(){return new d({network:"::",action:"deny"})}});window.ipaclfield=t,t.render()})}}); \ No newline at end of file diff --git a/www/nginx/src/opnsense/www/js/nginx/src/nginx_config.js b/www/nginx/src/opnsense/www/js/nginx/src/nginx_config.js index c2719990e..e61a2469b 100644 --- a/www/nginx/src/opnsense/www/js/nginx/src/nginx_config.js +++ b/www/nginx/src/opnsense/www/js/nginx/src/nginx_config.js @@ -70,6 +70,7 @@ function init_grids() { 'snifwd', 'errorpage', 'tls_fingerprint', + 'resolver', 'syslog_target', 'naxsirule'].forEach(function (element) { $("#grid-" + element).UIBootgrid(