This is an automated email from the ASF dual-hosted git repository. potiuk pushed a commit to branch hardened-python-base-images in repository https://gitbox.apache.org/repos/asf/airflow.git
commit a81cba26587b64629c6c71ab5f009f246ce52fb8 Author: Jarek Potiuk <[email protected]> AuthorDate: Sat Oct 10 14:04:03 2026 +0100 Keep a second hardened Python out of the trixie images On trixie, libenchant-2-dev's build dependencies need python3, which Docker's package repository satisfies with its own python-3.13 - a second interpreter in the 3.12 image that broke building C extensions. pyenchant only needs the runtime library, which it finds through the ldconfig cache, and the guard now fails early on any hardened Python other than the image's own. Generated-by: Claude Opus 5 Claude-Session: https://claude.ai/code/session_014np1fVAxHmGaBFt3qebJ8x --- Dockerfile | 19 +++++++++++++++++++ Dockerfile.ci | 26 ++++++++++++++++++++++---- scripts/docker/install_os_dependencies.sh | 19 +++++++++++++++++++ 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3360811687e..f3d576580da 100644 --- a/Dockerfile +++ b/Dockerfile @@ -131,6 +131,8 @@ if [[ "${AIRFLOW_IMAGE_FLAVOR}" == "hardened" ]]; then # The Debian 12 hardened images ship Python under /opt/python, the Debian 13 ones install it as # Debian packages under /usr - so ask the base image's Python where it lives. PYTHON_HOME=${PYTHON_HOME:-$(python3 -c 'import sys; print(sys.base_prefix)')} + # Read before apt runs: a package depending on python3 can pull another Python version in. + BASE_PYTHON_MAJOR_MINOR=$(python3 -c 'import sys; print(f"{sys.version_info[0]}.{sys.version_info[1]}")') elif [[ "${AIRFLOW_IMAGE_FLAVOR}" == "legacy" ]]; then PYTHON_HOME=${PYTHON_HOME:-/usr/python} else @@ -436,6 +438,23 @@ function check_no_system_python() { # /usr/python/lib/python3.11/lib-dynload/_ssl.cpython-311-aarch64-linux-gnu.so: undefined symbol: _PyModule_Add # Debian names its Python libraries "libpython3.13". The trixie hardened images register their own # Python with dpkg as "libpython-3.13", which is the Python we want, so it must not match. + # Debian 13 hardened images package their Python as "python-3.13" and serve other versions from + # their apt repository, so a package depending on python3 can also pull in a hardened Python of a + # different version than the image's own. + local other_hardened_python="" + if [[ -n "${BASE_PYTHON_MAJOR_MINOR=}" ]]; then + other_hardened_python=$(dpkg -l | awk '/^ii (lib)?python-3\.[0-9]+/ {print $2}' \ + | grep -v -E "python-${BASE_PYTHON_MAJOR_MINOR//./\\.}(-|:|$)" || true) + fi + if [[ -n "${other_hardened_python}" ]]; then + echo + echo "ERROR! A Python other than the image's ${BASE_PYTHON_MAJOR_MINOR} was installed: ${other_hardened_python//$'\n'/ }" + echo + apt-get install -yqq aptitude >/dev/null + aptitude why "$(echo "${other_hardened_python}" | head -1 | cut -d: -f1)" + echo + exit 1 + fi if dpkg -l | grep -E '^ii libpython3\.[0-9]+' >/dev/null; then echo echo "ERROR! System python is installed by one of the previous steps" diff --git a/Dockerfile.ci b/Dockerfile.ci index a5acf8ffc24..8681188656c 100644 --- a/Dockerfile.ci +++ b/Dockerfile.ci @@ -67,6 +67,8 @@ if [[ "${AIRFLOW_IMAGE_FLAVOR}" == "hardened" ]]; then # The Debian 12 hardened images ship Python under /opt/python, the Debian 13 ones install it as # Debian packages under /usr - so ask the base image's Python where it lives. PYTHON_HOME=${PYTHON_HOME:-$(python3 -c 'import sys; print(sys.base_prefix)')} + # Read before apt runs: a package depending on python3 can pull another Python version in. + BASE_PYTHON_MAJOR_MINOR=$(python3 -c 'import sys; print(f"{sys.version_info[0]}.{sys.version_info[1]}")') elif [[ "${AIRFLOW_IMAGE_FLAVOR}" == "legacy" ]]; then PYTHON_HOME=${PYTHON_HOME:-/usr/python} else @@ -372,6 +374,23 @@ function check_no_system_python() { # /usr/python/lib/python3.11/lib-dynload/_ssl.cpython-311-aarch64-linux-gnu.so: undefined symbol: _PyModule_Add # Debian names its Python libraries "libpython3.13". The trixie hardened images register their own # Python with dpkg as "libpython-3.13", which is the Python we want, so it must not match. + # Debian 13 hardened images package their Python as "python-3.13" and serve other versions from + # their apt repository, so a package depending on python3 can also pull in a hardened Python of a + # different version than the image's own. + local other_hardened_python="" + if [[ -n "${BASE_PYTHON_MAJOR_MINOR=}" ]]; then + other_hardened_python=$(dpkg -l | awk '/^ii (lib)?python-3\.[0-9]+/ {print $2}' \ + | grep -v -E "python-${BASE_PYTHON_MAJOR_MINOR//./\\.}(-|:|$)" || true) + fi + if [[ -n "${other_hardened_python}" ]]; then + echo + echo "ERROR! A Python other than the image's ${BASE_PYTHON_MAJOR_MINOR} was installed: ${other_hardened_python//$'\n'/ }" + echo + apt-get install -yqq aptitude >/dev/null + aptitude why "$(echo "${other_hardened_python}" | head -1 | cut -d: -f1)" + echo + exit 1 + fi if dpkg -l | grep -E '^ii libpython3\.[0-9]+' >/dev/null; then echo echo "ERROR! System python is installed by one of the previous steps" @@ -1874,11 +1893,10 @@ RUN echo "Base image version: ${BASE_IMAGE}" ARG DEV_APT_COMMAND="" ARG ADDITIONAL_DEV_APT_COMMAND="" ARG ADDITIONAL_DEV_ENV_VARS="" -# libenchant-2-dev is needed on top of libenchant-2-2: pyenchant locates the library via -# ctypes.util.find_library(), which without ldconfig in the hardened base falls back to -# linking with gcc and so needs the unversioned libenchant-2.so symlink the -dev package ships. +# No libenchant-2-dev: pyenchant finds libenchant-2-2 through the ldconfig cache, and on trixie the +# -dev package's build dependencies pull in a second Python from the hardened images' repository. ARG ADDITIONAL_DEV_APT_DEPS="bash-completion dumb-init git gdb graphviz krb5-user \ - less libenchant-2-2 libenchant-2-dev libgeos-dev libpq-dev net-tools \ + less libenchant-2-2 libgeos-dev libpq-dev net-tools \ netcat-openbsd openssh-server postgresql-client rsync tmux \ unzip vim xxd" diff --git a/scripts/docker/install_os_dependencies.sh b/scripts/docker/install_os_dependencies.sh index b1083387b6b..99c1c07b396 100644 --- a/scripts/docker/install_os_dependencies.sh +++ b/scripts/docker/install_os_dependencies.sh @@ -42,6 +42,8 @@ if [[ "${AIRFLOW_IMAGE_FLAVOR}" == "hardened" ]]; then # The Debian 12 hardened images ship Python under /opt/python, the Debian 13 ones install it as # Debian packages under /usr - so ask the base image's Python where it lives. PYTHON_HOME=${PYTHON_HOME:-$(python3 -c 'import sys; print(sys.base_prefix)')} + # Read before apt runs: a package depending on python3 can pull another Python version in. + BASE_PYTHON_MAJOR_MINOR=$(python3 -c 'import sys; print(f"{sys.version_info[0]}.{sys.version_info[1]}")') elif [[ "${AIRFLOW_IMAGE_FLAVOR}" == "legacy" ]]; then PYTHON_HOME=${PYTHON_HOME:-/usr/python} else @@ -347,6 +349,23 @@ function check_no_system_python() { # /usr/python/lib/python3.11/lib-dynload/_ssl.cpython-311-aarch64-linux-gnu.so: undefined symbol: _PyModule_Add # Debian names its Python libraries "libpython3.13". The trixie hardened images register their own # Python with dpkg as "libpython-3.13", which is the Python we want, so it must not match. + # Debian 13 hardened images package their Python as "python-3.13" and serve other versions from + # their apt repository, so a package depending on python3 can also pull in a hardened Python of a + # different version than the image's own. + local other_hardened_python="" + if [[ -n "${BASE_PYTHON_MAJOR_MINOR=}" ]]; then + other_hardened_python=$(dpkg -l | awk '/^ii (lib)?python-3\.[0-9]+/ {print $2}' \ + | grep -v -E "python-${BASE_PYTHON_MAJOR_MINOR//./\\.}(-|:|$)" || true) + fi + if [[ -n "${other_hardened_python}" ]]; then + echo + echo "ERROR! A Python other than the image's ${BASE_PYTHON_MAJOR_MINOR} was installed: ${other_hardened_python//$'\n'/ }" + echo + apt-get install -yqq aptitude >/dev/null + aptitude why "$(echo "${other_hardened_python}" | head -1 | cut -d: -f1)" + echo + exit 1 + fi if dpkg -l | grep -E '^ii libpython3\.[0-9]+' >/dev/null; then echo echo "ERROR! System python is installed by one of the previous steps"
