Skip to content

Version 2.1.0

Choose a tag to compare

@paskal paskal released this 11 Sep 22:42
· 67 commits to master since this release
v2.1.0
0d7eb15

A hardening release on top of the 2.0 engine: nginx denies Bitrix routes the site does not use, the backup and restore scripts fail instead of reporting success on a partial dump, the dev-site renewal no longer publishes production connection settings, and a Zabbix template for the backups is supplied. The upgrade notes below list the deployment changes an existing installation has to make.

Security

  • nginx mounts both web trees read-only. PHP keeps its writable mounts for uploads, cache and updates; a compromised nginx worker can no longer plant a script the PHP location would execute.
  • Server-side source files under /upload/ (.php, .phtml, .pht, .phar, .inc) return 404, and uploaded HTML is served as plain text, as the Bitrix advisory for CVE-2023-1720 recommends.
  • Denied Bitrix routes: /bitrix/tools/bizproc/user_selector.php, /bitrix/tools/calendar/push.php, /bitrix/tools/stssync.php, the two sonet_* tool loaders, the untracked Bitrix24 helper proxy at /ajax/b24_bp_handler.php, and the SProduction basket-sync endpoint. A fork that uses one of these modules needs to lift the corresponding return 404 in bitrix.conf. The SProduction deal callback remains reachable, and its diagnostic logs under /upload/sproduction.integration/ are no longer public.
  • Security headers are emitted on error responses as well.
  • bots.conf denies PHP to crawlers that ignore robots.txt and to rotating scanner ranges; static assets are still served. The README documents which user agents are flagged and which are denied.
  • Adminer runs on its own internal network with nginx only. It reaches MySQL over the mounted socket and no longer needs the shared bridge, where a user-supplied server field could make it open connections to any container.
  • Zabbix MySQL credentials moved out of item keys into a mounted client configuration (see upgrade note 1).
  • renew-dev.sh builds the dev copies of dbconn.php and .settings.php from the production files before the copy and excludes both from rsync; a connection file in a layout it cannot rewrite aborts before the dev database is dropped, so production connection settings are never published to the dev site.

Backups and recovery

  • mysql-dump.sh runs under bash with pipefail and promotes the dump from a temporary name only after it passed the checks, so a failed or undersized dump is never uploaded, while a valid dump survives a failed S3 sync. Any compressed archive below 1,000,000 bytes is rejected; a fork with a small database has to lower that guard in the script. Credentials and temporary output are removed when the script fails, and the S3 sync skips leftover temporary dumps.
  • disaster-recovery.sh: the unattended restore used to skip itself on a fresh clone; it now restores into a staging directory, waits for MySQL and for the unattended-upgrades dpkg lock, checks the archive and runs the import under pipefail.
  • compare-backups.sh fails on a diff error instead of reporting a completed comparison, and restores into fresh directories.
  • config/zabbix/templates/backup-monitoring.yaml alerts on stale or empty backups once imported and linked to the host.

nginx

  • Bitrix composite snapshots are served directly by nginx, HEAD included. This is inert until composite is enabled in the Bitrix admin and snapshots exist; see "Composite site" in the README.

Operations

  • scripts/pull-public.sh is the production pull wrapper for this deployment: it handles the root-owned cron and logrotate directories, restarts php-cron only when its file-mounted cron file changed inode, and tests a changed nginx file bind in a fresh container before restarting nginx. It is written for this repository's origin and layout; the README paragraph on scripts/pull-public.sh covers adopting it.
  • logrotate rotates the site cron logs under the webroot, no longer trips over a duplicate glob that made every daily run exit 1, and the MySQL postrotate sources its credentials by absolute path, so flush-logs runs.
  • Bitrix cron stderr lands in cron_events.log.
  • Dev renewal runs private/scripts/renew-dev-post.sh from the overlay when present.
  • Optional Zabbix templates for the site automation live under config/zabbix/templates/, with their alert routing in config/zabbix/ALERT-ROUTING.md.

Upgrade notes

  1. Monitoring profile: private/environment/zabbix-mysql-session.conf is now a required bind for zabbix-agent, so the service cannot be recreated without it. Copy config/zabbix/mysql-session.conf.example there with your monitoring credentials, then in Zabbix set {$MYSQL.DSN} to LocalMysql and leave {$MYSQL.USER} and {$MYSQL.PASSWORD} empty (private/environment/zabbix.env.example).
  2. FTP moved to the ftp-manual profile with restart: no; start it on demand and do not add the profile to COMPOSE_PROFILES. The updater no longer publishes a host port; nginx reaches it on the Compose network, and anything that connected to the host port has to go through nginx.
  3. Recreate nginx and Adminer after pulling: COMPOSE_PROFILES=<your profiles> docker compose up -d --force-recreate --no-deps nginx adminer. The read-only web mounts and the Adminer network isolation apply only to recreated containers. Nothing in this repository writes site files through nginx; a workflow that did now fails with "Read-only file system".
  4. scripts/mysql-dump.sh is a bash script. Run it as an executable, as the shipped cron line does, not as sh scripts/mysql-dump.sh.
  5. binlog_expire_logs_seconds in config/mysql/my.cnf is 432000 (five days); it was 18000, five hours, too short to bridge two dumps. Apply it live with SET GLOBAL binlog_expire_logs_seconds=432000 or restart MySQL, and allow disk for five days of your binlog volume.