diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b5a083..136e80d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ### Minor changes * **2026-10-09** + * Fix the container start when the mounted directory belongs to an ID used by a system account of the image (e.g. 998 for a `gitlab-runner` user): the commands were run with this account (no home directory, no sudo) instead of the `docker` user + * Fix `DOCKER_USER` set to an ID used by a system account of the image (`usermod: UID already exists`) + * Fix `php` run by a user that cannot use sudo (e.g. `sudo -u www-data php ...`): sudo password errors, and failure when a `PHP_*` variable had changed + * Fix PHP warnings (e.g. an extension that cannot be loaded) breaking the container start: they were written in `generated_conf.ini`, in the crontab and in the startup commands * Fix the fpm variant stop: PHP-FPM is now stopped gracefully (`SIGQUIT`: running requests are completed) and port 9000 is exposed * Fix stop requests sent while the apache or fpm container is starting: the stop signal (`SIGWINCH` / `SIGQUIT`) was ignored by the entrypoint and the container was killed after the stop timeout * Fix Apache modules listed in the documentation but rejected by `APACHE_EXTENSION_*`: `brotli`, `cern_meta`, `imagemap`, `md`, `proxy_hcheck`, `proxy_uwsgi`, `socache_redis` diff --git a/README.md b/README.md index 684615e..0054d37 100644 --- a/README.md +++ b/README.md @@ -423,6 +423,10 @@ working directory (`/var/www/html` for Apache/PHP-FPM, or `/usr/src/app` for CLI you want to run commands as this user. So it will **dynamically change the ID of the docker user** to match the ID of the current working directory user. +If this ID is already used by a system account of the image (e.g. `998`, used by `systemd-network` in the image and +often by the `gitlab-runner` user on CI hosts), this system account is moved to another ID: the commands are still run +by the `docker` user. + Furthermore, the image is changing the Apache default user/group to be `docker/docker` (instead if `www-data/www-data`). So Apache will run with the same rights as the user on your host. diff --git a/tests-suite/assets/php-startup-warning.ini b/tests-suite/assets/php-startup-warning.ini new file mode 100644 index 0000000..d242910 --- /dev/null +++ b/tests-suite/assets/php-startup-warning.ini @@ -0,0 +1,2 @@ +; Loading an extension that does not exist makes PHP display a startup warning +extension=does_not_exist diff --git a/tests-suite/tool-startup-command.sh b/tests-suite/tool-startup-command.sh index bce45d7..ec6f615 100755 --- a/tests-suite/tool-startup-command.sh +++ b/tests-suite/tool-startup-command.sh @@ -25,3 +25,13 @@ test_withFile() { "${REPO}:${TAG_PREFIX}${PHP_VERSION}-${BRANCH}-slim-${BRANCH_VARIANT}${ARCH_SUFFIX}" php -m 2>/dev/null | grep -q "startup.sh executed" assert_equals "0" "$?" } +############################################################ +## Tests that a PHP startup warning does not prevent the container from starting +############################################################ +test_phpStartupWarning() { + RESULT="$(docker run ${RUN_OPTIONS} --rm -e STARTUP_COMMAND_1="echo startup-ok" \ + -v "${SCRIPT_DIR}/assets/php-startup-warning.ini":"/etc/php/${PHP_VERSION}/cli/conf.d/99-startup-warning.ini" \ + "${REPO}:${TAG_PREFIX}${PHP_VERSION}-${BRANCH}-slim-${BRANCH_VARIANT}${ARCH_SUFFIX}" sleep 1 2>/dev/null)" + assert_equals "0" "$?" "Docker run failed" + assert_equals "startup-ok" "$RESULT" +} diff --git a/tests-suite/users-rights.sh b/tests-suite/users-rights.sh index abdfc61..aa80845 100755 --- a/tests-suite/users-rights.sh +++ b/tests-suite/users-rights.sh @@ -36,7 +36,7 @@ test_defaultUserCanWriteOnStdoutAndStderr() { ############################################################ ## It's also works for users with existing IDs in the container ############################################################ -test_defaultUserCanWriteOnStdoutAndStderr() { +test_defaultUserCanBeAnExistingUser() { mkdir -p "${TMP_DIR}/user33" cat << EOF > "${TMP_DIR}/user33/composer.json" { @@ -58,6 +58,32 @@ EOF } +############################################################ +## The system accounts of the image (systemd-network, polkitd...) +## do not take the place of the default user when they have the +## ID of the mounted directory (e.g. 998 for gitlab-runner) +############################################################ +test_defaultUserTakesUidOfSystemAccount() { + mkdir -p "${TMP_DIR}/user998" + docker run ${RUN_OPTIONS} --rm -v /tmp:/tmp busybox chown 998:998 "${TMP_DIR}/user998" > /dev/null 2>&1 + RESULT="$(docker run ${RUN_OPTIONS} --rm -v "${TMP_DIR}/user998":"${CONTAINER_CWD}" -e STARTUP_COMMAND_1='touch ~/.startup_done' \ + "${REPO}:${TAG_PREFIX}${PHP_VERSION}-${BRANCH}-slim-${BRANCH_VARIANT}${ARCH_SUFFIX}" \ + bash -c 'echo "$(id -un):$(id -ur)"')" + assert_equals "0" "$?" "Docker run failed" + assert_equals "docker:998" "${RESULT}" "Default user mismatch with a mounted directory owned by a system account ID" +} + +############################################################ +## Users that cannot use sudo can run PHP, even when a PHP_* +## environment variable has changed +############################################################ +test_userWithoutSudoCanRunPhp() { + RESULT="$(docker run ${RUN_OPTIONS} --rm "${REPO}:${TAG_PREFIX}${PHP_VERSION}-${BRANCH}-slim-${BRANCH_VARIANT}${ARCH_SUFFIX}" \ + sudo -E -u www-data PHP_INI_MEMORY_LIMIT=1G php -r 'echo "OK";' 2>&1)" + assert_equals "0" "$?" "Docker run failed" + assert_equals "OK" "${RESULT}" "PHP run as www-data failed or printed errors" +} + setup_suite() { export TMP_DIR="$(mktemp -d)" if [[ $VARIANT == cli* ]]; then export CONTAINER_CWD=/usr/src/app; else export CONTAINER_CWD=/var/www/html; fi diff --git a/utils/README.blueprint.md b/utils/README.blueprint.md index bf7d61d..64c6971 100644 --- a/utils/README.blueprint.md +++ b/utils/README.blueprint.md @@ -353,6 +353,10 @@ working directory (`/var/www/html` for Apache/PHP-FPM, or `/usr/src/app` for CLI you want to run commands as this user. So it will **dynamically change the ID of the docker user** to match the ID of the current working directory user. +If this ID is already used by a system account of the image (e.g. `998`, used by `systemd-network` in the image and +often by the `gitlab-runner` user on CI hosts), this system account is moved to another ID: the commands are still run +by the `docker` user. + Furthermore, the image is changing the Apache default user/group to be `docker/docker` (instead if `www-data/www-data`). So Apache will run with the same rights as the user on your host. diff --git a/utils/docker-entrypoint-as-root.sh b/utils/docker-entrypoint-as-root.sh index d2e3b13..539ea9f 100755 --- a/utils/docker-entrypoint-as-root.sh +++ b/utils/docker-entrypoint-as-root.sh @@ -66,6 +66,24 @@ fi # DOCKER_USER is a user name if the user exists in the container, otherwise, it is a user ID (from a user on the host). +# A system account of the image that has the ID of DOCKER_USER is moved to a free ID: the docker user takes this ID +SYS_UID_MIN=$(awk '$1 == "SYS_UID_MIN" { print $2 }' /etc/login.defs) +SYS_UID_MAX=$(awk '$1 == "SYS_UID_MAX" { print $2 }' /etc/login.defs) +SYS_UID_MIN=${SYS_UID_MIN:-100} +SYS_UID_MAX=${SYS_UID_MAX:-999} +SYSTEM_ACCOUNT=$(getent passwd "$DOCKER_USER" | cut -d: -f1,3) +SYSTEM_ACCOUNT_NAME=${SYSTEM_ACCOUNT%%:*} +SYSTEM_ACCOUNT_ID=${SYSTEM_ACCOUNT##*:} +if [[ -n "$SYSTEM_ACCOUNT" ]] && [[ "$SYSTEM_ACCOUNT_NAME" != "docker" ]] && (( SYSTEM_ACCOUNT_ID >= SYS_UID_MIN && SYSTEM_ACCOUNT_ID <= SYS_UID_MAX )); then + FREE_ID=$SYS_UID_MAX + while getent passwd "$FREE_ID" > /dev/null; do + FREE_ID=$((FREE_ID - 1)) + done + sed -i "s/^${SYSTEM_ACCOUNT_NAME}:\([^:]*\):${SYSTEM_ACCOUNT_ID}:/${SYSTEM_ACCOUNT_NAME}:\1:${FREE_ID}:/" /etc/passwd + DOCKER_USER=$SYSTEM_ACCOUNT_ID +fi +unset SYS_UID_MIN SYS_UID_MAX SYSTEM_ACCOUNT SYSTEM_ACCOUNT_NAME SYSTEM_ACCOUNT_ID FREE_ID + # If DOCKER_USER is an ID, let's if [[ "$DOCKER_USER" =~ ^[0-9]+$ ]] ; then # MAIN_DIR_USER is a user ID. @@ -123,14 +141,14 @@ unset DOCKER_FOR_MAC_REMOTE_HOST unset REMOTE_HOST_FOUND sudo chown docker:docker /opt/php_env_var_cache.php -/usr/bin/real_php /usr/local/bin/check_php_env_var_changes.php &> /dev/null +/usr/bin/real_php -d display_errors=stderr /usr/local/bin/check_php_env_var_changes.php &> /dev/null -/usr/bin/real_php /usr/local/bin/generate_conf.php > /etc/php/${PHP_VERSION}/mods-available/generated_conf.ini -PHP_VERSION="${PHP_VERSION}" /usr/bin/real_php /usr/local/bin/setup_extensions.php | sudo bash +/usr/bin/real_php -d display_errors=stderr /usr/local/bin/generate_conf.php > /etc/php/${PHP_VERSION}/mods-available/generated_conf.ini +PHP_VERSION="${PHP_VERSION}" /usr/bin/real_php -d display_errors=stderr /usr/local/bin/setup_extensions.php | sudo bash # output on the logs can be done by writing on the "tini" PID. Useful for CRONTAB TINI_PID=`ps -e | grep tini | awk '{print $1;}'` -/usr/bin/real_php /usr/local/bin/generate_cron.php $TINI_PID > /tmp/generated_crontab +/usr/bin/real_php -d display_errors=stderr /usr/local/bin/generate_cron.php $TINI_PID > /tmp/generated_crontab chmod 0644 /tmp/generated_crontab # If generated_crontab is not empty, start supercronic @@ -139,13 +157,13 @@ if [[ -s /tmp/generated_crontab ]]; then fi if [[ "$IMAGE_VARIANT" == "apache" ]]; then - /usr/bin/real_php /usr/local/bin/enable_apache_mods.php | bash + /usr/bin/real_php -d display_errors=stderr /usr/local/bin/enable_apache_mods.php | bash fi if [ -e /etc/container/startup.sh ]; then sudo -E -u "#$DOCKER_USER_ID" /etc/container/startup.sh fi -sudo -E -u "#$DOCKER_USER_ID" sh -c "/usr/bin/real_php /usr/local/bin/startup_commands.php | bash" +sudo -E -u "#$DOCKER_USER_ID" sh -c "/usr/bin/real_php -d display_errors=stderr /usr/local/bin/startup_commands.php | bash" if [[ "$APACHE_DOCUMENT_ROOT" == /* ]]; then export ABSOLUTE_APACHE_DOCUMENT_ROOT="$APACHE_DOCUMENT_ROOT" diff --git a/utils/php_proxy.sh b/utils/php_proxy.sh index d3dc5a0..f1ba90c 100755 --- a/utils/php_proxy.sh +++ b/utils/php_proxy.sh @@ -1,8 +1,11 @@ #!/bin/bash -sudo chown docker:docker /opt/php_env_var_cache.php +# Users that cannot use sudo run PHP with the current configuration +if ! sudo -n chown docker:docker /opt/php_env_var_cache.php 2> /dev/null; then + exec /usr/bin/real_php "$@" +fi -REGENERATE=$(/usr/bin/real_php /usr/local/bin/check_php_env_var_changes.php) +REGENERATE=$(/usr/bin/real_php -d display_errors=stderr /usr/local/bin/check_php_env_var_changes.php) if [[ "$REGENERATE" != "0" ]] && [[ "$REGENERATE" != "1" ]]; then >&2 echo "Unexpected PHP proxy output:" @@ -11,8 +14,8 @@ if [[ "$REGENERATE" != "0" ]] && [[ "$REGENERATE" != "1" ]]; then fi if [[ "$REGENERATE" == "1" ]]; then - /usr/bin/real_php /usr/local/bin/generate_conf.php | sudo tee "/etc/php/${PHP_VERSION}/mods-available/generated_conf.ini" > /dev/null - PHP_VERSION="${PHP_VERSION}" /usr/bin/real_php /usr/local/bin/setup_extensions.php | sudo bash + /usr/bin/real_php -d display_errors=stderr /usr/local/bin/generate_conf.php | sudo tee "/etc/php/${PHP_VERSION}/mods-available/generated_conf.ini" > /dev/null + PHP_VERSION="${PHP_VERSION}" /usr/bin/real_php -d display_errors=stderr /usr/local/bin/setup_extensions.php | sudo bash fi exec /usr/bin/real_php "$@"