wordpress-charmers team mailing list archive
-
wordpress-charmers team
-
Mailing list archive
-
Message #00801
[Merge] ~tcuthbert/charm-k8s-wordpress:container-hardening into charm-k8s-wordpress:container-hardening
Thomas Cuthbert has proposed merging ~tcuthbert/charm-k8s-wordpress:container-hardening into charm-k8s-wordpress:container-hardening.
Commit message:
fix: first pass at hardening the container
- run as unprivileged `wordpress` user
- lock down file permissions for app code
- ensure failed run commands cause a failed build
- configure php parameters at build time rather than on container
start
- configure db parameters in one call using envsubstr
- dynamic plugin loading at runtime is dangerous, plugin updates to
come from freshened container image builds or separate volumes
- php parameters are static so they should be set at build time
- Be more explicit with exempting sensitive URIs from being
cached
Requested reviews:
Wordpress Charmers (wordpress-charmers)
For more details, see:
https://code.launchpad.net/~tcuthbert/charm-k8s-wordpress/+git/charm-k8s-wordpress-1/+merge/414162
--
Your team Wordpress Charmers is requested to review the proposed merge of ~tcuthbert/charm-k8s-wordpress:container-hardening into charm-k8s-wordpress:container-hardening.
diff --git a/Dockerfile b/Dockerfile
index a338c56..9ad0b6b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,11 @@
ARG DIST_RELEASE
FROM ubuntu:${DIST_RELEASE} as base
+ARG USERNAME=wordpress
+ARG USER_UID=1000
+ARG USER_GID=$USER_UID
+
+RUN groupadd --gid $USER_GID $USERNAME \
+ && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME
# HTTPS_PROXY used when we RUN curl to download Wordpress itself
ARG BUILD_DATE
@@ -12,18 +18,23 @@ ARG HTTPS_PROXY
ENV APACHE_CONFDIR=/etc/apache2
ENV APACHE_ENVVARS=/etc/apache2/envvars
+ENV PYTHONUNBUFFERED="1"
# Avoid interactive prompts
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
# Update all packages, remove cruft, install required packages, configure apache
-RUN apt-get update && apt-get -y dist-upgrade \
- && apt-get --purge autoremove -y \
- && apt-get install -y apache2 \
+RUN set -eux; \
+ apt-get update; apt-get -y dist-upgrade; \
+ apt-get --purge autoremove -y; \
+ apt-get install -y \
+ apache2 \
bzr \
curl \
+ gettext-base \
git \
libapache2-mod-php \
+ libcap2-bin \
libgmp-dev \
php \
php-curl \
@@ -35,66 +46,63 @@ RUN apt-get update && apt-get -y dist-upgrade \
php-xml \
pwgen \
python3 \
+ python3-urllib3 \
python3-yaml \
ssl-cert \
- wget \
- && sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS" \
- && . "$APACHE_ENVVARS" \
- && for dir in "$APACHE_LOCK_DIR" "$APACHE_RUN_DIR" "$APACHE_LOG_DIR"; do rm -rvf "$dir"; mkdir -p "$dir"; chown "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$dir"; chmod 777 "$dir"; done \
- && ln -sfT /dev/stderr "$APACHE_LOG_DIR/error.log" \
- && ln -sfT /dev/stdout "$APACHE_LOG_DIR/access.log" \
- && ln -sfT /dev/stdout "$APACHE_LOG_DIR/other_vhosts_access.log" \
- && chown -R --no-dereference "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$APACHE_LOG_DIR"
+ wget; \
+ sed -ri 's/^export ([^=]+)=(.*)$/: ${\1:=\2}\nexport \1/' "$APACHE_ENVVARS"; \
+ . "$APACHE_ENVVARS"; \
+ for dir in "$APACHE_LOCK_DIR" "$APACHE_RUN_DIR" "$APACHE_LOG_DIR"; do rm -rvf "$dir"; mkdir -p "$dir"; chown "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$dir"; chmod 777 "$dir"; done; \
+ ln -sfT /dev/stderr "$APACHE_LOG_DIR/error.log"; \
+ ln -sfT /dev/stdout "$APACHE_LOG_DIR/access.log"; \
+ ln -sfT /dev/stdout "$APACHE_LOG_DIR/other_vhosts_access.log"; \
+ chown -R --no-dereference "$APACHE_RUN_USER:$APACHE_RUN_GROUP" "$APACHE_LOG_DIR"
# Configure PHP and apache2 - mod_php requires us to use mpm_prefork
-COPY ./image-builder/files/docker-php.conf $APACHE_CONFDIR/conf-available/docker-php.conf
-COPY ./image-builder/files/docker-php-swift-proxy.conf $APACHE_CONFDIR/conf-available/docker-php-swift-proxy.conf
-RUN a2enconf docker-php \
- && a2dismod mpm_event \
- && a2enmod headers \
- && a2enmod mpm_prefork \
- && a2enmod proxy \
- && a2enmod proxy_http \
- && a2enmod rewrite \
- && a2enmod ssl
+ADD ./image-builder/files /files
+RUN set -eux; \
+ cp /files/docker-php.conf $APACHE_CONFDIR/conf-available/docker-php.conf; \
+ cp /files/docker-php-swift-proxy.conf $APACHE_CONFDIR/conf-available/docker-php-swift-proxy.conf; \
+ a2enconf docker-php; \
+ a2dismod mpm_event; \
+ a2enmod headers; \
+ a2enmod mpm_prefork; \
+ a2enmod proxy; \
+ a2enmod proxy_http; \
+ a2enmod rewrite; \
+ a2enmod ssl
FROM base as plugins
# Download themes and plugins. This will eventually be separated into new container.
-COPY ./image-builder/src/fetcher.py /
+ADD ./image-builder/src/fetcher.py /fetcher.py
+RUN bash -c 'set -eux; mkdir -p /var/www/html/wp-content/{themes,plugins}'
WORKDIR /var/www/html/wp-content/
-RUN mkdir themes plugins && /fetcher.py
+RUN set -xeu; \
+ /fetcher.py && chown nobody:nogroup -R /var/www/html
VOLUME /var/www/html/wp-content
FROM base As install
ARG VERSION
-
+ARG WORDPRESS_URL=https://wordpress.org/wordpress-${VERSION}.tar.gz
# TODO: replace downloading the source wordpress code with copying it from the upstream wordpress container,
# which should speed builds up:
# COPY --from=wordpress-${VERSION}:fpm /usr/src/wordpress /usr/src/wordpress
# Install the main Wordpress code, this will be our only site so /var/www/html is fine
-RUN wget -O wordpress.tar.gz -t 3 -r "https://wordpress.org/wordpress-${VERSION}.tar.gz" \
- && tar -xzf wordpress.tar.gz -C /usr/src/ \
- && rm wordpress.tar.gz \
- && chown -R www-data:www-data /usr/src/wordpress \
- && rm -rf /var/www/html \
- && mv /usr/src/wordpress /var/www/html
-
-COPY ./image-builder/files/ /files/
-# wp-info.php contains template variables which our ENTRYPOINT script will populate
-RUN install -D /files/wp-info.php /var/www/html/wp-info.php
-RUN install -D /files/wp-config.php /var/www/html/wp-config.php
-RUN chown -R www-data:www-data /var/www/html
-
-# Copy our helper scripts and their wrapper into their own directory
-RUN install /files/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
-
-RUN install -t /srv/wordpress-helpers/ -D /files/_add_option.php \
- /files/_enable_plugin.php \
- /files/_get_option.php \
- /files/plugin_handler.py \
- /files/ready.sh
+RUN set -xeu; \
+ wget -O wordpress.tar.gz -t 3 -r "https://wordpress.org/wordpress-${VERSION}.tar.gz"; \
+ tar -xzf /wordpress.tar.gz --owner=nobody --group=wordpress --strip-components=1 -C /var/www/html; \
+ rm /wordpress.tar.gz
+
+RUN set -eux; \
+ install --owner wordpress --group wordpress -d /srv/wordpress-helpers; \
+ install --owner wordpress --group wordpress /files/wp-config.php /var/www/html/wp-config.php; \
+ install --owner wordpress --group wordpress -t /srv/wordpress-helpers/ -D /files/_add_option.php \
+ /files/_enable_plugin.php \
+ /files/_get_option.php \
+ /files/plugin_handler.py \
+ /files/ready.sh
# Make the wrapper executable
RUN chmod 0755 /srv/wordpress-helpers/plugin_handler.py
@@ -108,12 +116,32 @@ LABEL maintainer="wordpress-charmers@xxxxxxxxxxxxxxxxxxx"
# Used by Launchpad OCI Recipe to tag version
LABEL org.label-schema.version=${VERSION:-5.8.3}
+# Copy plugins from the plugin stage into the WordPress content directory.
+COPY --chown=nobody:wordpress --from=plugins /var/www/html/wp-content/plugins/ /var/www/html/wp-content/plugins/
+COPY --chown=nobody:wordpress --from=plugins /var/www/html/wp-content/themes/ /var/www/html/wp-content/themes/
+
+# Copy our helper scripts and their wrapper into their own directory
+ADD --chown=nobody:wordpress ./image-builder/files/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
+ADD --chown=nobody:wordpress ./image-builder/files/wp-info.php /var/www/html/wp-info.php
+RUN set -xeu; \
+ chmod 0550 /usr/local/bin/docker-entrypoint.sh; \
+ sed -i 's/max_execution_time = 30/max_execution_time = 300/' /etc/php/7.[24]/apache2/php.ini; \
+ sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 10M/' /etc/php/7.[24]/apache2/php.ini
+
+# Ensure remote users are unable to upload potentially malicious content into the container
+RUN set -xeu; \
+ find /var/www/ \
+ -exec chown nobody:wordpress {} \+ \
+ -exec chmod a-w,o-xr {} \+ \
+ -type d -exec chmod g+rx {} \; \
+ -or -type f -name '*.php' -exec chmod g+rx {} \+; \
+ chmod g+w /var/www/html/wp-includes/functions.php /var/www/html/wp-info.php; \
+ touch /var/log/wordpress-plugin-handler.log; \
+ chown wordpress:nogroup /var/log/wordpress-plugin-handler.log; \
+ setcap 'cap_net_bind_service=+ep' /usr/sbin/apache2
+
# Port 80 only, TLS will terminate elsewhere
EXPOSE 80
-
-# Copy plugins from the plugin stage into the WordPress content directory.
-COPY ./image-builder/src/fetcher.py /
-COPY --chown=www-data:www-data --from=plugins /var/www/html/wp-content/plugins/ /var/www/html/wp-content/plugins/
-COPY --chown=www-data:www-data --from=plugins /var/www/html/wp-content/themes/ /var/www/html/wp-content/themes/
+USER wordpress:wordpress
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
-CMD apachectl -D FOREGROUND
+CMD /usr/sbin/apache2ctl -D FOREGROUND
diff --git a/image-builder/files/docker-entrypoint.sh b/image-builder/files/docker-entrypoint.sh
index 8b9630f..3de9bc0 100644
--- a/image-builder/files/docker-entrypoint.sh
+++ b/image-builder/files/docker-entrypoint.sh
@@ -3,32 +3,11 @@ set -eu
printf "remove_filter('template_redirect', 'redirect_canonical');" >> /var/www/html/wp-includes/functions.php
-sed -i -e "s/%%%WORDPRESS_DB_HOST%%%/$WORDPRESS_DB_HOST/" /var/www/html/wp-info.php
-sed -i -e "s/%%%WORDPRESS_DB_NAME%%%/$WORDPRESS_DB_NAME/" /var/www/html/wp-info.php
-sed -i -e "s/%%%WORDPRESS_DB_USER%%%/$WORDPRESS_DB_USER/" /var/www/html/wp-info.php
-sed -i -e "s/%%%WORDPRESS_DB_PASSWORD%%%/$WORDPRESS_DB_PASSWORD/" /var/www/html/wp-info.php
-
-for key in AUTH_KEY SECURE_AUTH_KEY LOGGED_IN_KEY NONCE_KEY AUTH_SALT SECURE_AUTH_SALT LOGGED_IN_SALT NONCE_SALT;
-do
- sed -i -e "s/%%%${key}%%%/$(printenv ${key})/" /var/www/html/wp-info.php
-done
+envsubst "$(printf "'$%s'" $(printenv|grep -P 'WORD|_KEY|_AUTH'))" < /files/wp-info.php > /var/www/html/wp-info.php
# If we have passed in SWIFT_URL, then append swift proxy config.
[ -z "${SWIFT_URL-}" ] || a2enconf docker-php-swift-proxy
-# TODO: this will eventually be called directly by the charm.
-(
- cd /tmp
- /fetcher.py
- find /tmp -type f \( -path '/tmp/plugins/*' -o -path '/tmp/themes/*' \) -printf "%p\0/var/www/html/wp-content/%P\0" |
- xargs -rn2 -0 install -DT && rm -fr /tmp/* &&
- rm -fr /tmp/*
-)
-
nohup bash -c "/srv/wordpress-helpers/plugin_handler.py &"
-# Match against either php 7.2 (bionic) or 7.4 (focal).
-sed -i 's/max_execution_time = 30/max_execution_time = 300/' /etc/php/7.[24]/apache2/php.ini
-sed -i 's/upload_max_filesize = 2M/upload_max_filesize = 10M/' /etc/php/7.[24]/apache2/php.ini
-
exec "$@"
diff --git a/image-builder/files/docker-php.conf b/image-builder/files/docker-php.conf
index 00d1f36..eda1448 100644
--- a/image-builder/files/docker-php.conf
+++ b/image-builder/files/docker-php.conf
@@ -2,9 +2,11 @@
SetHandler application/x-httpd-php
</FilesMatch>
-<Location "/wp-admin">
- Header Set Cache-Control "max-age=0, no-store"
-</Location>
+<LocationMatch "^/?(wp-admin|wp-login)">
+ Header Unset ETag
+ Header Set Pragma "no-cache"
+ Header Set Cache-Control "max-age=0, no-store, no-cache, must-revalidate"
+</LocationMatch>
DirectoryIndex disabled
DirectoryIndex index.php index.html
diff --git a/image-builder/files/wp-info.php b/image-builder/files/wp-info.php
index 5a891a2..339f5dc 100644
--- a/image-builder/files/wp-info.php
+++ b/image-builder/files/wp-info.php
@@ -33,21 +33,21 @@ else {
define('WP_HOME', 'http://' . $_SERVER['HTTP_HOST']);
}
-define('DB_NAME', '%%%WORDPRESS_DB_NAME%%%');
-define('DB_USER', '%%%WORDPRESS_DB_USER%%%');
-define('DB_HOST', '%%%WORDPRESS_DB_HOST%%%');
+define('DB_NAME', '$WORDPRESS_DB_NAME');
+define('DB_USER', '$WORDPRESS_DB_USER');
+define('DB_HOST', '$WORDPRESS_DB_HOST');
-define('DB_PASSWORD', '%%%WORDPRESS_DB_PASSWORD%%%');
+define('DB_PASSWORD', '$WORDPRESS_DB_PASSWORD');
define('WP_CACHE', true);
-define('AUTH_KEY', '%%%AUTH_KEY%%%');
-define('SECURE_AUTH_KEY', '%%%SECURE_AUTH_KEY%%%');
-define('LOGGED_IN_KEY', '%%%LOGGED_IN_KEY%%%');
-define('NONCE_KEY', '%%%NONCE_KEY%%%');
-define('AUTH_SALT', '%%%AUTH_SALT%%%');
-define('SECURE_AUTH_SALT', '%%%SECURE_AUTH_SALT%%%');
-define('LOGGED_IN_SALT', '%%%LOGGED_IN_SALT%%%');
-define('NONCE_SALT', '%%%NONCE_SALT%%%');
+define('AUTH_KEY', '$AUTH_KEY');
+define('SECURE_AUTH_KEY', '$SECURE_AUTH_KEY');
+define('LOGGED_IN_KEY', '$LOGGED_IN_KEY');
+define('NONCE_KEY', '$NONCE_KEY');
+define('AUTH_SALT', '$AUTH_SALT');
+define('SECURE_AUTH_SALT', '$SECURE_AUTH_SALT');
+define('LOGGED_IN_SALT', '$LOGGED_IN_SALT');
+define('NONCE_SALT', '$NONCE_SALT');
$table_prefix = 'wp_';
Follow ups
-
[Merge] ~tcuthbert/charm-k8s-wordpress:container-hardening into charm-k8s-wordpress:container-hardening
From: mp+414162, 2022-06-29
-
[Merge] ~tcuthbert/charm-k8s-wordpress:container-hardening into charm-k8s-wordpress:container-hardening
From: Thomas Cuthbert, 2022-06-22
-
Re: [Merge] ~tcuthbert/charm-k8s-wordpress:container-hardening into charm-k8s-wordpress:container-hardening
From: Tom Haddon, 2022-06-22
-
Re: [Merge] ~tcuthbert/charm-k8s-wordpress:container-hardening into charm-k8s-wordpress:container-hardening
From: 🤖 prod-jenkaas-is, 2022-06-22
-
Re: [Merge] ~tcuthbert/charm-k8s-wordpress:container-hardening into charm-k8s-wordpress:container-hardening
From: 🤖 prod-jenkaas-is, 2022-06-22
-
Re: [Merge] ~tcuthbert/charm-k8s-wordpress:container-hardening into charm-k8s-wordpress:container-hardening
From: 🤖 prod-jenkaas-is, 2022-06-01
-
Re: [Merge] ~tcuthbert/charm-k8s-wordpress:container-hardening into charm-k8s-wordpress:container-hardening
From: 🤖 prod-jenkaas-is, 2022-06-01
-
Re: [Merge] ~tcuthbert/charm-k8s-wordpress:container-hardening into charm-k8s-wordpress:container-hardening
From: Tom Haddon, 2022-05-26
-
Re: [Merge] ~tcuthbert/charm-k8s-wordpress:container-hardening into charm-k8s-wordpress:container-hardening
From: 🤖 prod-jenkaas-is, 2022-05-26
-
Re: [Merge] ~tcuthbert/charm-k8s-wordpress:container-hardening into charm-k8s-wordpress:container-hardening
From: 🤖 prod-jenkaas-is, 2022-05-26
-
Re: [Merge] ~tcuthbert/charm-k8s-wordpress:container-hardening into charm-k8s-wordpress:container-hardening
From: Seth Arnold, 2022-05-26
-
Re: [Merge] ~tcuthbert/charm-k8s-wordpress:container-hardening into charm-k8s-wordpress:container-hardening
From: 🤖 prod-jenkaas-is, 2022-01-27
-
Re: [Merge] ~tcuthbert/charm-k8s-wordpress:container-hardening into charm-k8s-wordpress:container-hardening
From: 🤖 prod-jenkaas-is, 2022-01-27
-
Re: [Merge] ~tcuthbert/charm-k8s-wordpress:container-hardening into charm-k8s-wordpress:container-hardening
From: Thomas Cuthbert, 2022-01-17
-
Re: [Merge] ~tcuthbert/charm-k8s-wordpress:container-hardening into charm-k8s-wordpress:container-hardening
From: Joel Sing, 2022-01-17