Skip to content

v2.37.0 — pages you can make your own

Choose a tag to compare

@sean-e-dietrich sean-e-dietrich released this 02 Oct 22:29
6862407

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's lang, title, heading, intro, button, error_message, styles and stylesheet. Text is escaped. CSS is appended after the built-in rules, and CSS containing a closing style tag is refused. A stylesheet must be a path on this site or an https: URL.

    challenge:
      page:
        lang: fr
        heading: "Vérification rapide"
        button: "Continuer"
  • global.block_page and global.lockdown_page (#452, #455). Set either to true, or to a map of lang, title, heading, message, styles and stylesheet, 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 strict Content-Security-Policy that blocks script.

    • Per rule: metadata.block_page is merged over the global page, and metadata.banning_message replaces 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: true answers a client whose first Accept preference is JSON with {"error":"blocked","status":403,"request_id":"…"}.
    • mode: exception: FirewallBlockedException::getContentType() says what the body is.
  • Your own template (#456, #457). block_page.template, lockdown_page.template or a rule's metadata.block_page.template replaces 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 pool NoObjectsMarshaller. 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_log gains (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 need bin/firewall-migrate; see Upgrading.
  • prune_batch_size and prune_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 $_SERVER and getenv(), 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 example configs: ['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.yml couldn't connect on Pantheon (#446, #450). Only dbname decoded PRESSFLOW_SETTINGS. The other four values fell back to DDEV's db/db@db:3306, so storage failed and the firewall let traffic through.
  • A log flush is now multi-row INSERTs (#460, #463), as the buffer option 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 DELETE of a 2.17M-row backlog took 26 s inside a request. A winning flush now deletes in batches of prune_batch_size (default 1000).
  • 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 (default 10). Past the cap it warns that retention is behind and points at bin/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

  1. Run bin/firewall-migrate if you use the database log handler. Existing firewall_log tables 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.
  2. If a log table has a backlog, for example because retention was just switched on or retention_days lowered, run bin/firewall-log-prune once. Request-path pruning then only has to keep up with what's new.
  3. 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