diff --git a/www/nginx/Makefile b/www/nginx/Makefile
index 290dec87f..1343b145a 100644
--- a/www/nginx/Makefile
+++ b/www/nginx/Makefile
@@ -1,5 +1,5 @@
PLUGIN_NAME= nginx
-PLUGIN_VERSION= 1.25
+PLUGIN_VERSION= 1.26
PLUGIN_COMMENT= Nginx HTTP server and reverse proxy
PLUGIN_DEPENDS= nginx
PLUGIN_MAINTAINER= franz.fabian.94@gmail.com
diff --git a/www/nginx/README.md b/www/nginx/README.md
index e094ccd0f..e90253a91 100644
--- a/www/nginx/README.md
+++ b/www/nginx/README.md
@@ -33,11 +33,11 @@ Most are standard but some endpoints support maps, which are not
supported by OPNsense core.
You can detect them simply as they are doing more than just a mapping
-to the *base methods.
+to the \*base methods.
Such mappings work in the way that they catch up the request,
map the internal data first, and then forward their UUIDs
-to the *base method.
+to the \*base method.
## The nginx plugin as infrastucture
diff --git a/www/nginx/pkg-descr b/www/nginx/pkg-descr
index 165ae90fc..77df4834a 100644
--- a/www/nginx/pkg-descr
+++ b/www/nginx/pkg-descr
@@ -10,6 +10,13 @@ WWW: https://nginx.org/
Plugin Changelog
================
+1.26
+
+* Enhancement of security headers (contributed by Manuel Faux)
+ Add Frame-Ancestors, add "preload", removed deprecated HPKP
+* Performance enhancements for log display
+* Fixed display of vts and logs for non-default styles
+
1.25
* Reworked logging frontent to support filtering and historic view (contributed by Manuel Faux)
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 8cc0f4587..853a83291 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
@@ -342,7 +342,59 @@ class SettingsController extends ApiMutableModelControllerBase
// http security headers
public function searchsecurityHeaderAction()
{
- return $this->searchBase('security_header', array('description'));
+ $data = $this->searchBase('security_header',
+ ['description', 'referrer', 'xssprotection', 'strict_transport_security_time',
+ 'enable_csp', 'csp_report_only', 'csp_default_src_enabled', 'csp_script_src_enabled', 'csp_img_src_enabled',
+ 'csp_style_src_enabled', 'csp_media_src_enabled', 'csp_font_src_enabled', 'csp_frame_src_enabled',
+ 'csp_frame_ancestors_enabled', 'csp_form_action_enabled']);
+
+ // Create "hsts" column (disabled/time)
+ foreach ($data['rows'] as &$row) {
+ if (intval($row['strict_transport_security_time']) > 0) {
+ $row['hsts'] = sprintf(gettext("%d sec"), intval($row['strict_transport_security_time']));
+ } else {
+ $row['hsts'] = gettext('disabled');
+ }
+ }
+
+ // Create "csp" column (enabled/report only/disabled)
+ foreach ($data['rows'] as &$row) {
+ if ($row['enable_csp']) {
+ if ($row['csp_report_only']) {
+ $row['csp'] = gettext('report only');
+ } else {
+ $row['csp'] = gettext('enabled');
+ }
+ } else {
+ $row['csp'] = gettext('disabled');
+ }
+ }
+
+ // Create "csp_details" column
+ foreach ($data['rows'] as &$row) {
+ if ($row['enable_csp']) {
+ $enabled = [];
+ if ($row['csp_default_src_enabled']) $enabled[] = gettext("Default Source");
+ if ($row['csp_script_src_enabled']) $enabled[] = gettext("Script Source");
+ if ($row['csp_img_src_enabled']) $enabled[] = gettext("Image Source");
+ if ($row['csp_style_src_enabled']) $enabled[] = gettext("Style Source");
+ if ($row['csp_media_src_enabled']) $enabled[] = gettext("Media Source");
+ if ($row['csp_font_src_enabled']) $enabled[] = gettext("Font Source");
+ if ($row['csp_frame_src_enabled']) $enabled[] = gettext("Frame Source");
+ if ($row['csp_frame_ancestors_enabled']) $enabled[] = gettext("Frame Ancestors");
+ if ($row['csp_form_action_enabled']) $enabled[] = gettext("Form Action");
+
+ if (count($enabled)) {
+ $row['csp_details'] = implode(', ', $enabled);
+ } else {
+ $row['csp_details'] = gettext('none');
+ }
+ } else {
+ $row['csp_details'] = '';
+ }
+ }
+
+ return $data;
}
public function getsecurityHeaderAction($uuid = null)
diff --git a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/security_headers.xml b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/security_headers.xml
index f4004ea58..a5a2eab0b 100644
--- a/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/security_headers.xml
+++ b/www/nginx/src/opnsense/mvc/app/controllers/OPNsense/Nginx/forms/security_headers.xml
@@ -1,553 +1,689 @@
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 07411f856..ddcdcf19d 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
@@ -1194,19 +1194,10 @@
Y
1
-
- N
- /[a-z0-9\+\/=]+(,[a-z0-9\+\/=]+)*/i
-
-
+
Y
-
-
- N
-
-
- Y
-
+ 0
+
Y
@@ -1448,6 +1439,76 @@
Y
0
+
+ Y
+ 0
+
+
+ Y
+ 0
+
+
+ N
+
+
+ Y
+ 0
+
+
+ Y
+ 0
+
+
+ Y
+ 0
+
+
+ Y
+ 0
+
+
+ Y
+ 0
+
+
+ Y
+ 0
+
+
+ Y
+ 0
+
+
+ Y
+ 0
+
+
+ Y
+ 0
+
+
+ N
+
+
+ Y
+ 0
+
+
+ Y
+ 0
+
+
+ Y
+ 0
+
+
+ Y
+ 0
+
+
+ Y
+ 0
+
Y
0
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 72bd40d91..eacb21ea1 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
@@ -478,6 +478,11 @@
| {{ lang._('Description') }} |
+ {{ lang._('Referrer') }} |
+ {{ lang._('XSS Protection') }} |
+ {{ lang._('HSTS') }} |
+ {{ lang._('CSP') }} |
+ {{ lang._('CSP Rules') }} |
{{ lang._('Commands') }} |
@@ -691,7 +696,7 @@
{{ partial("layout_partials/base_dialog",['fields': httprewrite,'id':'httprewritedlg', 'label':lang._('Edit URL Rewrite')]) }}
{{ partial("layout_partials/base_dialog",['fields': naxsi_custom_policy,'id':'custompolicydlg', 'label':lang._('Edit WAF Policy')]) }}
{{ partial("layout_partials/base_dialog",['fields': naxsi_rule,'id':'naxsiruledlg', 'label':lang._('Edit Naxsi Rule')]) }}
-{{ partial("layout_partials/base_dialog",['fields': security_headers,'id':'security_headersdlg', 'label':lang._('Edit Security Headers')]) }}
+{{ partial("OPNsense/Nginx/tabbed_dialog",['fields': security_headers,'id':'security_headersdlg', 'label':lang._('Edit Security Headers')]) }}
{{ partial("layout_partials/base_dialog",['fields': limit_request_connection,'id':'limit_request_connectiondlg', 'label':lang._('Edit Request Connection Limit')]) }}
{{ partial("layout_partials/base_dialog",['fields': limit_zone,'id':'limit_zonedlg', 'label':lang._('Edit Limit Zone')]) }}
{{ partial("layout_partials/base_dialog",['fields': cache_path,'id':'cache_pathdlg', 'label':lang._('Edit Cache Path')]) }}
diff --git a/www/nginx/src/opnsense/mvc/app/views/OPNsense/Nginx/tabbed_dialog.volt b/www/nginx/src/opnsense/mvc/app/views/OPNsense/Nginx/tabbed_dialog.volt
new file mode 100644
index 000000000..0d5e75e9f
--- /dev/null
+++ b/www/nginx/src/opnsense/mvc/app/views/OPNsense/Nginx/tabbed_dialog.volt
@@ -0,0 +1,183 @@
+{#
+ # Copyright (c) 2021 Manuel Faux
+ # OPNsense® is Copyright © 2014-2021 by Deciso B.V.
+ # 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.
+ #}
+
+{#
+ # Generate input dialog, uses the following parameters (as associative array):
+ #
+ # fields : list of field type objects, see form_input_tr tag for details
+ # id : form id, used as unique id for this modal form. inner form to place data is called frm_[id]
+ # save button is identified by btn_[id]_save
+ # label : dialog label
+ #}
+
+{# Volt templates in php7 have issues with scope sometimes, copy input values to make them more unique #}
+{% set base_dialog_id=id %}
+{% set base_dialog_fields=fields %}
+{% set base_dialog_label=label %}
+
+{# Find if there are help supported or advanced field on this page #}
+{% set base_dialog_help=false %}
+{% set base_dialog_advanced=false %}
+{% for field in base_dialog_fields|default({})%}
+ {% for name,element in field %}
+ {% if name=='help' %}
+ {% set base_dialog_help=true %}
+ {% endif %}
+ {% if name=='advanced' %}
+ {% set base_dialog_advanced=true %}
+ {% endif %}
+ {% endfor %}
+ {% if base_dialog_help|default(false) and base_dialog_advanced|default(false) %}
+ {% break %}
+ {% endif %}
+{% endfor %}
+
+
+
+
+
+
+
+
+
+
+ {% for field in base_dialog_fields['tabs']|default({})%}
+ -
+ {{field[1]}}
+
+ {% endfor %}
+
+
+
+
+
+
+
diff --git a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/security_rule.conf b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/security_rule.conf
index 73ab27993..f309c4cbf 100644
--- a/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/security_rule.conf
+++ b/www/nginx/src/opnsense/service/templates/OPNsense/Nginx/security_rule.conf
@@ -1,3 +1,4 @@
+ # security rules
{% if security_rule.referrer is defined %}
{% do our_headers.append('Referrer-Policy') %}
add_header Referrer-Policy "{{ security_rule.referrer }}" always;
@@ -13,21 +14,12 @@
{% if security_rule.strict_transport_security_time is defined %}
{% do our_headers.append('Strict-Transport-Security') %}
add_header Strict-Transport-Security "max-age={{ security_rule.strict_transport_security_time }}{%
- if security_rule.strict_transport_security_include_subdomains is defined and
- security_rule.strict_transport_security_include_subdomains == '1' %}; includeSubDomains{% endif %}" always;
-{% endif %}
-{% if security_rule.hpkp_keys is defined and security_rule.hpkp_time is defined %}
-{% do our_headers.append('Public-Key-Pins') %}
-{% do our_headers.append('Public-Key-Pins-Report-Only') %}
- add_header Public-Key-Pins{% if security_rule.hpkp_report_only is defined and security_rule.hpkp_report_only == '1'
- %}-Report-Only{% endif %} "{% for key in security_rule.hpkp_keys.split(',')
- %}pin-sha256={{ key }}; {% endfor %}max-age={{ security_rule.hpkp_time }}{%
- if security_rule.hpkp_include_subdomains is defined and
- security_rule.hpkp_include_subdomains == '1' %}; includeSubDomains{% endif %}" always;
+ if security_rule.strict_transport_security_include_subdomains|default('0') == '1' %}; includeSubDomains{% endif %}{%
+ if security_rule.strict_transport_security_preload|default('0') == '1' %}; preload{% endif %}" always;
{% endif %}
{% if security_rule.enable_csp is defined and security_rule.enable_csp == '1' %}
{% set hash_csp = {} %}
-{% for csp_category in ['default-src', 'script-src', 'img-src', 'style-src', 'media-src', 'font-src', 'form-action'] %}
+{% for csp_category in ['default-src', 'script-src', 'img-src', 'style-src', 'media-src', 'font-src', 'frame-src', 'frame-ancestors', 'form-action'] %}
{% set prefix = 'csp_' + csp_category.replace('-', '_') + '_' %}
{% if security_rule[prefix + 'enabled'] == '1' %}
{% set current_list = [] %}