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"

Reply via email to