Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .fixtures.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
fixtures:
repositories:
archive: "https://github.com/puppetlabs/puppetlabs-archive.git"
concat: "https://github.com/puppetlabs/puppetlabs-concat.git"
facts: 'https://github.com/puppetlabs/puppetlabs-facts.git'
portage: "https://github.com/gentoo/puppet-portage.git"
Expand Down
116 changes: 104 additions & 12 deletions manifests/mod/security.pp
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,29 @@
# Configures the location of audit and debug logs.
#
# @param crs_package
# Name of package that installs CRS rules.
#
# Name of package that installs CRS rules. Only used when `crs_source` is `package`.
#
# @param crs_source
# How the OWASP Core Rule Set is obtained:
# * `package` - install `crs_package` and activate rules via per-rule symlinks (v2/v3 layout). Default on EL7/8/9.
# * `archive` - download the CRS v4 tarball via `puppet/archive` from `crs_archive_source` (e.g. an internal mirror) and wire the v4 includes.
# * `path` - use a pre-staged CRS v4 directory given by `crs_path` (no download); only wires the v4 includes.
# * `none` - engine only, no CRS managed. Default on EL10.
#
# @param crs_archive_source
# Source URL or path for the CRS v4 tarball when `crs_source` is `archive`. No module default
# (user-pinned, e.g. an internal mirror) to avoid a version/CVE maintenance treadmill. Required for `archive`.
#
# @param crs_archive_checksum
# Checksum of the CRS v4 tarball for verification when `crs_source` is `archive`.
#
# @param crs_archive_checksum_type
# Checksum algorithm for `crs_archive_checksum` (e.g. `sha256`).
#
# @param crs_path
# Absolute path to the CRS v4 directory. Required for `crs_source => path` (pre-staged files);
# for `crs_source => archive` it overrides the extraction directory (default `${modsec_dir}/coreruleset`).
#
# @param activated_rules
# An array of rules from the modsec_crs_path or absolute to activate via symlinks.
#
Expand Down Expand Up @@ -139,12 +160,20 @@
#
# @note On RHEL/EL 10 the ModSecurity engine is provided by EPEL (enable EPEL
# yourself; this module does not manage it). The OWASP CRS package
# (`mod_security_crs`) is not available on EL10, so the class manages the
# engine only there and does not install or activate CRS rules.
# (`mod_security_crs`) is not available on EL10, so `crs_source` defaults to
# `none` (engine only). CRS v4 can be opted into there via `crs_source =>
# 'archive'` (downloaded from `crs_archive_source`, e.g. an internal mirror)
# or `crs_source => 'path'` (a pre-staged directory). EL7/8/9 keep the
# package-based default unchanged.
class apache::mod::security (
Stdlib::Absolutepath $logroot = $apache::params::logroot,
Integer $version = $apache::params::modsec_version,
Optional[String] $crs_package = $apache::params::modsec_crs_package,
Enum['package', 'archive', 'path', 'none'] $crs_source = $apache::params::modsec_crs_source,
Optional[String[1]] $crs_archive_source = $apache::params::modsec_crs_archive_source,
Optional[String[1]] $crs_archive_checksum = $apache::params::modsec_crs_archive_checksum,
String[1] $crs_archive_checksum_type = $apache::params::modsec_crs_archive_checksum_type,
Optional[Stdlib::Absolutepath] $crs_path = undef,
Array[String] $activated_rules = $apache::params::modsec_default_rules,
Boolean $custom_rules = $apache::params::modsec_custom_rules,
Optional[Array[String]] $custom_rules_set = $apache::params::modsec_custom_rules_set,
Expand Down Expand Up @@ -227,14 +256,63 @@
lib => 'mod_unique_id.so',
}

if $crs_package {
package { $crs_package:
ensure => 'installed',
before => [
File[$apache::confd_dir],
File[$modsec_dir],
],
# Effective on-disk CRS v4 directory (used by archive/path modes). Kept
# outside $modsec_dir, which is managed with purge => true and would
# otherwise remove the extracted rule tree.
$_crs_dir = $crs_path ? {
undef => '/usr/share/coreruleset',
default => $crs_path,
}

# CRS acquisition. The activation wiring is selected later by the same
# $crs_source: `package` keeps the legacy per-rule symlinks (v2/v3 layout),
# while `archive`/`path` use the v4 include layout.
case $crs_source {
'package': {
if $crs_package {
package { $crs_package:
ensure => 'installed',
before => [
File[$apache::confd_dir],
File[$modsec_dir],
],
}
}
}
'archive': {
if ! $crs_archive_source {
fail('apache::mod::security: crs_source => "archive" requires crs_archive_source (URL/path to the CRS v4 tarball, e.g. an internal mirror).')
}
# TODO: CRS tarballs extract to a versioned top-level dir
# (coreruleset-x.y.z/). Confirm whether to point $crs_path at that
# subdir or normalise the layout after extraction.
file { $_crs_dir:
ensure => directory,
owner => 'root',
group => 'root',
mode => '0755',
}

archive { 'coreruleset.tar.gz':
ensure => present,
path => '/var/cache/coreruleset.tar.gz',
source => $crs_archive_source,
checksum => $crs_archive_checksum,
checksum_type => $crs_archive_checksum_type,
extract => true,
extract_path => $_crs_dir,
creates => "${_crs_dir}/crs-setup.conf",
cleanup => true,
require => File[$_crs_dir],
}
}
'path': {
if ! $crs_path {
fail('apache::mod::security: crs_source => "path" requires crs_path (absolute path to a pre-staged CRS v4 directory).')
}
}
'none': {}
default: {}
}

# Template uses:
Expand Down Expand Up @@ -329,7 +407,9 @@
}
}

if $manage_security_crs {
if $manage_security_crs and $crs_source == 'package' {
# Legacy CRS v2/v3 layout: a tuning conf plus per-rule symlinks under
# activated_rules/. Unchanged behaviour for EL7/8/9 package installs.
# Template uses:
# - $_secdefaultaction
# - $critical_anomaly_score
Expand Down Expand Up @@ -381,4 +461,16 @@
apache::security::rule_link { $activated_rules: }
}
}

if $manage_security_crs and $crs_source in ['archive', 'path'] {
# CRS v4 layout: load crs-setup.conf then rules/*.conf from the CRS
# directory. Dropped into $modsec_dir so the existing
# `IncludeOptional ${modsec_dir}/*.conf` in security.conf picks it up.
file { "${modsec_dir}/security_crs_v4.conf":
ensure => file,
content => epp('apache/mod/security_crs_v4.conf.epp', { 'crs_dir' => $_crs_dir }),
require => File[$modsec_dir],
notify => Class['apache::service'],
}
}
}
15 changes: 15 additions & 0 deletions manifests/params.pp
Original file line number Diff line number Diff line change
Expand Up @@ -806,4 +806,19 @@
$ssl_cipher = 'HIGH:MEDIUM:!aNULL:!MD5:!RC4:!3DES'
$ssl_proxy_cipher_suite = undef
}

# OWASP CRS acquisition mode (see apache::mod::security).
# EL10 has no mod_security_crs package, so default to engine-only there
# (CRS is opt-in via the archive/path modes). Every other platform keeps
# the existing package-based behaviour, so nothing changes for them.
if $facts['os']['family'] == 'RedHat' and versioncmp($facts['os']['release']['major'], '10') >= 0 {
$modsec_crs_source = 'none'
} else {
$modsec_crs_source = 'package'
}
# No module-shipped default version/URL: the archive source is user-pinned
# (e.g. an internal mirror) to keep us off the CRS version/CVE treadmill.
$modsec_crs_archive_source = undef
$modsec_crs_archive_checksum = undef
$modsec_crs_archive_checksum_type = 'sha256'
}
4 changes: 4 additions & 0 deletions metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
{
"name": "puppetlabs/concat",
"version_requirement": ">= 2.2.1 < 11.0.0"
},
{
"name": "puppetlabs/archive",
Comment thread
SugatD marked this conversation as resolved.
Outdated
"version_requirement": ">= 4.0.0 < 8.0.0"
}
],
"operatingsystem_support": [
Expand Down
80 changes: 80 additions & 0 deletions spec/classes/mod/security_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -438,4 +438,84 @@
)
}
end

# CRS v4 acquisition modes (MODULES-11857). Exercised on EL10, where the
# default is engine-only and CRS is opt-in via archive/path.
context 'crs_source modes on RedHat 10' do
let :facts do
{
os: {
'architecture' => 'x86_64',
'family' => 'RedHat',
'hardware' => 'x86_64',
'name' => 'RedHat',
'release' => { 'full' => '10.0', 'major' => '10' },
'selinux' => { 'enabled' => false },
},
}
end

context 'default (none)' do
it { is_expected.to compile.with_all_deps }
it { is_expected.not_to contain_archive('coreruleset.tar.gz') }
it { is_expected.not_to contain_file('/etc/httpd/modsecurity.d/security_crs_v4.conf') }
end

context "crs_source => 'archive'" do
context 'without crs_archive_source' do
let(:params) { { crs_source: 'archive' } }

it { is_expected.to compile.and_raise_error(%r{crs_archive_source}) }
end

context 'with crs_archive_source' do
let(:params) do
{
crs_source: 'archive',
crs_archive_source: 'https://mirror.internal.example/crs/coreruleset-4.10.0.tar.gz',
crs_archive_checksum: 'abc123',
}
end

it { is_expected.to compile.with_all_deps }

it {
expect(subject).to contain_archive('coreruleset.tar.gz').with(
source: 'https://mirror.internal.example/crs/coreruleset-4.10.0.tar.gz',
checksum: 'abc123',
checksum_type: 'sha256',
extract: true,
extract_path: '/usr/share/coreruleset',
)
}

it {
expect(subject).to contain_file('/etc/httpd/modsecurity.d/security_crs_v4.conf')
.with_content(%r{IncludeOptional /usr/share/coreruleset/crs-setup\.conf})
.with_content(%r{IncludeOptional /usr/share/coreruleset/rules/\*\.conf})
}
end
end

context "crs_source => 'path'" do
context 'without crs_path' do
let(:params) { { crs_source: 'path' } }

it { is_expected.to compile.and_raise_error(%r{crs_path}) }
end

context 'with crs_path' do
let(:params) { { crs_source: 'path', crs_path: '/opt/crs' } }

it { is_expected.to compile.with_all_deps }
it { is_expected.not_to contain_archive('coreruleset.tar.gz') }

it {
expect(subject).to contain_file('/etc/httpd/modsecurity.d/security_crs_v4.conf')
.with_content(%r{IncludeOptional /opt/crs/crs-setup\.conf})
.with_content(%r{IncludeOptional /opt/crs/rules/\*\.conf})
}
end
end
end
end
9 changes: 9 additions & 0 deletions templates/mod/security_crs_v4.conf.epp
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<%- | Stdlib::Absolutepath $crs_dir | -%>
# Managed by Puppet (apache::mod::security, crs_source => archive|path).
#
# OWASP Core Rule Set v4 on-disk layout. Unlike the v2/v3 package model
# (per-rule symlinks under activated_rules/), v4 is loaded as a self-contained
# directory: the setup file first, then every rule file in filename order
# (CRS numbers its files so the wildcard include resolves the correct order).
IncludeOptional <%= $crs_dir %>/crs-setup.conf
IncludeOptional <%= $crs_dir %>/rules/*.conf
Loading