www/nginx: 1.33 (#3678)

* add resolver directive support

* add GUI config preview

* gzip disabling support

* move bots UA to GUI

* Add auto-ban log

* ban.volt: show readable time in grid

* vts.css: add tables headers borders

* Add Upstream keepalives

* constraint fix

* ver bump

* Add Stream proxy_connect_timeout and proxy_timeout support

* maintainer's suggestions

Co-Authored-By: Fabian Franz BSc <fabianfrz@users.noreply.github.com>

* Keepalive vs sockets additional help text

* no moment.js needed

* reflect core selectpicker update

* maintainer's suggestions

Co-Authored-By: Fabian Franz BSc <fabianfrz@users.noreply.github.com>

* Update www/nginx/src/opnsense/mvc/app/views/OPNsense/Nginx/index.volt

Co-authored-by: Fabian Franz BSc <fabianfrz@users.noreply.github.com>

* maintainer's suggestions

Co-Authored-By: Fabian Franz BSc <fabianfrz@users.noreply.github.com>

* refactor a bit

-don't use global vars unnecessarily
-move css to separate file
-don't touch DOM for clipboard copy. use stored value
-don't use <pre> if violating standard
-add Button label translation
-check if clipboard write supported before link show()
-use text() instead of html() if possible
-change 'DOM ready' handler syntax
thnx @fabianfrz

* safety first

use lodash _.unescape to safely decode string without DOM manipulations

---------

Co-authored-by: Fabian Franz BSc <fabianfrz@users.noreply.github.com>
This commit is contained in:
kulikov-a
2024-07-10 10:08:47 +02:00
committed by GitHub
co-authored by Fabian Franz BSc
parent 9c6261c37c
commit 105d68920b
26 changed files with 637 additions and 53 deletions
+12
View File
@@ -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)
@@ -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");
}
@@ -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);
}
}
@@ -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 =
@@ -187,6 +187,13 @@
<advanced>true</advanced>
<help>Prefers server ciphers over client ciphers.</help>
</field>
<field>
<id>httpserver.resolver</id>
<label>Resolver</label>
<type>dropdown</type>
<advanced>true</advanced>
<help><![CDATA[Specify resolver for Upstream names resolution, OCSP stapling etc. Uses system resolver if not specified but can produce warning messages in log.]]></help>
</field>
<field>
<id>httpserver.ocsp_stapling</id>
<label>OCSP Stapling</label>
@@ -208,6 +215,13 @@
<advanced>true</advanced>
<help>Blocks files like .htaccess files or other files not intended for the public.</help>
</field>
<field>
<id>httpserver.disable_gzip</id>
<label>Disable gzip</label>
<type>checkbox</type>
<advanced>true</advanced>
<help>Disables responses gzipping. Makes sense if TLS is used and this method of BREACH attack protection is preferred.</help>
</field>
<field>
<id>httpserver.disable_bot_protection</id>
<label>Disable Bot Protection</label>
@@ -241,7 +241,14 @@
<label>WebSocket Support</label>
<type>checkbox</type>
<advanced>true</advanced>
<help>If you enable the WebSocket Support option, nginx will pass the upgrade header to the backed server.</help>
<help>If you enable the WebSocket Support option, nginx will pass the upgrade header to the backed server. Mutually exclusive with Keepalive support.</help>
</field>
<field>
<id>location.upstream_keepalive</id>
<label>Upstream Keepalive Support</label>
<type>checkbox</type>
<advanced>true</advanced>
<help>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.</help>
</field>
<field>
<id>location.proxy_read_timeout</id>
@@ -0,0 +1,44 @@
<form>
<field>
<id>resolver.description</id>
<label>Description</label>
<type>text</type>
<help>Brief description for reference.</help>
</field>
<field>
<id>resolver.address</id>
<label>Address(es)</label>
<allownew>true</allownew>
<style>tokenize</style>
<type>select_multiple</type>
<help>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".</help>
</field>
<field>
<id>resolver.ipv4_off</id>
<label>Disable IPv4</label>
<type>checkbox</type>
<help>Do not request IPv4 addresses.</help>
<advanced>true</advanced>
</field>
<field>
<id>resolver.ipv6_off</id>
<label>Disable IPv6</label>
<type>checkbox</type>
<help>Do not request IPv6 addresses.</help>
<advanced>true</advanced>
</field>
<field>
<id>resolver.valid</id>
<label>Validity Time</label>
<type>text</type>
<help>Override response TTL in seconds.</help>
<advanced>true</advanced>
</field>
<field>
<id>resolver.timeout</id>
<label>Time Out</label>
<type>text</type>
<help>Resolver time out in seconds. NGINX default (if empty) is 30 seconds.</help>
<advanced>true</advanced>
</field>
</form>
@@ -66,6 +66,20 @@
<type>text</type>
<advanced>true</advanced>
</field>
<field>
<id>nginx.http.bots_ua</id>
<label>Bots User Agents</label>
<type>select_multiple</type>
<style>tokenize</style>
<allownew>true</allownew>
<help>List of bot user agents that will be used in bot protection. Uses a well known list by default.</help>
</field>
<field>
<id>nginx.http.log_perm_ban</id>
<label>Log Banned Requests</label>
<type>checkbox</type>
<help>Log requests that led to auto-blocking (bots and honeypots).</help>
</field>
<field>
<id>nginx.http.ban_response</id>
<label>Autoban Response Code</label>
@@ -41,6 +41,20 @@
<advanced>true</advanced>
<help>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.</help>
</field>
<field>
<id>streamserver.proxy_connect_timeout</id>
<label>Proxy Connect Timeout</label>
<type>text</type>
<advanced>true</advanced>
<help>Defines a timeout (in seconds) for establishing a connection with a proxied server.</help>
</field>
<field>
<id>streamserver.proxy_timeout</id>
<label>Proxy Timeout</label>
<type>text</type>
<advanced>true</advanced>
<help>Sets the timeout (in seconds) between two successive read or write operations on client or proxied server connections.</help>
</field>
<field>
<id>streamserver.certificate</id>
<label>TLS Certificate</label>
@@ -23,6 +23,26 @@
<advanced>true</advanced>
<help>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.</help>
</field>
<field>
<id>upstream.keepalive</id>
<label>Keepalive</label>
<type>text</type>
<help>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.</help>
</field>
<field>
<id>upstream.keepalive_requests</id>
<label>Keepalive requests</label>
<type>text</type>
<advanced>true</advanced>
<help>Sets the maximum number of requests that can be served through one keepalive connection. NGINX default is 1000.</help>
</field>
<field>
<id>upstream.keepalive_timeout</id>
<label>Keepalive timeout</label>
<type>text</type>
<advanced>true</advanced>
<help>Sets a timeout in seconds during which an idle keepalive connection to an upstream server will stay open. NGINX default is 75s.</help>
</field>
<field>
<id>upstream.host_port</id>
<label>Host header port</label>
@@ -1,6 +1,6 @@
<model>
<mount>//OPNsense/Nginx</mount>
<version>1.32.1</version>
<version>1.33</version>
<description>nginx web server, reverse proxy and waf</description>
<items>
<general>
@@ -65,6 +65,14 @@
<Required>Y</Required>
<default>403</default>
</ban_response>
<log_perm_ban type="BooleanField">
<default>0</default>
<Required>Y</Required>
</log_perm_ban>
<bots_ua type="CSVListField">
<Required>Y</Required>
<default>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+</default>
</bots_ua>
<headers_more_enable type="BooleanField">
<Required>N</Required>
</headers_more_enable>
@@ -120,6 +128,18 @@
<BlankDesc>Weighted Round Robin</BlankDesc>
<Required>N</Required>
</load_balancing_algorithm>
<keepalive type="IntegerField">
<MinimumValue>0</MinimumValue>
<Required>N</Required>
</keepalive>
<keepalive_requests type="IntegerField">
<MinimumValue>1</MinimumValue>
<Required>N</Required>
</keepalive_requests>
<keepalive_timeout type="IntegerField">
<MinimumValue>1</MinimumValue>
<Required>N</Required>
</keepalive_timeout>
<host_port type="IntegerField">
<MinimumValue>1</MinimumValue>
<Required>N</Required>
@@ -420,7 +440,25 @@
<websocket type="BooleanField">
<Required>Y</Required>
<default>0</default>
<Constraints>
<check001>
<ValidationMessage>WebSocket and Upstream Keepalive support can not be combined.</ValidationMessage>
<type>SingleSelectConstraint</type>
<addFields>
<field1>upstream_keepalive</field1>
</addFields>
</check001>
</Constraints>
</websocket>
<upstream_keepalive type="BooleanField">
<Required>Y</Required>
<default>0</default>
<Constraints>
<check001>
<reference>websocket.check001</reference>
</check001>
</Constraints>
</upstream_keepalive>
<proxy_buffer_size type="IntegerField">
<Required>N</Required>
<minValue>1</minValue>
@@ -863,6 +901,18 @@
<default>1</default>
<Required>Y</Required>
</tls_prefer_server_ciphers>
<resolver type="ModelRelationField">
<Model>
<template>
<source>OPNsense.Nginx.Nginx</source>
<items>resolver</items>
<display>description</display>
</template>
</Model>
<ValidationMessage>Selected resolver not found</ValidationMessage>
<Required>N</Required>
<multiple>N</multiple>
</resolver>
<ocsp_stapling type="BooleanField">
<default>0</default>
<Required>Y</Required>
@@ -879,6 +929,10 @@
<default>0</default>
<Required>Y</Required>
</disable_bot_protection>
<disable_gzip type="BooleanField">
<default>0</default>
<Required>Y</Required>
</disable_gzip>
<naxsi_whitelist_srcip type="CSVListField">
<Required>N</Required>
<mask>/^((?:\d+\.){3,3}\d+|[a-f0-9\:]+)(?:\/\d+)?(,?(?:(?:(\d+\.){3,3}\d+|[a-f0-9\:]+)(?:\/\d+)?))*$/i</mask>
@@ -1121,6 +1175,14 @@
<Required>N</Required>
<MinimumValue>0</MinimumValue>
</proxy_responses>
<proxy_connect_timeout type="IntegerField">
<Required>N</Required>
<MinimumValue>0</MinimumValue>
</proxy_connect_timeout>
<proxy_timeout type="IntegerField">
<Required>N</Required>
<MinimumValue>0</MinimumValue>
</proxy_timeout>
</stream_server>
<sni_hostname_upstream_map type="ArrayField">
@@ -1199,6 +1261,47 @@
</action>
</ip_acl_item>
<resolver type="ArrayField">
<description type="TextField">
<Required>Y</Required>
<mask>/^[^" \t]+$/i</mask>
</description>
<address type="CSVListField">
<Required>Y</Required>
<multiple>Y</multiple>
<mask>/^(?:(?:(?:\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</mask>
<ValidationMessage>Please provide a valid resolver address, i.e. 8.8.8.8, [2001:4860:4860::8888], 8.8.8.8:5353.</ValidationMessage>
</address>
<valid type="IntegerField">
<MinimumValue>1</MinimumValue>
<Required>N</Required>
</valid>
<ipv4_off type="BooleanField">
<Required>N</Required>
<Constraints>
<check001>
<ValidationMessage>Can not disable all record types.</ValidationMessage>
<type>SingleSelectConstraint</type>
<addFields>
<field1>ipv6_off</field1>
</addFields>
</check001>
</Constraints>
</ipv4_off>
<ipv6_off type="BooleanField">
<Required>N</Required>
<Constraints>
<check001>
<reference>ipv4_off.check001</reference>
</check001>
</Constraints>
</ipv6_off>
<timeout type="IntegerField">
<MinimumValue>1</MinimumValue>
<Required>N</Required>
</timeout>
</resolver>
<http_rewrite type="ArrayField">
<description type="TextField">
<Required>Y</Required>
@@ -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 `<button type="button" class="btn btn-xs btn-default command-delete" data-row-id="${row.uuid}"><span class=\"fa fa-unlock-alt\"></span></button>`;
@@ -26,6 +26,10 @@
#}
<script>
{% set placeholder_txt = 'Click the Show Config button to load the current configuration. Please note that this is not a configuration from the running process memory. ' %}
{% set placeholder_txt = placeholder_txt ~ 'Also, the displayed configuration may differ from the configuration in OPNsense config if you have made but not applied changes.' %}
ngnx_config = [];
function bind_naxsi_rule_dl_button() {
let naxsi_rule_download_button = $('#naxsiruledownloadbtn');
naxsi_rule_download_button.click(function () {
@@ -55,42 +59,100 @@
});
});
}
function ngnx_show_conf() {
$("#nginx_conf tbody").empty().append('<tr><td class="placeholdertd">{{ lang._("Waiting for response..") }}</td></tr>');
$("#config_help_text").hide();
// clear existing config in memory (if any)
ngnx_config = [];
ajaxCall(url="/api/nginx/settings/showconfig/", sendData={}, callback=function(data,status) {
if (data['time'] && data['config']) {
let L = 0;
let content = [];
$.each(data['config'], function(index, line) {
// use lodash unescape to safely decode html chars in line and store for clipboard copy
ngnx_config.push(_.unescape(line));
L = line.indexOf('# configuration file ') > -1 ? 0 : L + 1;
// line received HTML-encoded. Should be XSS-safe if not decoded before inserting to DOM
content.push('<tr><td class="l-number">' + L.toString() + '</td><td class="config-line"><span>' + line + '</span></td></tr>');
});
$("#nginx_conf tbody").empty().append(content.join());
$("#config_help_text").show();
if ((typeof navigator.clipboard === 'object') && (typeof navigator.clipboard.writeText === 'function')) {
$('#nginx_config_copy').show();
}
BootstrapDialog.show({
type: BootstrapDialog.TYPE_INFO,
title: "{{ lang._('NGINX config loaded successfully') }}",
message: "{{ lang._('NGINX config loaded. Config file created at') }}" + ": " + (new Date(data['time']*1000)).toLocaleString(),
buttons: [{
label: '{{ lang._('Ok') }}',
action: function(dlg){
dlg.close();
}
}]
});
} else {
$("#nginx_conf td.placeholdertd").text("{{ lang._('Empty response from the backend. Please check logs.') }}");
}
});
}
function ngnx_test_conf() {
ajaxCall(url="/api/nginx/settings/testconfig/", sendData={}, callback=function(data,status) {
if (data['response'].indexOf('test failed') > -1) {
BootstrapDialog.show({
type: BootstrapDialog.TYPE_DANGER,
title: "{{ lang._('NGINX config test failed') }}",
message: data['response'],
buttons: [{
label: '{{ lang._('Ok') }}',
action: function(dlg){
dlg.close();
}
}]
});
} else {
BootstrapDialog.show({
type: BootstrapDialog.TYPE_INFO,
title: "{{ lang._('NGINX config test is successful') }}",
message: "{{ lang._('NGINX config test is successful') }}",
buttons: [{
label: '{{ lang._('Ok') }}',
action: function(dlg){
dlg.close();
}
}]
});
}
});
}
$(function() {
$("#nginx_config_copy").click(function () {
if (ngnx_config.length) {
$(this).fadeOut();
navigator.clipboard.writeText(ngnx_config.join('\n'));
$(this).fadeIn();
}
});
$("#subtab_item_nginx-other-config-preview").click(function () {
$("#nginx_conf tbody").empty().append('<tr><td class="placeholdertd">{{ lang._(placeholder_txt) }}</td></tr>');
});
$("#conf_show_btn").click(function () {
ngnx_show_conf();
});
$("#conf_test_btn").click(function () {
ngnx_test_conf();
});
});
</script>
<script src="{{ cache_safe('/ui/js/nginx/lib/lodash.min.js') }}"></script>
<script src="{{ cache_safe('/ui/js/nginx/lib/backbone-min.js') }}"></script>
<script src="{{ cache_safe('/ui/js/nginx/dist/configuration.min.js') }}"></script>
<style>
#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;
}
.filter-option {
padding: inherit !important;
}
</style>
<link rel="stylesheet" href="{{ cache_safe('/ui/css/nginx/index.css') }}" type="text/css" />
<ul class="nav nav-tabs" role="tablist" id="maintabs">
{{ partial("layout_partials/base_tabs_header",['formData':settings]) }}
@@ -137,6 +199,9 @@
<li>
<a data-toggle="tab" id="subtab_item_nginx-http-tls-fingerprint" href="#subtab_nginx-http-tls-fingerprint">{{ lang._('TLS Fingerprint (Advanced)')}}</a>
</li>
<li>
<a data-toggle="tab" id="subtab_item_nginx-http-resolver" href="#subtab_nginx-http-resolver">{{ lang._('Resolvers')}}</a>
</li>
</ul>
</li>
<li role="presentation" class="dropdown">
@@ -213,6 +278,9 @@
<li>
<a data-toggle="tab" id="subtab_item_nginx-other-syslog-target" href="#subtab_nginx-other-syslog-target">{{ lang._('SYSLOG Targets')}}</a>
</li>
<li>
<a data-toggle="tab" id="subtab_item_nginx-other-config-preview" href="#subtab_nginx-other-config-preview">{{ lang._('Config Preview')}}</a>
</li>
</ul>
</li>
</ul>
@@ -671,6 +739,29 @@
</tfoot>
</table>
</div>
<div id="subtab_nginx-http-resolver" class="tab-pane fade">
<table id="grid-resolver" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="resolverdlg">
<thead>
<tr>
<th data-column-id="uuid" data-type="string" data-sortable="true" data-visible="false">{{ lang._('ID') }}</th>
<th data-column-id="description" data-type="string" data-sortable="true" data-visible="true">{{ lang._('Description') }}</th>
<th data-column-id="address" data-type="string" data-sortable="true" data-visible="true">{{ lang._('Address') }}</th>
<th data-column-id="commands" data-width="10em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
</tr>
</thead>
<tbody>
</tbody>
<tfoot>
<tr>
<td></td>
<td>
<button data-action="add" type="button" class="btn btn-xs btn-default"><span class="fa fa-plus"></span></button>
<button type="button" class="btn btn-xs reload_btn btn-primary"><span class="fa fa-refresh reloadAct_progress"></span></button>
</td>
</tr>
</tfoot>
</table>
</div>
<div id="subtab_nginx-other-syslog-target" class="tab-pane fade">
<table id="grid-syslog_target" class="table table-condensed table-hover table-striped table-responsive" data-editDialog="syslog_target_dlg">
<thead>
@@ -695,9 +786,31 @@
</tfoot>
</table>
</div>
<div id="subtab_nginx-other-config-preview" class="tab-pane fade">
<div id="nginx_conf_container" class="nginx_table_responsive">
<table class="ngx_conf_table" id="nginx_conf">
<tbody class="ngx_conf_table_body"></tbody>
</table>
<table class="table table-striped table-condensed">
<tbody>
<tr>
<td>
<div id="config_help_text" style="display:none">
{{ lang._("Configuration files may contain sensitive information, keep it safe.") }}
<a id="nginx_config_copy" style="display:none">{{ lang._('Click here to copy to clipboard.') }}</a>
</div>
<div>
<button class="btn btn-primary" id="conf_show_btn" data-type="config" type="button"><b>{{ lang._('Show Config') }}</b></button>
<button class="btn btn-primary" id="conf_test_btn" data-type="test" type="button"><b>{{ lang._('Test Config') }}</b></button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
{{ 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')]) }}
@@ -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':
@@ -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))
@@ -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
@@ -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':
@@ -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
@@ -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 %}
@@ -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 %}

Some files were not shown because too many files have changed in this diff Show More