coreruleset / coreruleset/modsecurity-crs-docker

feat: install CRS plugins at container start from the plugin registry

Open
#470 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
Dockerfile
Stars
448
Forks
88
Avg merge
6m
Merged PRs (30d)
2

Description

What

Let a container install CRS plugins at start from a list given in the environment, resolving names against the plugin registry:

docker run -e CRS_PLUGINS="fake-bot,wordpress-rule-exclusions@v1.2.0" owasp/modsecurity-crs:nginx

Each entry is resolved to a repository and a release, unpacked into /opt/owasp-crs/plugins, and picked up by the activation that already exists.

Why

Today the only ways to run a plugin are to build a derived image or to mount the plugin files into /opt/owasp-crs/plugins yourself. Both are fine for a fixed deployment and awkward for anyone trying a plugin out, and neither is discoverable from the README.

What is already in place

  • src/opt/modsecurity/activate-plugins.sh uncomments the plugin includes in setup.conf when it finds *-config.conf, *-before.conf or *-after.conf under /opt/owasp-crs/plugins, and comments them back out when it does not. Nothing new is needed to enable a plugin once its files are on disk.
  • It runs as /docker-entrypoint.d/94-activate-plugins.sh on nginx and from docker-entrypoint.sh on apache, so a download step has to run before it.
  • CRS_DISABLE_PLUGINS already exists as the off switch.
  • /opt/owasp-crs is owned by the runtime user (httpd/nginx), so creating plugins/ at start works. Note the directory does not exist in the published images — the minimal CRS tarball does not carry it.

What this depends on

Name resolution cannot be conventional. coreruleset/<name>-plugin holds for only 15 of the 30 registry rows. The rest deviate:

dokuwiki-rule-exclusions            coreruleset/dokuwki-rule-exclusions-plugin   (typo in the link text)
dos-protection-modsecurity          coreruleset/dos-protection-plugin-modsecurity
roundcube-rule-exclusions-plugin    EsadCetiner/roundcube-rule-exclusions-plugin
sogo-rule-exclusions-plugin         EsadCetiner/sogo-rule-exclusions-plugin
iredadmin-rule-exclusions-plugin    EsadCetiner/iredadmin-rule-exclusions-plugin
plausible-rule-exclusions-plugin    EsadCetiner/plausible-rule-exclusions-plugin
wordpress-hardening-plugin          eilandert/wordpress-hardening-plugin
vimbadmin-crs-plugin                eilandert/vimbadmin-crs-plugin
netnea-crs-upgrading-plugin         netnea/netnea-crs-upgrading-plugin
machine-learning-integration-plugin coreruleset/machine-learning-integration-plugin
performance-plugin                  coreruleset/performance-plugin
database-logging-plugin             coreruleset/database-logging-plugin
referer-hardening-plugin            coreruleset/referer-hardening-plugin
false-positive-report-plugin        coreruleset/false-positive-report-plugin
traffic-observation-plugin          coreruleset/traffic-observation-plugin

Some names already end in -plugin, some do not; four repositories are owned by third parties. So this feature needs the machine-readable index proposed in coreruleset/plugin-registry#21 (registry.json at a stable URL) rather than string manipulation, and it should not be built before that exists. Parsing the README table at runtime would be worse than the status quo.

coreruleset/plugin-registry#37 is the other half: the per-plugin plugin.yaml describes a plugin's configuration variables, which is what would let us map container environment variables onto plugin settings instead of requiring a mounted *-config.conf.

Versions have to be pinned, and not every plugin has a release. Sampling four:

plugin releases
fake-bot 2, latest v1.1.0
wordpress-rule-exclusions 4, latest v1.2.0
roundcube-rule-exclusions 4, latest v1.0.4
antivirus none

Installing from a moving branch would mean the rule set changes under a container restart, so a plugin without a release needs a decided answer: refuse it, or require an explicit commit SHA.

Trust

This is the part that needs a decision before any code.

The images verify the CRS tarball with gpg --verify against the CRS signing key. Downloading plugins at start would add rules to the same engine without that check — plugin releases carry no signatures today, and coreruleset/plugin-registry#21 puts signing out of scope for its first iteration. Four registry entries are third-party repositories, so "install by name" would mean fetching and loading code from outside the project on the strength of a name in a table.

Suggested posture, to be argued:

  • off unless CRS_PLUGINS is set, and never a default
  • restrict to registry entries marked type: official unless a second variable explicitly opts into third-party plugins
  • require a pinned version, with an optional sha256 per entry that is verified when present
  • keep the build-time route (docker build with a plugin list, producing an image with the plugins baked in and scanned) documented as the recommended production path, with the runtime variable positioned as a convenience for trying things out
  • fail closed: if an entry cannot be resolved, verified or unpacked, the container does not start rather than starting with a partial rule set

Runtime constraints worth stating in the design

  • egress at container start, which some deployments do not allow — the feature has to be entirely inert when the variable is unset
  • air-gapped and offline installs, where mounting the files stays the only option
  • read-only root filesystem variants (see #172), where writing to /opt/owasp-crs/plugins is not possible
  • startup latency and the failure mode when the registry or a release is unreachable
  • rule ID collisions between plugins, which the registry ranges make detectable

Acceptance criteria

  • CRS_PLUGINS accepting a comma-separated list of name or name@version, documented in the README with the trust caveats
  • resolution through the registry index from coreruleset/plugin-registry#21, no name-to-repository guessing
  • pinned versions only, with the no-release case decided and documented
  • the download step runs before plugin activation, so the existing activate-plugins.sh enables what was installed
  • inert when unset: no network access, no directory creation, identical behaviour to today
  • fail closed on resolution, verification or unpack errors
  • verification of downloaded artifacts, or an explicit written statement of what is not verified and why
  • a test that installs a plugin and asserts its rules are loaded, plus one asserting the unset case changes nothing

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with src/opt/modsecurity/activate-plugins.sh and the nginx and Apache entrypoint paths, then review the registry and plugin.yaml dependencies named in the issue. The work is not ready to implement until version, trust, verification, and no-release decisions are settled. Done requires the documented acceptance criteria, including pre-activation installation, fail-closed behavior, inert unset behavior, and installation and unset-case tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker, dockerfile, shell
Domain
devops, infrastructure, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
28/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.