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 "${@}"