Repository navigation
v2.37.0 — pages you can make your own
v2.37.0
Pages you can make your own.
The challenge interstitial, the block response and the lockdown response can now carry a site's wording, language and colours, or be replaced with the site's own HTML. The compiled config cache now actually hits on web requests, and can live in a PSR-6 pool. The database log handler writes, indexes and prunes the way its docs always said it did.
PHP 8.1–8.5 supported. Every new setting is opt-in. Without them, a block and a lockdown are the same plain text they were, and the challenge page looks the same. Three things need attention on upgrade; see Upgrading.
Added
Challenge, block and lockdown pages
-
challenge.page(#451, #453). Sets the interstitial'slang,title,heading,intro,button,error_message,stylesandstylesheet. Text is escaped. CSS is appended after the built-in rules, and CSS containing a closingstyletag is refused. A stylesheet must be a path on this site or anhttps:URL.challenge: page: lang: fr heading: "Vérification rapide" button: "Continuer"
-
global.block_pageandglobal.lockdown_page(#452, #455). Set either totrue, or to a map oflang,title,heading,message,stylesandstylesheet, and a block or a lockdown is served as an HTML page instead of one line of text. The page shares the interstitial's card and stylesheet, and is sent with a strictContent-Security-Policythat blocks script.- Per rule:
metadata.block_pageis merged over the global page, andmetadata.banning_messagereplaces the global message. - New placeholders:
{{block.status}}and{{block.rule}}. The rule name only appears where you write it. - JSON for API clients:
global.banning_json: trueanswers a client whose firstAcceptpreference is JSON with{"error":"blocked","status":403,"request_id":"…"}. mode: exception:FirewallBlockedException::getContentType()says what the body is.
- Per rule:
-
Your own template (#456, #457).
block_page.template,lockdown_page.templateor a rule'smetadata.block_page.templatereplaces the built-in document with yours, usually loaded with'%file(/etc/firewall/block.html)%'. The page keys fill{{page.message}},{{page.heading}},{{page.title}}and{{page.lang}}, alongside every{{request.*}}and{{block.*}}placeholder. Every placeholder is escaped, and a client value is never expanded as a placeholder itself. The CSP still applies. -
Colours as CSS custom properties (#454, #466). Every colour on the built-in pages is a
--fw-*property, so one line themes all three:challenge: page: styles: ":root { --fw-accent: #0b8f5a; --fw-accent-hover: #087448; }"
These names are the supported way to theme the pages. Selectors still work, but they belong to the markup and can change. The defaults are the colours the pages already had.
Compiled config cache
- A PSR-6 pool, or off disk (#447, #449).
Config::setConfigCachePool($pool)keeps the compiled configuration in a cache pool instead of PHP files, which suits a host whose only persistent writable directory is a network filesystem. Give the poolNoObjectsMarshaller.define('KANOPI_FIREWALL_CONFIG_FILE_CACHE', false)stops files being written by processes that have no pool, such as CLI or cron.
Database log handler
- Indexes by rule (#458, #461).
firewall_loggains(plugin_name, logged_at)and(logged_at, plugin_name). On a 2.84M-row table, per-rule totals for the last day went from 0.42 s to 0.02 s. Existing tables needbin/firewall-migrate; see Upgrading. prune_batch_sizeandprune_max_batches(#459, #462, #464, #465). See Fixed.
Fixed
- The compiled config cache missed on every web request (#445, #448). The environment fingerprint hashed the whole of
$_SERVERandgetenv(), which under PHP-FPM carry per-request values. So every web request re-parsed the config, rewrote the cache entry and swept the directory. On a network filesystem that was about 6 s a request. Request-scoped keys are now left out of the fingerprint. - A config reading a request-scoped value through
%env()%is never cached (#467, #468). Such a config, for exampleconfigs: ['sites/%env(SERVER_NAME)%.yml'], is parsed per request, so one host can't be served another's config. Everything else stays cached. This closes a gap #448 opened, which was never released. presets/storage-pantheon.ymlcouldn't connect on Pantheon (#446, #450). OnlydbnamedecodedPRESSFLOW_SETTINGS. The other four values fell back to DDEV'sdb/db@db:3306, so storage failed and the firewall let traffic through.- A log flush is now multi-row
INSERTs (#460, #463), as thebufferoption always said: 71 rows a statement. 1,700 records flushed in 65 ms instead of 523 ms on MariaDB. - Pruning the log table runs in batches (#459, #462). One unbounded
DELETEof a 2.17M-row backlog took 26 s inside a request. A winning flush now deletes in batches ofprune_batch_size(default1000). - Request-path pruning keeps up with what's written (#464, #465). A winning flush runs enough batches to cover what it wrote, capped at
prune_max_batches(default10). Past the cap it warns that retention is behind and points atbin/firewall-log-prune. - The log handler's own warnings now reach the PHP error log (#464, #465). Before, only errors did, so the warning that a table is behind the declared schema was silently dropped for
firewall_log.
Upgrading
- Run
bin/firewall-migrateif you use the database log handler. Existingfirewall_logtables don't gain the two new indexes on their own. Building them takes a lock on a table that may be large, so run it at a quiet time. The handler's drift warning, which now actually reaches the error log, will remind you. - If a log table has a backlog, for example because retention was just switched on or
retention_dayslowered, runbin/firewall-log-pruneonce. Request-path pruning then only has to keep up with what's new. - New lines in the PHP error log. The log handler now reports two warnings it used to drop: a table behind the declared schema, and retention behind the rows. Each appears once per process.
Full changelog: v2.36.1...v2.37.0