mirror of
https://github.com/opnsense/plugins.git
synced 2026-06-11 01:40:49 -04:00
www/nginx: Rework security header forms and adding CSP frame-ancestors and HSTS preload directives (#2702)
The security header edit dialog contains many fields and a tabbed interface for the dialog itself allows better configuration of the headers.
This commit is contained in:
parent
1a86d52df8
commit
5a61822b0d
9 changed files with 1016 additions and 580 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1194,19 +1194,10 @@
|
|||
<Required>Y</Required>
|
||||
<default>1</default>
|
||||
</strict_transport_security_include_subdomains>
|
||||
<hpkp_keys type="CSVListField">
|
||||
<Required>N</Required>
|
||||
<mask>/[a-z0-9\+\/=]+(,[a-z0-9\+\/=]+)*/i</mask>
|
||||
</hpkp_keys>
|
||||
<hpkp_report_only type="BooleanField">
|
||||
<strict_transport_security_preload type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
</hpkp_report_only>
|
||||
<hpkp_time type="IntegerField">
|
||||
<Required>N</Required>
|
||||
</hpkp_time>
|
||||
<hpkp_include_subdomains type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
</hpkp_include_subdomains>
|
||||
<default>0</default>
|
||||
</strict_transport_security_preload>
|
||||
<enable_csp type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
</enable_csp>
|
||||
|
|
@ -1448,6 +1439,76 @@
|
|||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
</csp_font_src_none>
|
||||
<csp_frame_src_enabled type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
</csp_frame_src_enabled>
|
||||
<csp_frame_src_data_urls type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
</csp_frame_src_data_urls>
|
||||
<csp_frame_src_http_urls type="CSVListField">
|
||||
<Required>N</Required>
|
||||
</csp_frame_src_http_urls>
|
||||
<csp_frame_src_inline type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
</csp_frame_src_inline>
|
||||
<csp_frame_src_eval type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
</csp_frame_src_eval>
|
||||
<csp_frame_src_self type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
</csp_frame_src_self>
|
||||
<csp_frame_src_blob type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
</csp_frame_src_blob>
|
||||
<csp_frame_src_mediastream type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
</csp_frame_src_mediastream>
|
||||
<csp_frame_src_filesystem type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
</csp_frame_src_filesystem>
|
||||
<csp_frame_src_none type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
</csp_frame_src_none>
|
||||
<csp_frame_ancestors_enabled type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
</csp_frame_ancestors_enabled>
|
||||
<csp_frame_ancestors_data_urls type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
</csp_frame_ancestors_data_urls>
|
||||
<csp_frame_ancestors_http_urls type="CSVListField">
|
||||
<Required>N</Required>
|
||||
</csp_frame_ancestors_http_urls>
|
||||
<csp_frame_ancestors_self type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
</csp_frame_ancestors_self>
|
||||
<csp_frame_ancestors_blob type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
</csp_frame_ancestors_blob>
|
||||
<csp_frame_ancestors_mediastream type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
</csp_frame_ancestors_mediastream>
|
||||
<csp_frame_ancestors_filesystem type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
</csp_frame_ancestors_filesystem>
|
||||
<csp_frame_ancestors_none type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
</csp_frame_ancestors_none>
|
||||
<csp_form_action_enabled type="BooleanField">
|
||||
<Required>Y</Required>
|
||||
<default>0</default>
|
||||
|
|
|
|||
|
|
@ -478,6 +478,11 @@
|
|||
<thead>
|
||||
<tr>
|
||||
<th data-column-id="description" data-type="string" data-sortable="true" data-visible="true">{{ lang._('Description') }}</th>
|
||||
<th data-column-id="referrer" data-type="string" data-sortable="true" data-visible="false">{{ lang._('Referrer') }}</th>
|
||||
<th data-column-id="xssprotection" data-type="string" data-sortable="true" data-visible="true">{{ lang._('XSS Protection') }}</th>
|
||||
<th data-column-id="hsts" data-type="string" data-sortable="true" data-visible="true">{{ lang._('HSTS') }}</th>
|
||||
<th data-column-id="csp" data-type="string" data-sortable="true" data-visible="true">{{ lang._('CSP') }}</th>
|
||||
<th data-column-id="csp_details" data-type="string" data-sortable="true" data-visible="false">{{ lang._('CSP Rules') }}</th>
|
||||
<th data-column-id="commands" data-width="7em" data-formatter="commands" data-sortable="false">{{ lang._('Commands') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
@ -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')]) }}
|
||||
|
|
|
|||
|
|
@ -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 %}
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
// hook into on-show event to extend validation
|
||||
$('#{{base_dialog_id}}').on('shown.bs.modal', function (e) {
|
||||
$('.nav-tabs a[href="#frm_{{base_dialog_id}}-tab_general"]').tab('show');
|
||||
|
||||
$("#btn_{{base_dialog_id}}_save").click(function() {
|
||||
// TODO: Search for tab with validation errors, but currently only the first tab has even validation rules
|
||||
$('.nav-tabs a[href="#frm_{{base_dialog_id}}-tab_general"]').tab('show');
|
||||
});
|
||||
})
|
||||
|
||||
// Read currently selected tab in main form and store for later
|
||||
$('#{{base_dialog_id}}').on('show.bs.modal', function (e) {
|
||||
$('#{{base_dialog_id}}').attr("data-inittab", window.location.hash);
|
||||
});
|
||||
// Restore selected tab of main form as URL was modified by dialog tabs
|
||||
$('#{{base_dialog_id}}').on('hide.bs.modal', function (e) {
|
||||
window.location.hash = $('#{{base_dialog_id}}').attr("data-inittab");
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="modal fade" id="{{base_dialog_id}}" tabindex="-1" role="dialog" aria-labelledby="{{base_dialog_id}}Label" aria-hidden="true">
|
||||
<div class="modal-backdrop fade in"></div>
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="{{ lang._('Close') }}"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="{{base_dialog_id}}Label">{{base_dialog_label}}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<ul class="nav nav-tabs" role="tablist" id="dialogtabs">
|
||||
{% for field in base_dialog_fields['tabs']|default({})%}
|
||||
<li>
|
||||
<a data-toggle="tab" href="#frm_{{base_dialog_id}}-tab_{{field[0]}}"><b>{{field[1]}}</b></a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<form id="frm_{{base_dialog_id}}">
|
||||
<div class="content-box tab-content">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-condensed">
|
||||
<colgroup>
|
||||
<col class="col-md-3"/>
|
||||
<col class="col-md-{{ 12-3-msgzone_width|default(5) }}"/>
|
||||
<col class="col-md-{{ msgzone_width|default(5) }}"/>
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{% if base_dialog_advanced|default(false) or base_dialog_help|default(false) %}
|
||||
<tr>
|
||||
<td>{% if base_dialog_advanced|default(false) %}<a href="#"><i class="fa fa-toggle-off text-danger" id="show_advanced_formDialog{{base_dialog_id}}"></i></a> <small>{{ lang._('advanced mode') }}</small>{% endif %}</td>
|
||||
<td colspan="2" style="text-align:right;">
|
||||
{% if base_dialog_help|default(false) %}<small>{{ lang._('full help') }}</small> <a href="#"><i class="fa fa-toggle-off text-danger" id="show_all_help_formDialog{{base_dialog_id}}"></i></a>{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% for tab in base_dialog_fields['tabs']|default({})%}
|
||||
<div id="frm_{{base_dialog_id}}-tab_{{tab[0]}}" class="tab-pane fade">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-condensed">
|
||||
<colgroup>
|
||||
<col class="col-md-3"/>
|
||||
<col class="col-md-{{ 12-3-msgzone_width|default(5) }}"/>
|
||||
<col class="col-md-{{ msgzone_width|default(5) }}"/>
|
||||
</colgroup>
|
||||
<tbody>
|
||||
{% for field in tab[2]|default({})%}
|
||||
{# looks a bit buggy in the volt templates, field parameters won't reset properly here #}
|
||||
{% set advanced=false %}
|
||||
{% set help=false %}
|
||||
{% set hint=false %}
|
||||
{% set style=false %}
|
||||
{% set maxheight=false %}
|
||||
{% set width=false %}
|
||||
{% set allownew=false %}
|
||||
{% set readonly=false %}
|
||||
{% if field['type'] == 'header' %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="table-responsive {{field['style']|default('')}}">
|
||||
<table class="table table-striped table-condensed">
|
||||
<colgroup>
|
||||
<col class="col-md-3"/>
|
||||
<col class="col-md-{{ 12-3-msgzone_width|default(5) }}"/>
|
||||
<col class="col-md-{{ msgzone_width|default(5) }}"/>
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr{% if field['advanced']|default(false)=='true' %} data-advanced="true"{% endif %}>
|
||||
<th colspan="3">
|
||||
<h2>{{field['label']}}</h2>
|
||||
{%- if field['hint']|default(false) %}
|
||||
<small>{{field['hint']}}</small>
|
||||
{%- endif %}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% else %}
|
||||
{{ partial("layout_partials/form_input_tr",field)}}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
{% if hasSaveBtn|default('true') == 'true' %}
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ lang._('Cancel') }}</button>
|
||||
<button type="button" class="btn btn-primary" id="btn_{{base_dialog_id}}_save">{{ lang._('Save') }} <i id="btn_{{base_dialog_id}}_save_progress" class=""></i></button>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">{{ lang._('Close') }}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -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 = [] %}
|
||||
|
|
|
|||
Loading…
Reference in a new issue