IAM appliance - nginx-modsec-docker

Image built atop debian:13-slim with nGinx from official Debian packages.

Image versioning

This image is versioned by nGinx .deb version.

Naming scheme is pretty simple: bcv-nginx-modsec:NGINX_DEB_PACKAGE_VERSION-rIMAGE_VERSION.

  • Image name is bcv-nginx-modsec.

  • NGINX_DEB_PACKAGE_VERSION is a version of nGinx in the image.

  • IMAGE_VERSION is an image release version written as a serial number, starting at 0. When images have the same Tomcat versions but different image versions it means there were some changes in the image itself (setup scripts, etc.) but application itself did not change.

Example

bcv-nginx-modsec:1.26.3-3+deb13u9-r0    // first release of image
bcv-nginx-modsec:1.26.3-3+deb13u9-r2    // third release of the same image
bcv-nginx-modsec:1.30.4-3+deb14u1-r0    // first release of image with differrent nGinx version

Building

Simply cd to the directory which contains the Dockerfile and issue docker build --no-cache -t <image tag here> ./.

The build process:

  1. Pulls debian:13-slim image.

  2. Updates binaries inside the image.

  3. Installs nGinx, ModSecurity, CRS ruleset and necessary tooling.

  4. Creates startup scripts structure inside /runscripts folder in the image. If you want to add your scripts to the runscripts, simply place them between sources and run the build process.

No security hardening is performed.

Use

Image can be used without further configuration because it contains some defaults.

  • Deploy directory is /var/www/html.

  • Config directory is /etc/nginx/conf.d.

  • STDOUT/STDERR logging, even for ModSecurity. You have to filter the logfile yourself.

Container runscripts

Container follows usual BCV structure of runscripts: run.sh, run.d/, runOnce.sh, runOnce.d/, runEvery.sh, runEvery.d/, startNginx.sh, startNginx.d/.

Notable runscripts:

  • runOnce.d/000_001-updateTimezone.sh - Updates container timezone according to the value in TZ variable, see the variable doc for details.

  • runOnce.d/000_002-createNginxConfig.sh - Creates actual nGinx configuration from the template and environment variable.

  • runOnce.d/000_003-createModsecConfig.sh - Creates actual ModSecurity master configuration from the template and environment variable. Does not touch rule files.

Container shutdown

When initialized and running, the process tree in container looks like this:

run.sh
|___ startNginx.sh (that execs to nginx binary)

Upon shutdown, Docker sends SIGTERM to the run.sh process. This process traps the SIGTERM and sends it to its direct child process (the nginx master process), politely terminating the application. The run.sh also waits until all processes of tomcat user terminate or until the STOP_TIMEOUT is reached. Afterwards, it waits another 1 second just to be safe.

Please note that Docker also implements timeout for container shutdowns. If the STOP_TIMEOUT is set too high, it may be overriden by Docker from the outside and Docker will kill the container before stop timeout is reached.

Environment variables

You can pass a number of environment variables into the container. All variables below are applied on the first start of the container only. Since they are expected to be set in the compose file, changing any of them makes compose recreate the container, so the new values take effect on its next (first) start.

  • STOP_TIMEOUT - Number of seconds (at most) the run.sh will wait for Tomcat to terminate. May be overriden by Docker itself (by killing the container). Default: 15s.

  • RUNONCE_BREAKPOINT - When set (even to empty string), causes run.sh to sleep for 3600s so you can exec into the container and look around.

  • RUNEVERY_BREAKPOINT - When set (even to empty string), causes run.sh to sleep for 3600s so you can exec into the container and look around.

  • STARTNGINX_BREAKPOINT - When set (even to empty string), causes run.sh to sleep for 3600s so you can exec into the container and look around.

  • TZ - On the first start of the container, we set the timezone. Syntax is IANA tzdata (the same you know from Linux). Default: UTC.

  • NGINX_WORKER_CONNECTIONS - Maximum number of simultaneous connections per nginx worker process (nginx worker_connections). The number includes connections to upstreams, not only client connections. Default: 1024.

  • NGINX_SSL_PROTOCOLS - Space-separated list of enabled TLS protocols (nginx ssl_protocols). Default: TLSv1.2 TLSv1.3.

  • NGINX_SSL_PREFER_SERVER_CIPHERS - Whether the server’s cipher order takes precedence over the client’s (nginx ssl_prefer_server_ciphers). Values: on, off. Default: off.

  • NGINX_SSL_CIPHERS - Colon-separated list of enabled ciphers in OpenSSL format (nginx ssl_ciphers). Affects TLSv1.2 and older only, TLSv1.3 ciphers are not configured by this. Default: ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305.

  • NGINX_SSL_ECDH_CURVE - Colon-separated list of key exchange groups (nginx ssl_ecdh_curve). Default: X25519MLKEM768:X25519:prime256v1:secp384r1.

  • NGINX_LARGE_HEADER_BUFFERS_NUM, NGINX_LARGE_HEADER_BUFFERS_SIZE - Number and size of buffers used for reading large client request headers (nginx large_client_header_buffers). The request line and each single header must fit into one buffer, otherwise the client gets 414 or 400. Increase (e.g. to 4 x 32k) when clients send large cookies or tokens. Default: 4 and 8k.

  • NGINX_MODSECURITY - Enables ModSecurity in nginx (modsecurity directive of the ModSecurity-nginx connector). Values: on, off. When off, all MODSEC_* variables have no effect. Default: on.

  • NGINX_GZIP_MIN_LENGTH - Minimum response length in bytes for the response to be gzip-compressed (nginx gzip_min_length). Default: 10240.

  • MODSEC_SecRuleEngine - Rule processing mode. On - rules are processed and their actions (e.g. blocking) applied, DetectionOnly - rules are processed and matches logged, but no request is blocked, Off - rules are not processed. Default: On.

  • MODSEC_SecRequestBodyAccess - Whether request bodies are buffered and inspected. Required for inspecting POST parameters. Values: On, Off. Default: On.

  • MODSEC_SecRequestBodyLimit - Maximum request body size in bytes, including uploaded files. Default: 13107200 (12 MB).

  • MODSEC_SecRequestBodyNoFilesLimit - Maximum request body size in bytes, excluding files transferred in multipart/form-data requests. Default: 131072 (128 kB).

  • MODSEC_SecRequestBodyLimitAction - What to do when the request body exceeds MODSEC_SecRequestBodyLimit. Reject - the request is rejected (413), ProcessPartial - only the part up to the limit is inspected, the rest is passed through uninspected. Default: ProcessPartial.

  • MODSEC_SecRequestBodyJsonDepthLimit - Maximum nesting depth of a JSON request body. Exceeding it is treated as a request body parsing error. Default: 64.

  • MODSEC_SecResponseBodyAccess - Whether response bodies are buffered and inspected (e.g. for data leakage detection). Increases memory usage and latency. Values: On, Off. Default: On.

  • MODSEC_SecResponseBodyMimeType - Space-separated list of MIME types whose response bodies are inspected. Default: text/plain text/html text/xml.

  • MODSEC_SecResponseBodyLimit - Maximum response body size in bytes that is buffered for inspection. Default: 524288 (512 kB).

  • MODSEC_SecResponseBodyLimitAction - What to do when the response body exceeds MODSEC_SecResponseBodyLimit. Reject - the response is replaced by an error, ProcessPartial - only the part up to the limit is inspected. Default: ProcessPartial.

  • MODSEC_SecUploadKeepFiles - Whether files intercepted from uploads are kept on disk after the transaction. On - always, RelevantOnly - only for transactions relevant for audit logging, Off - never. Default: Off.

    • WARNING: We consider this option a debug feature. Enabling it causes intercepted files to be stored inside the container!

  • MODSEC_SecAuditEngine - Audit logging mode. On - all transactions are logged, RelevantOnly - only transactions that triggered a warning/error or whose status matches MODSEC_SecAuditLogRelevantStatus, Off - no audit logging. Default: RelevantOnly.

  • MODSEC_SecAuditLogRelevantStatus - Regular expression matched against the response status code; matching transactions are considered relevant for audit logging. The default matches all 5xx and all 4xx except 404. The double quotes are part of the value and need to be properly escaped in the compose file. Default: "^(?:5|4(?!04))".

  • MODSEC_SecAuditLogParts - Letters selecting the parts of a transaction written to the audit log. A - audit log header, B - request headers, C - request body, E - response body, F - response headers, H - audit log trailer, Z - final boundary (mandatory). Default: AFHZ.

Mounted files and volumes

  • Mandatory

    • Directory with virtual hosts configuration

      • This directory contains .conf files that are included inside the http{} context of the nGinx configuration. Usually, those files contain definitions of server{} blocks.

      • Without this directory mounted (and populated), your nGinx configuration will contain no hosts.

      • Example

        volumes:
          - type: bind
            source: ./config
            target: /etc/nginx/conf.d
            read_only: true
    • Server certificate and key

      • Those files contain server certificate chain and server key.

      • Without those files, nGinx will have no TLS certificates in place.

      • Example

        volumes:
          - type: bind
            source: ./secrets/current_web_cert.key
            target: /etc/nginx/cert/current_web_cert.key
            read_only: true
          - type: bind
            source: ./secrets/current_web_cert.crt
            target: /etc/nginx/cert/current_web_cert.crt
            read_only: true
  • Optional

    • Let’s Encrypt challenges directory

      • Empty directory where certbot places ACME challenges.

      • Without this directory mounted, the host-based certbot utility will not be able to use http-01 challenge.

      • Example

        volumes:
          - type: bind
            source: ./letsencrypt
            target: /var/www/html/letsencrypt
            read_only: true

Forbidden variables

  • RUNSCRIPTS_PATH - Defined in the Dockerfile and used during both build of the image and life of the container. This is a root folder from which the startup scripts locate each other. If you change it, the container start process will go haywire. For safety reasons, this variable is set as readonly in the run.sh.