This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 51ebe32acc1 Stop Breeze container SSH setup from modifying the host 
~/.ssh (#72182)
51ebe32acc1 is described below

commit 51ebe32acc11cf39b168566e1906c26fd063557b
Author: Ramit Kataria <[email protected]>
AuthorDate: Fri Aug 28 04:45:01 2026 -0700

    Stop Breeze container SSH setup from modifying the host ~/.ssh (#72182)
    
    When Breeze runs with --forward-credentials, the host's ~/.ssh is
    bind-mounted read-write at /root/.ssh and the container start used to
    operate on it directly. Every start overwrote the user's id_rsa with a
    throwaway key, appended that key to authorized_keys and three localhost
    entries to known_hosts (observed to accumulate hundreds of entries), left
    a dangling authorized_keys2 symlink, and ran 'chmod 600 ~/.ssh/*'. That
    chmod also strips the execute bit from the ~/.ssh/agent directory that
    OpenSSH 10.x uses for agent sockets. On macOS this kills the
    launchd-managed ssh-agent and, after the next reboot, makes every ssh on
    the host hang waiting on the dead agent socket.
    
    The ssh-to-localhost setup, which the real-connection SSH and SFTP
    provider tests rely on, now lives entirely in /root/.breeze-ssh, a path
    that is never mounted from the host. sshd accepts the generated key via
    an sshd_config.d drop-in, the OpenSSH client finds it via an ssh_config.d
    drop-in, and paramiko-based hooks, which read neither, discover it
    through an ssh-agent started by the entrypoint. entrypoint_exec.sh
    exports the agent socket so 'breeze exec' shells get it too. The
    authorized_keys2 symlink had no consumers anywhere in the repo and is
    dropped.
---
 Dockerfile.ci                                     | 42 ++++++++++++++++++-----
 scripts/ci/docker-compose/forward-credentials.yml |  2 ++
 scripts/docker/entrypoint_ci.sh                   | 38 +++++++++++++++-----
 scripts/docker/entrypoint_exec.sh                 |  6 ++++
 4 files changed, 70 insertions(+), 18 deletions(-)

diff --git a/Dockerfile.ci b/Dockerfile.ci
index af7ceb0c905..60a8eec64ba 100644
--- a/Dockerfile.ci
+++ b/Dockerfile.ci
@@ -1303,15 +1303,35 @@ function environment_initialization() {
     ln -s -f /usr/bin/gcloud /usr/lib/google-cloud-sdk/bin/gcloud
 
     if [[ ${SKIP_SSH_SETUP="false"} == "false" ]]; then
-        # Set up ssh keys
-        echo 'yes' | ssh-keygen -t rsa -C [email protected] -m PEM -P 
'' -f ~/.ssh/id_rsa \
-            >"${AIRFLOW_HOME}/logs/ssh-keygen.log" 2>&1
-
-        cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
-        ln -s -f ~/.ssh/authorized_keys ~/.ssh/authorized_keys2
-        chmod 600 ~/.ssh/*
+        # Set up the ssh-to-localhost plumbing used by the SSH/SFTP provider 
tests under a
+        # dedicated directory instead of ~/.ssh: `breeze 
--forward-credentials` bind-mounts
+        # the host's ~/.ssh at /root/.ssh and container startup must never 
modify it.
+        breeze_ssh_dir="/root/.breeze-ssh"
+        mkdir -p "${breeze_ssh_dir}"
+        chmod 700 "${breeze_ssh_dir}"
+        if [[ ! -f "${breeze_ssh_dir}/id_rsa" ]]; then
+            ssh-keygen -t rsa -C airflow-breeze-internal-key -m PEM -P '' -f 
"${breeze_ssh_dir}/id_rsa" \
+                >"${AIRFLOW_HOME}/logs/ssh-keygen.log" 2>&1
+        fi
+        cp "${breeze_ssh_dir}/id_rsa.pub" "${breeze_ssh_dir}/authorized_keys"
+        chmod 600 "${breeze_ssh_dir}/id_rsa" 
"${breeze_ssh_dir}/authorized_keys"
+        echo "AuthorizedKeysFile ${breeze_ssh_dir}/authorized_keys 
.ssh/authorized_keys" \
+            > /etc/ssh/sshd_config.d/airflow-breeze.conf
+        # The heredoc delimiter must not be "EOF": Dockerfile.ci inlines this 
script inside
+        # a COPY <<"EOF" heredoc and a bare EOF line would terminate it early.
+        cat > /etc/ssh/ssh_config.d/airflow-breeze.conf <<SSH_CONFIG
+Host localhost 127.0.0.1 ::1
+    IdentityFile ${breeze_ssh_dir}/id_rsa
+    UserKnownHostsFile ${breeze_ssh_dir}/known_hosts
+    StrictHostKeyChecking accept-new
+SSH_CONFIG
+        # Paramiko-based hooks do not read /etc/ssh/ssh_config.d but try 
ssh-agent keys by
+        # default, so expose the key through an agent. The fixed socket path 
lets shells
+        # entered via entrypoint_exec.sh (breeze exec) pick up the same agent.
+        rm -f "${breeze_ssh_dir}/agent.sock"
+        eval "$(ssh-agent -s -a "${breeze_ssh_dir}/agent.sock")" >/dev/null
+        ssh-add "${breeze_ssh_dir}/id_rsa" 
>>"${AIRFLOW_HOME}/logs/ssh-keygen.log" 2>&1
 
-        # SSH Service
         sudo service ssh restart >/dev/null 2>&1
 
         # Sometimes the server is not quick enough to load the keys!
@@ -1320,7 +1340,7 @@ function environment_initialization() {
             sleep 0.05
         done
 
-        ssh-keyscan -H localhost >> ~/.ssh/known_hosts 2>/dev/null
+        ssh-keyscan -H localhost > "${breeze_ssh_dir}/known_hosts" 2>/dev/null
     fi
 
     if [[ ${INTEGRATION_LOCALSTACK:-"false"} == "true" ]]; then
@@ -1700,6 +1720,10 @@ COPY <<"EOF" /entrypoint_exec.sh
 
 . /opt/airflow/scripts/in_container/run_init_script.sh
 
+if [[ -S /root/.breeze-ssh/agent.sock ]]; then
+    export SSH_AUTH_SOCK="/root/.breeze-ssh/agent.sock"
+fi
+
 exec /bin/bash "${@}"
 EOF
 
diff --git a/scripts/ci/docker-compose/forward-credentials.yml 
b/scripts/ci/docker-compose/forward-credentials.yml
index fcc4e4e67d1..f83c6a45db3 100644
--- a/scripts/ci/docker-compose/forward-credentials.yml
+++ b/scripts/ci/docker-compose/forward-credentials.yml
@@ -21,6 +21,8 @@ services:
     # Useful for gcloud/aws/kubernetes etc. authorisation to be passed
     # To inside docker. Use with care - your credentials will be available to
     # everything you install in Docker.
+    # Container startup must never write into these directories - they are the
+    # user's real dotfiles (see the ssh setup in entrypoint_ci.sh).
     environment:
       - GITHUB_TOKEN
     volumes:
diff --git a/scripts/docker/entrypoint_ci.sh b/scripts/docker/entrypoint_ci.sh
index 2a35fb4f9bc..928d05f4ad2 100755
--- a/scripts/docker/entrypoint_ci.sh
+++ b/scripts/docker/entrypoint_ci.sh
@@ -169,15 +169,35 @@ function environment_initialization() {
     ln -s -f /usr/bin/gcloud /usr/lib/google-cloud-sdk/bin/gcloud
 
     if [[ ${SKIP_SSH_SETUP="false"} == "false" ]]; then
-        # Set up ssh keys
-        echo 'yes' | ssh-keygen -t rsa -C [email protected] -m PEM -P 
'' -f ~/.ssh/id_rsa \
-            >"${AIRFLOW_HOME}/logs/ssh-keygen.log" 2>&1
-
-        cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
-        ln -s -f ~/.ssh/authorized_keys ~/.ssh/authorized_keys2
-        chmod 600 ~/.ssh/*
+        # Set up the ssh-to-localhost plumbing used by the SSH/SFTP provider 
tests under a
+        # dedicated directory instead of ~/.ssh: `breeze 
--forward-credentials` bind-mounts
+        # the host's ~/.ssh at /root/.ssh and container startup must never 
modify it.
+        breeze_ssh_dir="/root/.breeze-ssh"
+        mkdir -p "${breeze_ssh_dir}"
+        chmod 700 "${breeze_ssh_dir}"
+        if [[ ! -f "${breeze_ssh_dir}/id_rsa" ]]; then
+            ssh-keygen -t rsa -C airflow-breeze-internal-key -m PEM -P '' -f 
"${breeze_ssh_dir}/id_rsa" \
+                >"${AIRFLOW_HOME}/logs/ssh-keygen.log" 2>&1
+        fi
+        cp "${breeze_ssh_dir}/id_rsa.pub" "${breeze_ssh_dir}/authorized_keys"
+        chmod 600 "${breeze_ssh_dir}/id_rsa" 
"${breeze_ssh_dir}/authorized_keys"
+        echo "AuthorizedKeysFile ${breeze_ssh_dir}/authorized_keys 
.ssh/authorized_keys" \
+            > /etc/ssh/sshd_config.d/airflow-breeze.conf
+        # The heredoc delimiter must not be "EOF": Dockerfile.ci inlines this 
script inside
+        # a COPY <<"EOF" heredoc and a bare EOF line would terminate it early.
+        cat > /etc/ssh/ssh_config.d/airflow-breeze.conf <<SSH_CONFIG
+Host localhost 127.0.0.1 ::1
+    IdentityFile ${breeze_ssh_dir}/id_rsa
+    UserKnownHostsFile ${breeze_ssh_dir}/known_hosts
+    StrictHostKeyChecking accept-new
+SSH_CONFIG
+        # Paramiko-based hooks do not read /etc/ssh/ssh_config.d but try 
ssh-agent keys by
+        # default, so expose the key through an agent. The fixed socket path 
lets shells
+        # entered via entrypoint_exec.sh (breeze exec) pick up the same agent.
+        rm -f "${breeze_ssh_dir}/agent.sock"
+        eval "$(ssh-agent -s -a "${breeze_ssh_dir}/agent.sock")" >/dev/null
+        ssh-add "${breeze_ssh_dir}/id_rsa" 
>>"${AIRFLOW_HOME}/logs/ssh-keygen.log" 2>&1
 
-        # SSH Service
         sudo service ssh restart >/dev/null 2>&1
 
         # Sometimes the server is not quick enough to load the keys!
@@ -186,7 +206,7 @@ function environment_initialization() {
             sleep 0.05
         done
 
-        ssh-keyscan -H localhost >> ~/.ssh/known_hosts 2>/dev/null
+        ssh-keyscan -H localhost > "${breeze_ssh_dir}/known_hosts" 2>/dev/null
     fi
 
     if [[ ${INTEGRATION_LOCALSTACK:-"false"} == "true" ]]; then
diff --git a/scripts/docker/entrypoint_exec.sh 
b/scripts/docker/entrypoint_exec.sh
index 431a91d3f99..101bcf798c4 100755
--- a/scripts/docker/entrypoint_exec.sh
+++ b/scripts/docker/entrypoint_exec.sh
@@ -24,4 +24,10 @@
 # shellcheck source=scripts/in_container/run_init_script.sh
 . /opt/airflow/scripts/in_container/run_init_script.sh
 
+# Reuse the ssh-agent started by entrypoint_ci.sh so that ssh-ing to localhost 
also
+# works in shells entered with `breeze exec`.
+if [[ -S /root/.breeze-ssh/agent.sock ]]; then
+    export SSH_AUTH_SOCK="/root/.breeze-ssh/agent.sock"
+fi
+
 exec /bin/bash "${@}"

Reply via email to