Skip to content

fix(roles/apache_httpd): stop hiding module mismatches behind IfModule - #340

Draft
NavidSassan wants to merge 6 commits into
mainfrom
fix/apache-httpd-module-guards
Draft

fix(roles/apache_httpd): stop hiding module mismatches behind IfModule#340
NavidSassan wants to merge 6 commits into
mainfrom
fix/apache-httpd-module-guards

Conversation

@NavidSassan

Copy link
Copy Markdown
Member

Stops the apache_httpd role from hiding module/conf mismatches behind <IfModule> guards, and fixes the two mismatches that were being hidden.

Why

An <IfModule> guard makes a conf snippet a no-op when its module is missing, silently. Two of them were wrong and nobody noticed:

  • cgi.conf and cgid.conf guarded on the MPM. IncludeOptional mods-enabled/*.conf expands alphabetically, so cgi*.conf is read before mpm_*.conf and the guard could never match. A host that enabled cgi or cgid in its inventory got the entry accepted and the module never loaded, with httpd -t reporting Syntax OK.
  • http2.conf carried <IfModule !mpm_prefork>, which is not a valid module token, so it never matched either.

Removing the guards makes a mismatch fail loudly at the config test instead of being ignored.

Breaking change

A conf-available snippet and the module it configures have to be enabled together now. Getting the role's own defaults consistent required disabling two snippets whose modules were never enabled: deflate and mod_security.

Consequence worth reading: compression has been silently off on every host managed by this role, because the deflate snippet was enabled while its module was not. This PR does not turn it on, it only makes the two settings consistent. Enabling compression properly is a separate discussion (BREACH).

Commits

  • af9af7bf load the cgi and cgid modules when enabled
  • 8b9e2e2d set the qos mod to enabled: false
  • 4cf056fd use a valid module name in the userdir guard
  • d4b3398f stop hiding module mismatches behind IfModule (breaking)
  • 1eb085c6 correct the documented SSLUseStapling default

Open before this is ready

  • Unverified claim in the README. 1eb085c6 states that Apache "logs a warning per certificate on every start" when stapling is on without an OCSP responder URL. That was written from memory and never verified. It should be confirmed or trimmed before merge.
  • Not covered by the Molecule scenario. The apache_httpd scenario asserts HTTP/2 behaviour, not the guard removal. The cgi/cgid finding was proven by hand in Rocky 8/9 and Debian 13 containers.
  • Debian family untested end to end, since the scenario currently fails there on apache_httpd: reload fails on the Debian family because the deployed apache2.conf drops PidFile #338.

Both files wrapped their LoadModule in an MPM guard, so that the MPM in use
picked the matching CGI module by itself. The guard could never match: the
role's httpd.conf includes only mods-enabled/*.conf, httpd expands that glob
alphabetically, and cgi.conf and cgid.conf both sort before mpm_event.conf,
mpm_prefork.conf and mpm_worker.conf. At the point they were parsed no MPM was
loaded yet, so the block was skipped and the module never made it into the
running server, with httpd -t reporting Syntax OK throughout.

Verified on Rocky 9 by reproducing the role's include layout: cgid_module is
absent as shipped and present once the guard is gone, or once the MPM file is
renamed to sort first.

Drop the guards rather than renaming the MPM entries, because filename is part
of the public interface that inventories override, and because a silently
skipped module is exactly the failure mode the role avoids elsewhere. Which
module to pair with which MPM is now stated in the template comments; measured
on Rocky 9 and Debian 13, every pairing serves CGI, so it is a sizing decision
rather than a hard requirement.

The minimal PHP-FPM example enabled cgi although that vHost serves PHP through
proxy_fcgi and never runs a CGI script, so following it produced a working site
with the module silently absent - which is a large part of why this went
unnoticed. Drop it. The non-hardened example keeps cgid, since it deliberately
mirrors the stock RHEL configuration, but now says why and what it costs.
The entry read enabled: true, state: 'absent', which reads as a contradiction:
state: 'absent' removes the mods-available file and the mods-enabled symlink on
its own, so enabled was inert. Debian and Ubuntu already say false. No change on
a managed host, hence no CHANGELOG entry.
<IfModule userdir> never matched: find_module() (server/core.c) resolves either
the module's __FILE__ (mod_userdir.c) or its LoadModule symbol
(userdir_module), and "userdir" is neither. Measured on Rocky 9 against a
control token. The block was therefore never applied, so the php_admin_flag
engine Off it wraps has never disabled PHP in /home/*/public_html.

Latent rather than exploitable: the php conf is not enabled by default and the
role does not manage mod_userdir, so no host had both pieces in place. Kept as a
guard rather than removed, unlike the rest of this branch, precisely because
mod_userdir is outside the role's control.
An <IfModule> around a module's own directives turns a configuration mistake
into silence: httpd -t reports Syntax OK and the setting never applies, which is
the failure mode this role avoids everywhere else. Drop the guards from the
conf-available snippets and from the guarded LoadModule lines.

Two of the defaults were inconsistent and only worked because of the guard: the
deflate and mod_security snippets were enabled while their modules were not, so
compression has silently been off and ModSecurity's PCRE tuning has never
applied. Disable the snippets to match the modules, which keeps the effective
behaviour of every host unchanged; whether compression should be on by default
is a separate question. The two EXAMPLES that enabled the deflate module now
enable the snippet alongside it, so those recipes still compress.

Removing each guard was measured on Rocky 9 rather than assumed:
- deflate, mod_security, autoindex: the directives now abort with
  AH00526 Syntax error when the module is absent, which is the point
- proxy_fcgi/_http/_wstunnel: without mod_proxy the load fails with
  "Cannot load mod_proxy_fcgi.so", clearer than a skipped LoadModule
- authn_file, authz_host, authz_user: mod_authz_host loads perfectly well
  without authz_core, so the guard protected nothing at all
- php7, php8: on a threaded MPM PHP itself refuses with "your PHP Module is
  not compiled to be threadsafe" and Apache does not start, a better message
  than any guard produces

autoindex has a template but was missing from apache_httpd__conf__role_var; add
it as enabled: false, state: 'absent' so the role's coverage is visible.

mods-available/wsgi_python3.conf keeps <IfModule !wsgi_module>: a negated guard
around its own LoadModule is the correct idiom for not loading two mod_wsgi
builds into one process.

BREAKING CHANGE: a conf-available snippet and the module it configures have to
be enabled together; the deflate and mod_security snippets are no longer enabled
by default, so a host that enabled only the deflate module loses compression.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant