www/nginx: 1.32 (#3205)

* add support for resetting timed out connections and 444 responses (reset_timedout_connection)
* add option to change autoban response code to 444 (NGX_HTTP_CLOSE)
* add ssl_reject_handshake directive support
* add error log severity level support for HTTP and Stream servers
* add logging of possible errors causes to the setup script
* $internalModelUseSafeDelete enabled in API Settings controller to check item references before delete
* minor style adjustments for IP ACL and SNI Based Routing forms
* fix: add the PROXY protocol for the HTTPS listener too, if it is set for the server
* fix: set Stream server outbound PROXY protocol based on Upstream settings
* fix: set Trusted Proxies (set_real_ip_from) for Stream server only with PROXY protocol enabled
  WARNING: set_real_ip_from directive is temporary disabled due to ngx_stream_realip module missing
* fix: do not include commented out core rules in naxsi policies when importing
* fix: fixed a typo in setting the proxy_ssl_session_reuse directive value (thanks to Sigurd Våg Aaknes)
* use syslog
* enable set_real_ip_from
* move config test to setup script
This commit is contained in:
kulikov-a
2023-03-29 09:44:27 +02:00
committed by GitHub
parent 2379cefe4e
commit 7124198658
18 changed files with 190 additions and 29 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
PLUGIN_NAME= nginx
PLUGIN_VERSION= 1.31
PLUGIN_VERSION= 1.32
PLUGIN_COMMENT= Nginx HTTP server and reverse proxy
PLUGIN_DEPENDS= nginx
PLUGIN_MAINTAINER= franz.fabian.94@gmail.com
+16
View File
@@ -10,6 +10,22 @@ WWW: https://nginx.org/
Plugin Changelog
================
1.32
* add support for resetting timed out connections and 444 responses (reset_timedout_connection)
* add option to change autoban response code to 444 (NGX_HTTP_CLOSE)
* add ssl_reject_handshake directive support
* add error log severity level support for HTTP and Stream servers
* add logging of possible errors causes to the setup script
* migrate general error log to syslog
* $internalModelUseSafeDelete enabled in API Settings controller to check item references before delete
* minor style adjustments for IP ACL and SNI Based Routing forms
* fix: add the PROXY protocol for the HTTPS listener too, if it is set for the server
* fix: set Stream server outbound PROXY protocol based on Upstream settings
* fix: set Trusted Proxies (set_real_ip_from) for Stream server only with PROXY protocol enabled
* fix: do not include commented out core rules in naxsi policies when importing
* fix: fixed a typo in setting the proxy_ssl_session_reuse directive value (thanks to Sigurd Våg Aaknes)
1.31
* Allow dynamic proxy_ssl_name (contributed by Mike Reiche)
@@ -26,6 +26,21 @@
POSSIBILITY OF SUCH DAMAGE.
*/
/**
* register syslog facilities
* @return array
*/
function nginx_syslog()
{
$syslogconf = array();
$syslogconf['nginx'] = array(
'facility' => array('nginx'),
);
return $syslogconf;
}
function nginx_cron()
{
return array(
@@ -35,6 +35,7 @@ class SettingsController extends ApiMutableModelControllerBase
{
protected static $internalModelClass = '\OPNsense\Nginx\Nginx';
protected static $internalModelName = 'nginx';
protected static $internalModelUseSafeDelete = true;
// download rules
public function downloadrulesAction()
@@ -22,6 +22,6 @@
<label>Flag</label>
<type>dropdown</type>
<style>selectpicker</style>
<help>Stop rule processing (break) and perform (internal) redirect (last). Return a moved permanently status code (301) or a teporary redirect (302).</help>
<help>Stop rule processing (break) or perform (internal) redirect (last). Return a moved permanently status code (301) or a temporary redirect (302).</help>
</field>
</form>
@@ -20,6 +20,13 @@
<label>Default Server</label>
<type>checkbox</type>
</field>
<field>
<id>httpserver.tls_reject_handshake</id>
<label>Reject SSL Handshake</label>
<type>checkbox</type>
<help>If enabled, TLS handshakes for this server will be rejected.</help>
<advanced>true</advanced>
</field>
<field>
<id>httpserver.syslog_targets</id>
<label>SYSLOG targets</label>
@@ -127,6 +134,14 @@
<label>Access Log Format</label>
<type>dropdown</type>
</field>
<field>
<id>httpserver.error_log_level</id>
<label>Error Log Level</label>
<style>selectpicker</style>
<type>dropdown</type>
<help>Select Error Log Level. Log levels are listed in the order of increasing verbosity. Setting a certain log level will cause all messages of the specified and more severe log levels to be logged.</help>
<advanced>true</advanced>
</field>
<field>
<id>httpserver.enable_acme_support</id>
<label>Enable Let's Encrypt Plugin Support</label>
@@ -42,6 +42,12 @@
<type>text</type>
<help>After this idle time, the client gets disconnected.</help>
</field>
<field>
<id>nginx.http.reset_timedout</id>
<label>Reset Timed Out Connections</label>
<type>checkbox</type>
<help>Reset timed out connections and connections closed with the non-standard code 444. When the socket is closed, TCP RST is sent to the client, and all memory occupied by this socket is released. This helps avoid keeping an already closed socket with filled buffers in a FIN_WAIT1 state for a long time.</help>
</field>
<field>
<id>nginx.http.default_type</id>
<label>Default MIME-Type</label>
@@ -60,6 +66,14 @@
<type>text</type>
<advanced>true</advanced>
</field>
<field>
<id>nginx.http.ban_response</id>
<label>Autoban Response Code</label>
<style>selectpicker</style>
<type>dropdown</type>
<help>Select a response code for auto-blocking requests (bot user-agent or honeypot location). The default code is 403. 444 is a special response code that closes the connection without a response to the client.</help>
<advanced>true</advanced>
</field>
<field>
<id>nginx.http.headers_more_enable</id>
<label>Enable Headers More module</label>
@@ -63,6 +63,14 @@
<label>Access Log Format</label>
<type>dropdown</type>
</field>
<field>
<id>streamserver.error_log_level</id>
<label>Error Log Level</label>
<style>selectpicker</style>
<type>dropdown</type>
<help>Select Error Log Level. Log levels are listed in the order of increasing verbosity. Setting a certain log level will cause all messages of the specified and more severe log levels to be logged.</help>
<advanced>true</advanced>
</field>
<field>
<id>streamserver.route_field</id>
<label>Route With</label>
@@ -9,7 +9,7 @@
<HTTPErrorLogs VisibleName="Logs / HTTP Error" url="/ui/nginx/logs/errors" order="110"/>
<StreamAccessLogs VisibleName="Logs / Stream Access" url="/ui/nginx/logs/stream_accesses" order="120"/>
<StreamErrorLogs VisibleName="Logs / Stream Error" url="/ui/nginx/logs/stream_errors" order="130"/>
<ErrorLogs VisibleName="Logs / Global Error" url="/ui/nginx/logs" order="140"/>
<LogFile VisibleName="Log File" order="140" url="/ui/diagnostics/log/core/nginx"/>
</Nginx>
</Services>
</menu>
@@ -1,6 +1,6 @@
<model>
<mount>//OPNsense/Nginx</mount>
<version>1.30</version>
<version>1.32</version>
<description>nginx web server, reverse proxy and waf</description>
<items>
<general>
@@ -41,6 +41,10 @@
<default>60</default>
<Required>N</Required>
</keepalive_timeout>
<reset_timedout type="BooleanField">
<default>0</default>
<Required>Y</Required>
</reset_timedout>
<default_type type="TextField">
<Required>N</Required>
</default_type>
@@ -52,6 +56,15 @@
<Required>N</Required>
<MinimumValue>1</MinimumValue>
</server_names_hash_max_size>
<ban_response type="OptionField">
<multiple>N</multiple>
<OptionValues>
<http_403 value="403">403 Forbidden</http_403>
<http_444 value="444">444 Terminate Connection</http_444>
</OptionValues>
<Required>Y</Required>
<default>403</default>
</ban_response>
<headers_more_enable type="BooleanField">
<Required>N</Required>
</headers_more_enable>
@@ -698,6 +711,10 @@
</check001>
</Constraints>
</default_server>
<tls_reject_handshake type="BooleanField">
<default>0</default>
<Required>Y</Required>
</tls_reject_handshake>
<proxy_protocol type="BooleanField">
<default>0</default>
<Required>Y</Required>
@@ -786,6 +803,20 @@
</OptionValues>
<Required>Y</Required>
</access_log_format>
<error_log_level type="OptionField">
<multiple>N</multiple>
<OptionValues>
<emerg value="emerg">Emergency</emerg>
<alert value="alert">Alert</alert>
<crit value="crit">Critical</crit>
<error value="error">Error (default)</error>
<warn value="warn">Warning</warn>
<notice value="notice">Notice</notice>
<info value="info">Informational</info>
</OptionValues>
<Required>Y</Required>
<default>error</default>
</error_log_level>
<enable_acme_support type="BooleanField">
<Required>Y</Required>
<default>1</default>
@@ -1004,6 +1035,20 @@
</OptionValues>
<Required>Y</Required>
</access_log_format>
<error_log_level type="OptionField">
<multiple>N</multiple>
<OptionValues>
<emerg value="emerg">Emergency</emerg>
<alert value="alert">Alert</alert>
<crit value="crit">Critical</crit>
<error value="error">Error</error>
<warn value="warn">Warning</warn>
<notice value="notice">Notice</notice>
<info value="info">Informational (default)</info>
</OptionValues>
<Required>Y</Required>
<default>info</default>
</error_log_level>
<route_field type="OptionField">
<default>upstream</default>
<OptionValues>
@@ -73,6 +73,10 @@
#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;
@@ -81,6 +85,9 @@
#frm_ipacl_dlg .bootstrap-select {
width: 100% !important;
}
.filter-option {
padding: inherit !important;
}
</style>
@@ -100,8 +100,13 @@ function save_to_model($data)
$policy->action = 'BLOCK';
// create new values for policy
$rule_list = [];
$dis_rules = [];
foreach ($rules as $rule) {
$rule_mdl = $model->naxsi_rule->Add();
// exclude commented rules from policy
if (str_starts_with($rule['rule'], '#')) {
$dis_rules[] = (string)$rule_mdl->getAttributes()["uuid"];
}
$rule_mdl->description = $rule['message'];
$rule_mdl->message = $rule['message'];
$rule_mdl->ruletype = 'main';
@@ -160,9 +165,8 @@ function save_to_model($data)
}
$rule_list[] = $rule_mdl->getAttributes()["uuid"];
}
$policy->naxsi_rules = implode(',', $rule_list);
$policy->naxsi_rules = implode(',', array_diff($rule_list, $dis_rules));
}
$val_result = $model->performValidation(false);
if (count($val_result) !== 0) {
print_r($val_result);
+22 -1
View File
@@ -31,6 +31,7 @@ const KEY_DIRECTORY = '/usr/local/etc/nginx/key/';
const GROUP_OWNER = 'staff';
require_once('config.inc');
require_once('certs.inc');
require_once('util.inc');
use OPNsense\Nginx\Nginx;
function export_pem_file($filename, $data, $post_append = null)
@@ -75,6 +76,7 @@ if (!isset($config['OPNsense']['Nginx'])) {
@chgrp('/var/db/nginx/auth', GROUP_OWNER);
@chgrp('/var/log/nginx', GROUP_OWNER);
$nginx = $config['OPNsense']['Nginx'];
openlog("nginx", LOG_ODELAY, LOG_USER);
if (isset($nginx['http_server'])) {
if (is_array($nginx['http_server']) && !isset($nginx['http_server']['servername'])) {
$http_servers = $nginx['http_server'];
@@ -84,8 +86,10 @@ if (isset($nginx['http_server'])) {
foreach ($http_servers as $http_server) {
if (!empty($http_server['listen_https_address']) && !empty($http_server['certificate'])) {
// try to find the reference
$hostname = explode(',', $http_server['servername'])[0];
$cert = find_cert($http_server['certificate']);
if (!isset($cert)) {
syslog(LOG_ERR, "NGINX setup: Certificate is set but not found in config for server {$hostname}.");
continue;
}
$chain = [];
@@ -94,8 +98,9 @@ if (isset($nginx['http_server'])) {
foreach ($ca_chain as $entry) {
$chain[] = base64_decode($entry['crt']);
}
} else {
syslog(LOG_WARNING, "NGINX setup: Certificate chain is empty for server {$hostname}.");
}
$hostname = explode(',', $http_server['servername'])[0];
export_pem_file(
KEY_DIRECTORY . $hostname . '.pem',
$cert['crt'],
@@ -131,6 +136,7 @@ if (isset($nginx['stream_server'])) {
// try to find the reference
$cert = find_cert($stream_server['certificate']);
if (!isset($cert)) {
syslog(LOG_ERR, "NGINX setup: Certificate is set but not found in config for stream server {$stream_server['listen_address']}.");
continue;
}
$chain = [];
@@ -139,6 +145,8 @@ if (isset($nginx['stream_server'])) {
foreach ($ca_chain as $entry) {
$chain[] = base64_decode($entry['crt']);
}
} else {
syslog(LOG_WARNING, "NGINX setup: Certificate chain is empty for stream server {$stream_server['listen_address']}.");
}
export_pem_file(
KEY_DIRECTORY . $stream_server['@attributes']['uuid'] . '.pem',
@@ -194,6 +202,8 @@ if (isset($nginx['upstream'])) {
KEY_DIRECTORY . $upstream['tls_client_certificate'] . '.key',
$cert['prv']
);
} else {
syslog(LOG_ERR, "NGINX setup: Client certificate is set but not found in config for upstream {$upstream['description']}.");
}
}
if (!empty($upstream['tls_trusted_certificate'])) {
@@ -203,6 +213,8 @@ if (isset($nginx['upstream'])) {
$ca = find_ca($caref);
if (isset($ca)) {
$cas[] = base64_decode($ca['crt']);
} else {
syslog(LOG_ERR, "NGINX setup: Trusted CA certificate is set but not found in config for upstream {$upstream['description']}.");
}
}
export_pem_file(
@@ -319,4 +331,13 @@ file_put_contents(
);
chmod('/usr/local/etc/nginx/tls_fingerprints.json', 0644);
// test config and exit early if it not good
$conf_test_errors = shell_safe('nginx -t -q 2>&1');
if (!empty($conf_test_errors)) {
syslog(LOG_EMERG, $conf_test_errors);
closelog();
exit(1);
}
closelog();
passthru('/usr/local/etc/rc.d/php-fpm start');
@@ -46,6 +46,9 @@ server_names_hash_bucket_size {{ OPNsense.Nginx.http.server_names_hash_bucket_si
{% if OPNsense.Nginx.http.keepalive_timeout is defined and OPNsense.Nginx.http.keepalive_timeout != '' %}
keepalive_timeout {{ OPNsense.Nginx.http.keepalive_timeout }};
{% endif %}
{% if OPNsense.Nginx.http.reset_timedout is defined and OPNsense.Nginx.http.reset_timedout == '1' %}
reset_timedout_connection on;
{% endif %}
map $http_upgrade $connection_upgrade {
default upgrade;
@@ -105,36 +108,42 @@ server {
{% endfor %}
{% endif %}
{% if server.listen_https_address is defined and server.listen_https_address != '' and server.certificate is defined %}
{% if server.listen_https_address is defined and server.listen_https_address != '' %}
{% for listen_address in server.listen_https_address.split(',') %}
listen {{ listen_address }} http2 ssl{% if server.default_server is defined and server.default_server == '1' %} default_server{% endif %};
listen {{ listen_address }} http2 ssl{% if server.proxy_protocol is defined and server.proxy_protocol == '1' %} proxy_protocol{% endif %}{% if server.default_server is defined and server.default_server == '1' %} default_server{% endif %};
{% endfor %}
{% if server.ca is defined %}
{% if server.tls_reject_handshake is defined and server.tls_reject_handshake == '1'%}
ssl_reject_handshake on;
{% endif %}
{% if server.certificate is defined %}
{% if server.ca is defined %}
ssl_client_certificate /usr/local/etc/nginx/key/{{ single_servername }}_ca.pem;
ssl_verify_client {{ server.verify_client }};
{% endif %}
{% if server.zero_rtt == '1' %}
{% endif %}
{% if server.zero_rtt == '1' %}
ssl_early_data on;
{% endif %}
{% endif %}
ssl_certificate_key /usr/local/etc/nginx/key/{{ single_servername }}.key;
ssl_certificate /usr/local/etc/nginx/key/{{ single_servername }}.pem;
ssl_protocols {{ server.tls_protocols.replace(',', ' ') }};
ssl_dhparam /usr/local/opnsense/data/OPNsense/Nginx/dh-parameters.4096.rfc7919;
{% if server.tls_ciphers is defined and server.tls_ciphers != '' %}
{% if server.tls_ciphers is defined and server.tls_ciphers != '' %}
ssl_ciphers {{ server.tls_ciphers }};
{% endif %}
{% if server.tls_ecdh_curve is defined and server.tls_ecdh_curve != '' %}
{% endif %}
{% if server.tls_ecdh_curve is defined and server.tls_ecdh_curve != '' %}
ssl_ecdh_curve {{ server.tls_ecdh_curve }};
{% endif %}
{% endif %}
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_prefer_server_ciphers {% if server.tls_prefer_server_ciphers is defined and server.tls_prefer_server_ciphers == '0'%}off{% else %}on{% endif %};
{% if server.ocsp_stapling is defined and server.ocsp_stapling == '1'%}
{% if server.ocsp_stapling is defined and server.ocsp_stapling == '1'%}
ssl_stapling on;
ssl_stapling_verify {% if server.ocsp_verify is defined and server.ocsp_verify == '1' %}On{% else %}Off{% endif %};
{% else %}
{% else %}
ssl_stapling off;
{% endif %}
{% endif %}
{% endif %}
@@ -172,7 +181,7 @@ server {
{% include "OPNsense/Nginx/syslog_targets.conf" %}
{% endif %}
access_log /var/log/nginx/tls_handshake.log handshake;
error_log /var/log/nginx/{{ server.servername }}.error.log;
error_log /var/log/nginx/{{ server.servername }}.error.log{% if server.error_log_level is defined %} {{ server.error_log_level }}{% endif %};
{% if server.root is defined and server.root != '' %}
root "{{server.root}}";
{% endif %}
@@ -233,9 +242,8 @@ server {
location @permanentban {
access_log /var/log/nginx/permanentban.access.log main;
internal;
add_header Content-Type text/plain;
add_header Charset utf-8;
return 403 "You got banned permanently from this server.";
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 %};
}
error_page 418 = @permanentban;
location = /waf_denied.html {
@@ -199,8 +199,8 @@ location {{ location.matchtype }} {{ location.urlpattern }} {
{% if upstream.tls_protocol_versions is defined and upstream.tls_protocol_versions != '' %}
proxy_ssl_protocols {{ upstream.tls_protocol_versions.replace(',', ' ') }};
{% endif %}
{% if upstream.tls_name_override is defined %}
proxy_ssl_session_reuse {% if upstream.tls_name_override != '0' %}off{% else %}on{% endif %};
{% if upstream.tls_session_reuse is defined %}
proxy_ssl_session_reuse {% if upstream.tls_session_reuse == '1' %}on{% else %}off{% endif %};
{% endif %}
{% if upstream.tls_trusted_certificate is defined and upstream.tls_trusted_certificate != '' %}
proxy_ssl_trusted_certificate /usr/local/etc/nginx/key/trust_upstream_{{ location.upstream }}.pem;
@@ -12,7 +12,8 @@ load_module /usr/local/libexec/nginx/ngx_http_headers_more_filter_module.so;
user www staff;
worker_processes {{ OPNsense.Nginx.http.workerprocesses }};
error_log /var/log/nginx/error.log;
#error_log /var/log/nginx/error.log;
error_log syslog:server=unix:/var/run/log,facility=local6,nohostname warn;
events {
worker_connections {{ OPNsense.Nginx.http.workerconnections }};
@@ -58,7 +58,7 @@
{% set syslog_targets = server.syslog_targets.split(',') %}
{% include "OPNsense/Nginx/syslog_targets.conf" %}
{% endif %}
error_log /var/log/nginx/stream_{{ server['@uuid'] }}.error.log info;
error_log /var/log/nginx/stream_{{ server['@uuid'] }}.error.log {% if server.error_log_level is defined and server.error_log_level != 'info'%}{{ server.error_log_level }}{% else %}info{% endif %};
{% if server.route_field == 'sni_upstream_map' %}
ssl_preread on;
@@ -100,11 +100,11 @@
{% elif server.route_field == 'sni_upstream_map' %}
proxy_pass $hostmap{{ server.sni_upstream_map.replace('-','') }};
{% endif %}
proxy_protocol {% if server.proxy_protocol == '1' %}on{% else %}off{% endif %};
proxy_protocol {% if upstream.proxy_protocol == '1' %}on{% else %}off{% endif %};
{% if server.proxy_responses is defined and server.proxy_responses != '' %}
proxy_responses {{ server.proxy_responses }};
{% endif%}
{% if server.trusted_proxies is defined and server.trusted_proxies != '' %}
{% 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 }};
{% endfor %}
@@ -0,0 +1,6 @@
###################################################################
# Local syslog-ng configuration filter definition [nginx].
###################################################################
filter f_local_nginx {
program("nginx");
};