From 994e4af2f19e7e92012018abbe952a8fcbe868a2 Mon Sep 17 00:00:00 2001 From: bovy89 Date: Tue, 24 Mar 2026 11:49:23 +0100 Subject: [PATCH] feat(apache::vhost): refactor ModSecurity configuration handling - Add modsec_rule_engine parameter (Enum['On','Off','DetectionOnly'], default 'On') to explicitly control SecRuleEngine directive - Deprecate modsec_disable_vhost boolean in favor of modsec_rule_engine (retain backward compatibility via deprecation warning) - Security fragment template is now always included (as before) - Add EPP signature with explicit type enforcement to _security.epp - Guard all non-engine directives in _security.epp behind modsec_rule_engine != 'Off' to prevent inert configuration noise when ModSecurity is disabled --- manifests/mod/security.pp | 4 +- manifests/vhost.pp | 72 ++++++++++++++++++++--------------- templates/vhost/_security.epp | 51 +++++++++++++++---------- 3 files changed, 74 insertions(+), 53 deletions(-) diff --git a/manifests/mod/security.pp b/manifests/mod/security.pp index 27de2e8a42..dfca095c4c 100644 --- a/manifests/mod/security.pp +++ b/manifests/mod/security.pp @@ -145,8 +145,8 @@ Boolean $custom_rules = $apache::params::modsec_custom_rules, Optional[Array[String]] $custom_rules_set = $apache::params::modsec_custom_rules_set, Stdlib::Absolutepath $modsec_dir = $apache::params::modsec_dir, - String $modsec_secruleengine = $apache::params::modsec_secruleengine, - Integer[0, 9] $debug_log_level = 0, + Enum['On', 'Off', 'DetectionOnly'] $modsec_secruleengine = $apache::params::modsec_secruleengine, + Integer[0, 9] $debug_log_level = 0, String $audit_log_relevant_status = '^(?:5|4(?!04))', String $audit_log_parts = $apache::params::modsec_audit_log_parts, String $audit_log_type = $apache::params::modsec_audit_log_type, diff --git a/manifests/vhost.pp b/manifests/vhost.pp index 03c86938b0..30449eb907 100644 --- a/manifests/vhost.pp +++ b/manifests/vhost.pp @@ -1710,6 +1710,10 @@ # # @param proxy_protocol_exceptions # Disable processing of PROXY header for certain hosts or networks +# +# @param modsec_rule_engine +# Configures the rules engine. +# define apache::vhost ( Variant[Stdlib::Absolutepath, Boolean] $docroot, Boolean $manage_docroot = true, @@ -1973,6 +1977,7 @@ Optional[Variant[String[1], Array[String[1]]]] $userdir = undef, Optional[Boolean] $proxy_protocol = undef, Array[Stdlib::Host] $proxy_protocol_exceptions = [], + Optional[Enum['On', 'Off', 'DetectionOnly']] $modsec_rule_engine = undef, ) { # The base class must be included first because it is used by parameter defaults if ! defined(Class['apache']) { @@ -2181,28 +2186,19 @@ } ## Create a global LocationMatch if locations aren't defined - if $modsec_disable_ids { - if $modsec_disable_ids =~ Array { - $_modsec_disable_ids = { '.*' => $modsec_disable_ids } - } else { - $_modsec_disable_ids = $modsec_disable_ids - } + $_modsec_disable_ids = $modsec_disable_ids ? { + Array => { '.*' => $modsec_disable_ids }, + default => $modsec_disable_ids, } - if $modsec_disable_msgs { - if $modsec_disable_msgs =~ Array { - $_modsec_disable_msgs = { '.*' => $modsec_disable_msgs } - } else { - $_modsec_disable_msgs = $modsec_disable_msgs - } + $_modsec_disable_msgs = $modsec_disable_msgs ? { + Array => { '.*' => $modsec_disable_msgs }, + default => $modsec_disable_msgs, } - if $modsec_disable_tags { - if $modsec_disable_tags =~ Array { - $_modsec_disable_tags = { '.*' => $modsec_disable_tags } - } else { - $_modsec_disable_tags = $modsec_disable_tags - } + $_modsec_disable_tags = $modsec_disable_tags ? { + Array => { '.*' => $modsec_disable_tags }, + default => $modsec_disable_tags, } concat { "${priority_real}${filename}.conf": @@ -2840,19 +2836,33 @@ } } - if $modsec_disable_vhost or $modsec_disable_ids or !empty($modsec_disable_ips) or $modsec_disable_msgs or $modsec_disable_tags or $modsec_audit_log_destination or ($modsec_inbound_anomaly_threshold and $modsec_outbound_anomaly_threshold) or $modsec_allowed_methods { - $security_params = { - 'modsec_disable_vhost' => $modsec_disable_vhost, - 'modsec_audit_log_destination' => $modsec_audit_log_destination, - '_modsec_disable_ids' => $modsec_disable_ids, - 'modsec_disable_ips' => $modsec_disable_ips, - '_modsec_disable_msgs' => $modsec_disable_msgs, - '_modsec_disable_tags' => $modsec_disable_tags, - 'modsec_body_limit' => $modsec_body_limit, - 'modsec_inbound_anomaly_threshold' => $modsec_inbound_anomaly_threshold, - 'modsec_outbound_anomaly_threshold' => $modsec_outbound_anomaly_threshold, - 'modsec_allowed_methods' => $modsec_allowed_methods, - } + if $modsec_disable_vhost and $modsec_rule_engine == undef { + warning('modsec_disable_vhost is deprecated, use modsec_rule_engine => Off instead') + } + + $_modsec_rule_engine = $modsec_rule_engine ? { + undef => $modsec_disable_vhost ? { + true => 'Off', + default => 'On', + }, + default => $modsec_rule_engine, + } + + $modsec_enabled = ($modsec_rule_engine != undef or $modsec_disable_vhost or $modsec_audit_log_destination != undef or $modsec_disable_ids != undef or !empty($modsec_disable_ips) or $modsec_disable_msgs != undef or $modsec_disable_tags != undef or $modsec_body_limit != undef or $modsec_inbound_anomaly_threshold != undef or $modsec_outbound_anomaly_threshold != undef or $modsec_allowed_methods != undef) + + $security_params = { + '_modsec_rule_engine' => $_modsec_rule_engine, + 'modsec_audit_log_destination' => $modsec_audit_log_destination, + '_modsec_disable_ids' => $_modsec_disable_ids, + 'modsec_disable_ips' => $modsec_disable_ips, + '_modsec_disable_msgs' => $_modsec_disable_msgs, + '_modsec_disable_tags' => $_modsec_disable_tags, + 'modsec_body_limit' => $modsec_body_limit, + 'modsec_inbound_anomaly_threshold' => $modsec_inbound_anomaly_threshold, + 'modsec_outbound_anomaly_threshold' => $modsec_outbound_anomaly_threshold, + 'modsec_allowed_methods' => $modsec_allowed_methods, + } + if $modsec_enabled { concat::fragment { "${name}-security": target => "${priority_real}${filename}.conf", order => 320, diff --git a/templates/vhost/_security.epp b/templates/vhost/_security.epp index 24d3b4a39c..932420aaf9 100644 --- a/templates/vhost/_security.epp +++ b/templates/vhost/_security.epp @@ -1,55 +1,65 @@ +<%- | Enum['On', 'Off', 'DetectionOnly'] $_modsec_rule_engine, + Optional[String] $modsec_audit_log_destination, + Optional[Hash] $_modsec_disable_ids, + Array[String] $modsec_disable_ips, + Optional[Hash] $_modsec_disable_msgs, + Optional[Hash] $_modsec_disable_tags, + Optional[String] $modsec_body_limit, + Optional[Integer] $modsec_inbound_anomaly_threshold, + Optional[Integer] $modsec_outbound_anomaly_threshold, + Optional[String] $modsec_allowed_methods, +| -%> -<% if $modsec_disable_vhost {-%> - SecRuleEngine Off -<% } -%> -<% if $modsec_audit_log_destination {-%> + SecRuleEngine <%= $_modsec_rule_engine %> +<% if $_modsec_rule_engine != 'Off' { -%> +<% if $modsec_audit_log_destination { -%> SecAuditLog "<%= $modsec_audit_log_destination %>" <% } -%> -<% if $_modsec_disable_ids =~ Hash {-%> -<% $_modsec_disable_ids.each |$location, $rules| {-%> +<% if $_modsec_disable_ids =~ Hash and !$_modsec_disable_ids.empty { -%> +<% $_modsec_disable_ids.each |$location, $rules| { -%> > -<% Array($rules).each |$rule| {-%> +<% Array($rules).each |$rule| { -%> SecRuleRemoveById <%= $rule %> <% } -%> <% } -%> <% } -%> -<% unless $modsec_disable_ips.empty {%> +<% unless $modsec_disable_ips.empty { -%> SecRule REMOTE_ADDR "<%= join($modsec_disable_ips, ',') %>" "nolog,allow,id:1234123455" - SecAction "phase:2,pass,nolog,id:1234123456" + SecAction "phase:2,pass,nolog,id:1234123456" <% } -%> -<% if $_modsec_disable_msgs =~ Hash {-%> -<% $_modsec_disable_msgs.each |$location, $rules| {-%> +<% if $_modsec_disable_msgs =~ Hash and !$_modsec_disable_msgs.empty { -%> +<% $_modsec_disable_msgs.each |$location, $rules| { -%> > -<% Array($rules).each |$rule| {-%> +<% Array($rules).each |$rule| { -%> SecRuleRemoveByMsg "<%= $rule %>" <% } -%> <% } -%> <% } -%> -<% if $_modsec_disable_tags =~ Hash {-%> -<% $_modsec_disable_tags.each |$location, $rules| {-%> +<% if $_modsec_disable_tags =~ Hash and !$_modsec_disable_tags.empty { -%> +<% $_modsec_disable_tags.each |$location, $rules| { -%> > -<% Array($rules).each |$rule| {-%> +<% Array($rules).each |$rule| { -%> SecRuleRemoveByTag "<%= $rule %>" <% } -%> <% } -%> <% } -%> -<% if $modsec_body_limit {-%> +<% if $modsec_body_limit { -%> SecRequestBodyLimit <%= $modsec_body_limit %> <% } -%> -<% if $modsec_inbound_anomaly_threshold and $modsec_outbound_anomaly_threshold {-%> +<% if $modsec_inbound_anomaly_threshold and $modsec_outbound_anomaly_threshold { -%> SecAction \ "id:900110,\ phase:1,\ nolog,\ pass,\ t:none,\ - setvar:tx.inbound_anomaly_score_threshold=<%= $modsec_inbound_anomaly_threshold -%>, \ - setvar:tx.outbound_anomaly_score_threshold=<%= $modsec_outbound_anomaly_threshold -%>" + setvar:tx.inbound_anomaly_score_threshold=<%= $modsec_inbound_anomaly_threshold %>, \ + setvar:tx.outbound_anomaly_score_threshold=<%= $modsec_outbound_anomaly_threshold %>" <% } -%> -<% if $modsec_allowed_methods {-%> +<% if $modsec_allowed_methods { -%> SecAction \ "id:900200,\ phase:1,\ @@ -58,4 +68,5 @@ t:none,\ setvar:'tx.allowed_methods=<%= $modsec_allowed_methods -%>'" <% } -%> +<% } -%>