diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/ci.yml similarity index 92% rename from .github/workflows/docker-publish.yml rename to .github/workflows/ci.yml index 07315bf..026ad69 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/ci.yml @@ -1,8 +1,8 @@ -name: Docker Publish +name: CI on: push: - tags: [ 'cp*.*.*-rev*' ] + tags: [ '*.*.*-r*' ] pull_request: branches: [ master ] @@ -62,11 +62,11 @@ jobs: with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} tags: | - type=match,pattern=cp(\d+\.\d+),group=1 - type=match,pattern=cp\d+\.\d+ - type=match,pattern=cp([^-]+),group=1 - type=match,pattern=cp[^-]+ - type=match,pattern=cp.+ + type=match,pattern=(\d+\.\d+),group=1 + type=match,pattern=\d+\.\d+ + type=match,pattern=([^-]+),group=1 + type=match,pattern=[^-]+ + type=match,pattern=.+ # Build and push Docker image with Buildx (don't push on PR) # https://github.com/docker/build-push-action diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..93d1f86 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +## 1.7.1-rc1 + +* **BREAKING CHANGES**: + + * Migrated to Alpine + Nginx + php-fpm. This allowed to reduce memory usage from ~300MB to ~40MB! + * Changed versioning convention from `cpXXX-revYYY` to `CLASSICPRESS_VERSION-rRELEASE_NUMBER` + * Migrated from `APACHE_RUN_USER_ID` and `APACHE_RUN_GROUP_ID` to shared between host and container group `press(gid=2048)`. + * `wp-config.template.php` now contains `define('WP_AUTO_UPDATE_CORE', false);` which should stop CP from auto-updating (this is added only to new installations, so it may be that you must add it manually to your config) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 064af22..ef81714 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -4,16 +4,16 @@ The simplest way to build this image locally, it's to use Docker Compose. To do so, you will need to create 2 files: `CP_DB_PASSWORD.secret` and `DB_ROOT_PASSWORD.secret` (files with `.secret` extension are git-ignored). ```sh -export UID=$(id -u) -export GID=$(id -g) echo "my_secret_password" > secrets/CP_DB_PASSWORD.secret echo "turbo_secret_password" > secrets/DB_ROOT_PASSWORD.secret ``` +Remamber to have a group `press(gid=2048)` and your user is assigned to it. + Now, you simply run: ```sh docker compose -f docker-compose.dev.yaml up ``` -The container will be accessible via http://localhost:8000 +The container will be accessible via http://localhost:8080 diff --git a/README.md b/README.md index 02cfd50..d8ca3a6 100644 --- a/README.md +++ b/README.md @@ -4,17 +4,25 @@ Quote from [www.classicpress.net](https://www.classicpress.net/): > ClassicPress is a community-led open source content management system and a fork of WordPress that preserves the TinyMCE classic editor as the default option. +## Changelog + +[Open changelog](https://github.com/marverix/classicpress-docker/blob/master/CHANGELOG.md) + ## Tags + ### Convention -Tagging convention is: `cpXXX-revYYY` +Tagging convention is: `CLASIC_PRESS_VERSION-rRELEASE` + +`CLASIC_PRESS_VERSION` is ClassicPress version, `RELEASE` is Docker Image release number. Eg. `1.7.1-r1`. -`XXX` is ClassicPress version. `YYY` is Docker Image revision number. ## Basic Information -* The image is based on [`php:7.4-apache-bullseye`](https://hub.docker.com/_/php?tab=tags&name=7.4-apache-bullseye) +* The image is based on Alpine 3.16 and php 8.0 (3.16 is a bit old, but it's last version having php8.0 which is required by ClassicPress 1.x) +* Some code taken from [`TrafeX/docker-php-nginx:2.5.0`](https://github.com/TrafeX/docker-php-nginx) which I highly recommend! Unfortunatelly I coudln't use it (inherit) because Docker has no mechanism to "unexpose" port and remove health check +* Thanks to Alpine + Nginx + php-fpm, the image is using only around ~40MB of RAM * Has enabled all required and recommended php extensions for WordPress -* Has installed [`apache2-mod-security2`](https://github.com/SpiderLabs/ModSecurity) with [enabled OWASP CSR](https://owasp.org/www-project-modsecurity-core-rule-set/) +* Basic security hardening done * Support for Docker Secrets via env variables with `_FILE` suffix Note: Even with basic hardening done, it's highly recommended to not to expose a container directly to the outside world. Consider using a reverse proxy like [traefik](https://doc.traefik.io/traefik/) or [Nginx Proxy Manager](https://nginxproxymanager.com/). @@ -28,35 +36,22 @@ https://hub.docker.com/r/marverix/classicpress/tags Good Docker practice is that one service/server == one docker container, this is why you will still need to run separate container with a database (MySQL/MariaDB). -### Privilages - -Apache server inside is using two environment variables to set privilages: - -* `APACHE_RUN_USER_ID` -* `APACHE_RUN_GROUP_ID` - -By default Docker runs everything with _root_ privilages. There are many great articles describing the impications of this solution, like: +### Write Permission -* [File Permissions: the painful side of Docker](https://blog.gougousis.net/file-permissions-the-painful-side-of-docker/) -* [Permission problems in bind mount in Docker Volume](https://techflare.blog/permission-problems-in-bind-mount-in-docker-volume/) -* [File permissions on Docker volumes](https://ikriv.com/blog/?p=4698) +This image deals with write access shared between host and the container by group `press` (and user) with ID _2048_. This is why your user running the container must be in this group. -This is why this image is running the Apache server as `apache:apache`. -The trick here is, that Apache's user ID (`APACHE_RUN_USER_ID`) and group ID (`APACHE_RUN_GROUP_ID`) are set on fly, to user ID and group ID of the docker container runner. Only downside of this solution is that -you need to set `UID` and `GID` env variables (for example in `~/.bashrc`) like this: +If you are running Debian/Ubuntu-based run on your host machine: ```sh -export UID=$(id -u) -export GID=$(id -g) +sudo groupadd -g 2048 press +sudo usermod $(whoami) -aG press ``` -With this, you can use it in Docker Compose like this: +Read more: -```yaml - environment: - - "APACHE_RUN_USER_ID=${UID}" - - "APACHE_RUN_GROUP_ID=${GID}" -``` +* [File Permissions: the painful side of Docker](https://blog.gougousis.net/file-permissions-the-painful-side-of-docker/) +* [Permission problems in bind mount in Docker Volume](https://techflare.blog/permission-problems-in-bind-mount-in-docker-volume/) +* [File permissions on Docker volumes](https://ikriv.com/blog/?p=4698) ### With Docker Compose @@ -105,8 +100,6 @@ docker-compose -f docker-compose.example.yaml --env-file=myblog-env-example up --expose 80:80 \ --name myblog \ --volume myblog_data:/data \ - --env APACHE_RUN_USER_ID=$UID \ - --env APACHE_RUN_GROUP_ID=$GID \ --env CP_DB_NAME=myblog_db \ --env CP_DB_USER=myblog_user \ --env CP_DB_PASSWORD=my_secret_passowrd \ diff --git a/classicpress/000-default.conf b/classicpress/000-default.conf deleted file mode 100644 index c09971e..0000000 --- a/classicpress/000-default.conf +++ /dev/null @@ -1,15 +0,0 @@ - - DocumentRoot /var/www/html - - ServerName localhost - ServerAdmin webmaster@localhost - ServerSignature Off - - ErrorLog ${APACHE_LOG_DIR}/error.log - CustomLog ${APACHE_LOG_DIR}/access.log combined - - AddDefaultCharset UTF-8 - - SecRuleEngine On - SecStatusEngine On - diff --git a/classicpress/Dockerfile b/classicpress/Dockerfile index bdcf448..7a36735 100644 --- a/classicpress/Dockerfile +++ b/classicpress/Dockerfile @@ -1,8 +1,11 @@ -FROM php:8.1-apache-bullseye +FROM alpine:3.16 + +LABEL Maintainer="Marek SierociƄski" +LABEL Description="ClassicPress image based on trafex/php-nginx" # Version of ClassicPress -ARG version=1.5.3 -ARG corerules_version=3.3.4 +ARG version=1.7.1 +# ARG corerules_version=3.3.4 ARG www_dir=/var/www/html ARG WORKDIR_BUILD=/tmp/build @@ -13,45 +16,59 @@ ENV WWW_DIR=${www_dir} ENV DATA_DIR=/data ENV WP_CONFIG=${DATA_DIR}/wp-config.php ENV WP_CONTENT=${DATA_DIR}/wp-content -ENV BACKUP_WP_CONTENT="${WWW_DIR}/../wp-content-backup" +ENV PRESS_USER=press +ENV PRESS_HOME=/home/${PRESS_USER} +ENV BACKUP_WP_CONTENT="${PRESS_HOME}/wp-content-backup" -ENV APACHE_RUN_USER=apache -ENV APACHE_RUN_GROUP=www-data +WORKDIR /var/www/html -ENV LC_ALL=en_US.UTF-8 -ENV LANG=en_US.UTF-8 -ENV LANGUAGE=en_US.UTF-8 +# Update packages index +RUN apk update \ + # Install packages + && apk add --no-cache \ + bash \ + curl \ + nginx \ + php8 \ + php8-ctype \ + php8-curl \ + php8-dom \ + php8-fpm \ + php8-gd \ + php8-intl \ + php8-mbstring \ + php8-mysqli \ + php8-opcache \ + php8-openssl \ + php8-phar \ + php8-session \ + php8-xml \ + php8-xmlreader \ + php8-zlib \ + supervisor COPY ./ ${WORKDIR_FILES}/ -# Install packages -RUN apt-get update \ - && apt-get install -y \ - apt-transport-https lsb-release ca-certificates wget gnupg2 \ - && wget -qO - https://modsecurity.digitalwave.hu/archive.key | apt-key add - \ - && sh -c 'echo "deb http://modsecurity.digitalwave.hu/debian/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/dwmodsec.list' \ - && sh -c 'echo "deb http://modsecurity.digitalwave.hu/debian/ $(lsb_release -sc)-backports main" >> /etc/apt/sources.list.d/dwmodsec.list' \ - && apt-get update \ - && apt-get install -y \ - libapache2-mod-security2 libmodsecurity3 \ - zlib1g-dev libpng16-16 libpng-dev libzip4 libzip-dev locales \ -# Ensure UTF-8 - && sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen \ - && locale-gen \ -# Change working directory - && mkdir -p ${WORKDIR_DOWNLOADS} \ - && cd ${WORKDIR_DOWNLOADS} \ -# Download ClassicPress +# Prepare users +RUN adduser -D -u 2048 press \ + && addgroup press press \ + && addgroup press nginx + +# Configure services +RUN cp ${WORKDIR_FILES}/conf/php.ini /etc/php8/conf.d/custom.ini \ + && cp ${WORKDIR_FILES}/conf/fpm-pool.conf /etc/php8/php-fpm.d/www.conf \ + && cp ${WORKDIR_FILES}/conf/nginx.conf /etc/nginx/nginx.conf \ + && cp ${WORKDIR_FILES}/conf/supervisord.conf /etc/supervisord.conf \ +&& rm -rf ${www_dir}/* + +# Install ClassicPress +RUN mkdir -p ${WORKDIR_DOWNLOADS} && cd ${WORKDIR_DOWNLOADS} \ && wget -qO classicpress.tar.gz https://github.com/ClassicPress/ClassicPress-release/archive/refs/tags/${version}.tar.gz \ -# Download corerules - && wget -qO corerules.tar.gz https://github.com/coreruleset/coreruleset/archive/refs/tags/v${corerules_version}.tar.gz \ -# Clean www_dir - && rm -rf ${www_dir}/* \ -# Unpack ClassicPress to www_dir - && tar -xf classicpress.tar.gz -C ${www_dir} --strip-components=1 \ + && tar -xf classicpress.tar.gz -C ${www_dir} --strip-components=1 + +# Setup # Create /data - && mkdir ${DATA_DIR} \ - && cd ${DATA_DIR} \ +RUN mkdir ${DATA_DIR} && cd ${DATA_DIR} \ # Move wp-content to /data && mv ${www_dir}/wp-content ${WP_CONTENT} \ && ln -s ${WP_CONTENT} ${www_dir}/wp-content \ @@ -60,53 +77,24 @@ RUN apt-get update \ && touch ${WP_CONFIG} \ && ln -s ${WP_CONFIG} ${www_dir}/wp-config.php \ # Copy wp-config.template.php - && cp ${WORKDIR_FILES}/wp-config.template.php ${www_dir}/../wp-config.template.php \ -# Copy php.ini - && cp ${WORKDIR_FILES}/php.ini "${PHP_INI_DIR}/php.ini" \ -# Install missing php extensions - && EXTRA_CFLAGS="-I/usr/src/php" docker-php-ext-install \ - exif gd mysqli zip \ -# Enable apache mod-rewrite - && a2enmod rewrite \ -# Enable apache mod-headers - && a2enmod headers \ -# Enable mod-security2 - && a2enmod security2 \ - && cp /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf \ - && sed -Ei "s/Sec([A-Z][a-z]+)Engine .+/Sec\1Engine On/g" /etc/modsecurity/modsecurity.conf \ - && cp ${WORKDIR_FILES}/security2.conf /etc/apache2/mods-available/security2.conf \ -# Setup mod-security2 - && cd /usr/share/modsecurity-crs \ - && rm -rf ./* \ - && tar -xf ${WORKDIR_DOWNLOADS}/corerules.tar.gz -C ./ --strip-components=1 \ - && mv crs-setup.conf.example crs-setup.conf \ - && mv ./rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf.example ./rules/REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.conf \ -# Copy default site conf - && cp ${WORKDIR_FILES}/000-default.conf /etc/apache2/sites-available/000-default.conf \ -# Copy security conf - && cp ${WORKDIR_FILES}/security.conf /etc/apache2/conf-available/security.conf \ -# Copy htaccess - && cp ${WORKDIR_FILES}/htaccess ${DATA_DIR}/.htaccess \ - && ln -s ${DATA_DIR}/.htaccess ${www_dir}/.htaccess \ + && cp ${WORKDIR_FILES}/wp-config.template.php ${PRESS_HOME}/wp-config.template.php \ # Copy startup script - && cp ${WORKDIR_FILES}/classicpress.sh /opt/classicpress.sh \ -# Create user - && useradd -rMUG daemon,www-data apache \ + && cp ${WORKDIR_FILES}/classicpress.sh ${PRESS_HOME}/classicpress.sh \ +# Privilages + && cp ${WORKDIR_FILES}/ch/chmod /bin/schmod \ + && cp ${WORKDIR_FILES}/ch/chgrp /bin/schgrp \ + && chmod +s /bin/schmod /bin/schgrp \ + && chmod +x ${PRESS_HOME}/classicpress.sh \ + && chown -R ${PRESS_USER}:${PRESS_USER} /data ${www_dir} \ + && chmod -R 777 /run /var/log \ # Clean - && apt-get purge -y --auto-remove \ - wget zlib1g-dev \ - && apt-get autoclean \ - && rm -r /var/lib/apt/lists/* \ && rm -rf /tmp/* -# Set back www_dir as workdir -WORKDIR ${www_dir} +USER press -# Expose port 80 EXPOSE 80 -# It's important to NOT change user with USER. We want to have root permissions -# in startup script, so we can dynamcally change UID/GID and ownership. +HEALTHCHECK --timeout=10s CMD curl --silent --fail http://127.0.0.1/fpm-ping # Set startup command -CMD [ "/opt/classicpress.sh" ] +CMD [ "/home/press/classicpress.sh" ] diff --git a/classicpress/ch/chgrp b/classicpress/ch/chgrp new file mode 100755 index 0000000..9901960 Binary files /dev/null and b/classicpress/ch/chgrp differ diff --git a/classicpress/ch/chmod b/classicpress/ch/chmod new file mode 100755 index 0000000..553f792 Binary files /dev/null and b/classicpress/ch/chmod differ diff --git a/classicpress/ch/chown b/classicpress/ch/chown new file mode 100755 index 0000000..7a19db3 Binary files /dev/null and b/classicpress/ch/chown differ diff --git a/classicpress/classicpress.sh b/classicpress/classicpress.sh index d0b4afa..60f55ac 100755 --- a/classicpress/classicpress.sh +++ b/classicpress/classicpress.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +schgrp -R press /data + # ClassicPress Startup Script TMP_WP_CONFIG=/tmp/wp-config.php @@ -33,7 +35,6 @@ function store_env() { sed -i "s/$1/${!1}/g" "${TMP_WP_CONFIG}" } - # Checking wp-config.php if [ ! -f "${WP_CONFIG}" ]; then echo "Notice: File ${WP_CONFIG} not found - touching" @@ -41,8 +42,8 @@ if [ ! -f "${WP_CONFIG}" ]; then fi if [ ! -w "${WP_CONFIG}" ]; then - echo "Error: File ${WP_CONFIG} found, but not writable. Have you mounted the /data as a volume?" - exit 1 + echo "Notice: File ${WP_CONFIG} found, but not writable. Fixing" + schmod g+w "${WP_CONFIG}" fi if [ -s "${WP_CONFIG}" ]; then @@ -71,7 +72,7 @@ else random_env "CP_NONCE_SALT" echo "Preparing wp-config.php ..." - cp "${WWW_DIR}/../wp-config.template.php" "${TMP_WP_CONFIG}" + cp "${PRESS_HOME}/wp-config.template.php" "${TMP_WP_CONFIG}" store_env "CP_DB_NAME" store_env "CP_DB_USER" store_env "CP_DB_PASSWORD" @@ -99,14 +100,7 @@ else cp -r "${BACKUP_WP_CONTENT}" "${WP_CONTENT}" fi +schmod -R g+w "${WP_CONTENT}" -# Chanigin ownership -echo "Changing ownership..." -sed -Ei "s/$APACHE_RUN_USER:x:[0-9]+:[0-9]+/$APACHE_RUN_USER:x:$APACHE_RUN_USER_ID:$APACHE_RUN_GROUP_ID/g" /etc/passwd -chown -R ${APACHE_RUN_USER}:${APACHE_RUN_GROUP} ${WWW_DIR} ${DATA_DIR} - - -# Starting apache -echo "Starting Apache in foreground ..." -# https://github.com/docker-library/php/blob/master/7.4/bullseye/apache/apache2-foreground -apache2-foreground +# Starting +supervisord diff --git a/classicpress/conf/fpm-pool.conf b/classicpress/conf/fpm-pool.conf new file mode 100644 index 0000000..4be2061 --- /dev/null +++ b/classicpress/conf/fpm-pool.conf @@ -0,0 +1,56 @@ +[global] +; Log to stderr +error_log = /dev/stderr + +[www] +; The address on which to accept FastCGI requests. +; Valid syntaxes are: +; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on +; a specific port; +; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on +; a specific port; +; 'port' - to listen on a TCP socket to all addresses +; (IPv6 and IPv4-mapped) on a specific port; +; '/path/to/unix/socket' - to listen on a unix socket. +; Note: This value is mandatory. +listen = /run/php-fpm.sock + +; Enable status page +pm.status_path = /fpm-status + +; Ondemand process manager +pm = ondemand + +; The number of child processes to be created when pm is set to 'static' and the +; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'. +; This value sets the limit on the number of simultaneous requests that will be +; served. Equivalent to the ApacheMaxClients directive with mpm_prefork. +; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP +; CGI. The below defaults are based on a server without much resources. Don't +; forget to tweak pm.* to fit your needs. +; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand' +; Note: This value is mandatory. +pm.max_children = 100 + +; The number of seconds after which an idle process will be killed. +; Note: Used only when pm is set to 'ondemand' +; Default Value: 10s +pm.process_idle_timeout = 10s; + +; The number of requests each child process should execute before respawning. +; This can be useful to work around memory leaks in 3rd party libraries. For +; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS. +; Default Value: 0 +pm.max_requests = 1000 + +; Make sure the FPM workers can reach the environment variables for configuration +clear_env = no + +; Catch output from PHP +catch_workers_output = yes + +; Remove the 'child 10 said into stderr' prefix in the log and only show the actual message +decorate_workers_output = no + +; Enable ping page to use in healthcheck +ping.path = /fpm-ping diff --git a/classicpress/conf/nginx.conf b/classicpress/conf/nginx.conf new file mode 100644 index 0000000..9962006 --- /dev/null +++ b/classicpress/conf/nginx.conf @@ -0,0 +1,161 @@ +worker_processes auto; +error_log stderr warn; +pid /run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + client_max_body_size 25m; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Define custom log format to include reponse times + log_format main_timed '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for" ' + '$request_time $upstream_response_time $pipe $upstream_cache_status'; + + access_log /dev/stdout main_timed; + error_log /dev/stderr notice; + + # Write temporary files to /tmp so they can be created as a non-privileged user + client_body_temp_path /tmp/client_temp; + proxy_temp_path /tmp/proxy_temp_path; + fastcgi_temp_path /tmp/fastcgi_temp; + uwsgi_temp_path /tmp/uwsgi_temp; + scgi_temp_path /tmp/scgi_temp; + + # Default server definition + server { + listen [::]:80 default_server; + listen 80 default_server; + server_name _; + + absolute_redirect off; + + root /var/www/html; + index index.php index.html; + + location / { + # First attempt to serve request as file, then + # as directory, then fall back to index.php + try_files $uri $uri/ /index.php?q=$uri&$args; + } + + # Redirect server error pages to the static page /50x.html + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /var/lib/nginx/html; + } + + # Pass the PHP scripts to PHP-FPM listening on php-fpm.sock + location ~ \.php$ { + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass unix:/run/php-fpm.sock; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param SCRIPT_NAME $fastcgi_script_name; + fastcgi_index index.php; + include fastcgi_params; + } + + location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ { + expires max; + log_not_found off; + } + + # Deny access to . files, for security + location ~ /\. { + log_not_found off; + deny all; + } + + # Allow fpm ping and status from localhost + location ~ ^/(fpm-status|fpm-ping)$ { + access_log off; + allow 127.0.0.1; + deny all; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + fastcgi_pass unix:/run/php-fpm.sock; + } + + # Global restrictions configuration file. + # Designed to be included in any server {} block. + location = /favicon.ico { + log_not_found off; + access_log off; + } + + location = /robots.txt { + allow all; + log_not_found off; + access_log off; + } + + # Deny all attempts to access hidden files such as .htaccess, .htpasswd, .DS_Store (Mac). + # Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban) + location ~ /\. { + deny all; + } + + # Deny access to any files with a .php extension in the uploads directory + # Works in sub-directory installs and also in multisite network + # Keep logging the requests to parse later (or to pass to firewall utilities such as fail2ban) + location ~* /(?:uploads|files)/.*\.php$ { + deny all; + } + } + + gzip on; + gzip_proxied any; + gzip_types text/plain application/xml text/css text/js text/xml application/x-javascript text/javascript application/json application/xml+rss; + gzip_vary on; + gzip_disable "msie6"; + + # Security + + # > Verify that the HTTP headers or any part of the HTTP response do not expose detailed version information + # > of system components. + # ~ 14.3.3 + server_tokens off; + + # > Verify that all responses contain a X-Content-Type-Options: nosniff header. + # ~ 14.4.4 + # === https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options + add_header X-Content-Type-Options "nosniff"; + + # > Verify that a suitable Referrer-Policy header is included to avoid exposing sensitive information + # > in the URL through the Referer header to untrusted parties. + # ~ 14.4.6 + # === https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy + add_header Referrer-Policy "strict-origin-when-cross-origin"; + + # > Verify that the content of a web application cannot be embedded in a third-party site by default and that + # > embedding of the exact resources is only allowed where necessary by using suitable + # > Content-Security-Policy: frame-ancestors and X-Frame-Options response headers. + # ~ 14.4.7 + # === https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options + add_header X-Frame-Options "SAMEORIGIN"; + + # > The X-XSS-Protection header has been deprecated by modern browsers and its use can introduce additional security + # > issues on the client side. As such, it is recommended to set the header as X-XSS-Protection: 0 in order to disable + # > the XSS Auditor, and not allow it to take the default behavior of the browser handling the response. + # === https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#x-xss-protection-header + add_header X-XSS-Protection "0"; + + # > The X-Content-Type-Options response HTTP header is a marker used by the server to indicate that the MIME types + # > advertised in the Content-Type headers should not be changed and be followed. This is a way to opt out of MIME type + # > sniffing, or, in other words, to say that the MIME types are deliberately configured. + # === https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options + add_header X-Content-Type-Options "nosniff"; +} diff --git a/classicpress/php.ini b/classicpress/conf/php.ini similarity index 99% rename from classicpress/php.ini rename to classicpress/conf/php.ini index 5477fa9..8f666f1 100644 --- a/classicpress/php.ini +++ b/classicpress/conf/php.ini @@ -965,7 +965,7 @@ cli_server.color = On [Date] ; Defines the default timezone used by the date functions ; https://php.net/date.timezone -;date.timezone = +date.timezone = UTC ; https://php.net/date.default-latitude ;date.default_latitude = 31.7667 diff --git a/classicpress/conf/supervisord.conf b/classicpress/conf/supervisord.conf new file mode 100644 index 0000000..216a387 --- /dev/null +++ b/classicpress/conf/supervisord.conf @@ -0,0 +1,23 @@ +[supervisord] +nodaemon=true +logfile=/dev/null +logfile_maxbytes=0 +pidfile=/run/supervisord.pid + +[program:php-fpm] +command=php-fpm8 -F +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=false +startretries=0 + +[program:nginx] +command=nginx -g 'daemon off;' +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=false +startretries=0 diff --git a/classicpress/htaccess b/classicpress/htaccess deleted file mode 100644 index f092a85..0000000 --- a/classicpress/htaccess +++ /dev/null @@ -1,7 +0,0 @@ -RewriteEngine On -RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] -RewriteBase / -RewriteRule ^index\.php$ - [L] -RewriteCond %{REQUEST_FILENAME} !-f -RewriteCond %{REQUEST_FILENAME} !-d -RewriteRule . /index.php [L] diff --git a/classicpress/security.conf b/classicpress/security.conf deleted file mode 100644 index faea0f7..0000000 --- a/classicpress/security.conf +++ /dev/null @@ -1,54 +0,0 @@ -# Disable access to the entire file system except for the directories that -# are explicitly allowed later. - - AllowOverride None - Require all denied - - -# ServerTokens -# This directive configures what you return as the Server HTTP response -# Header. The default is 'Full' which sends information about the OS-Type -# and compiled in modules. -# Set to one of: Full | OS | Minimal | Minor | Major | Prod -ServerTokens Prod - -# Optionally add a line containing the server version and virtual host -# name to server-generated pages (internal error documents, FTP directory -# listings, mod_status and mod_info output etc., but not CGI generated -# documents or custom error documents). -# Set to "EMail" to also include a mailto: link to the ServerAdmin. -# Set to one of: On | Off | EMail -ServerSignature Off - -# -# Allow TRACE method -# -# Set to "extended" to also reflect the request body (only for testing and -# diagnostic purposes). -# -# Set to one of: On | Off | extended -TraceEnable Off - -# > Verify that all responses contain a X-Content-Type-Options: nosniff header. -# ~ 14.4.4 -# === https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options -Header set X-Content-Type-Options: "nosniff" - -# > Verify that a suitable Referrer-Policy header is included to avoid exposing sensitive information -# > in the URL through the Referer header to untrusted parties. -# ~ 14.4.6 -# === https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy -Header set Referrer-Policy: "strict-origin-when-cross-origin" - -# > Verify that the content of a web application cannot be embedded in a third-party site by default and that -# > embedding of the exact resources is only allowed where necessary by using suitable -# > Content-Security-Policy: frame-ancestors and X-Frame-Options response headers. -# ~ 14.4.7 -# === https://developer.mozilla.org/en-US/docs/HTTP/X-Frame-Options -Header set X-Frame-Options: "SAMEORIGIN" - -# > The X-XSS-Protection header has been deprecated by modern browsers and its use can introduce additional security -# > issues on the client side. As such, it is recommended to set the header as X-XSS-Protection: 0 in order to disable -# > the XSS Auditor, and not allow it to take the default behavior of the browser handling the response. -# === https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#x-xss-protection-header -Header set X-XSS-Protection: "0" diff --git a/classicpress/security2.conf b/classicpress/security2.conf deleted file mode 100644 index 7c60582..0000000 --- a/classicpress/security2.conf +++ /dev/null @@ -1,5 +0,0 @@ - - SecDataDir /var/cache/modsecurity - Include /usr/share/modsecurity-crs/crs-setup.conf - Include /usr/share/modsecurity-crs/rules/*.conf - diff --git a/classicpress/wp-config.template.php b/classicpress/wp-config.template.php index df1f155..3e958f7 100644 --- a/classicpress/wp-config.template.php +++ b/classicpress/wp-config.template.php @@ -18,6 +18,7 @@ $table_prefix = 'CP_DB_TABLE_PREFIX'; define('WP_DEBUG', false); +define('WP_AUTO_UPDATE_CORE', false); // Lock CP version if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && strpos($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https') !== false) { $_SERVER['HTTPS'] = 'on'; diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml index 5dca0b8..ca51f96 100644 --- a/docker-compose.dev.yaml +++ b/docker-compose.dev.yaml @@ -22,8 +22,6 @@ services: depends_on: - mariadb environment: - - "APACHE_RUN_USER_ID=${UID}" - - "APACHE_RUN_GROUP_ID=${GID}" - "CP_DB_NAME=${CP_DB_NAME}" - "CP_DB_USER=${CP_DB_USER}" - "CP_DB_PASSWORD_FILE=/run/secrets/CP_DB_PASSWORD" diff --git a/docker-compose.example.yaml b/docker-compose.example.yaml index 6f5d0e1..5e0b5f8 100644 --- a/docker-compose.example.yaml +++ b/docker-compose.example.yaml @@ -27,8 +27,6 @@ services: depends_on: - mariadb environment: - - "APACHE_RUN_USER_ID=${UID}" - - "APACHE_RUN_GROUP_ID=${GID}" - "CP_DB_NAME=${MYBLOG}_db" - "CP_DB_USER=${MYBLOG}_user" - "CP_DB_PASSWORD_FILE=/run/secrets/MYBLOG_DB_PASSWORD"