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 fb1187dbec Add disabled_algorithms as an extra parameter for SSH
connections (#24090)
fb1187dbec is described below
commit fb1187dbec19377d2a8b7dbc35813b2aaa56506f
Author: Paul Williams <[email protected]>
AuthorDate: Fri Jun 3 11:19:45 2022 -0400
Add disabled_algorithms as an extra parameter for SSH connections (#24090)
---
airflow/providers/ssh/hooks/ssh.py | 11 +++++++
.../connections/ssh.rst | 2 ++
tests/providers/ssh/hooks/test_ssh.py | 34 ++++++++++++++++++++++
3 files changed, 47 insertions(+)
diff --git a/airflow/providers/ssh/hooks/ssh.py
b/airflow/providers/ssh/hooks/ssh.py
index f3f7d11a57..53c74f44c5 100644
--- a/airflow/providers/ssh/hooks/ssh.py
+++ b/airflow/providers/ssh/hooks/ssh.py
@@ -68,6 +68,9 @@ class SSHHook(BaseHook):
:param keepalive_interval: send a keepalive packet to remote host every
keepalive_interval seconds
:param banner_timeout: timeout to wait for banner from the server in
seconds
+ :param disabled_algorithms: dictionary mapping algorithm type to an
+ iterable of algorithm identifiers, which will be disabled for the
+ lifetime of the transport
"""
# List of classes to try loading private keys as, ordered (roughly) by
most common to least common
@@ -112,6 +115,7 @@ class SSHHook(BaseHook):
conn_timeout: Optional[int] = None,
keepalive_interval: int = 30,
banner_timeout: float = 30.0,
+ disabled_algorithms: Optional[dict] = None,
) -> None:
super().__init__()
self.ssh_conn_id = ssh_conn_id
@@ -125,6 +129,7 @@ class SSHHook(BaseHook):
self.conn_timeout = conn_timeout
self.keepalive_interval = keepalive_interval
self.banner_timeout = banner_timeout
+ self.disabled_algorithms = disabled_algorithms
self.host_proxy_cmd = None
# Default values, overridable from Connection
@@ -197,6 +202,9 @@ class SSHHook(BaseHook):
):
self.look_for_keys = False
+ if "disabled_algorithms" in extra_options:
+ self.disabled_algorithms =
extra_options.get("disabled_algorithms")
+
if host_key is not None:
if host_key.startswith("ssh-"):
key_type, host_key = host_key.split(None)[:2]
@@ -313,6 +321,9 @@ class SSHHook(BaseHook):
if self.key_file:
connect_kwargs.update(key_filename=self.key_file)
+ if self.disabled_algorithms:
+ connect_kwargs.update(disabled_algorithms=self.disabled_algorithms)
+
log_before_sleep = lambda retry_state: self.log.info(
"Failed to connect. Sleeping before retry attempt %d",
retry_state.attempt_number
)
diff --git a/docs/apache-airflow-providers-ssh/connections/ssh.rst
b/docs/apache-airflow-providers-ssh/connections/ssh.rst
index 3cb0e0d898..b91c0854a1 100644
--- a/docs/apache-airflow-providers-ssh/connections/ssh.rst
+++ b/docs/apache-airflow-providers-ssh/connections/ssh.rst
@@ -54,6 +54,7 @@ Extra (optional)
* ``allow_host_key_change`` - Set to ``true`` if you want to allow
connecting to hosts that has host key changed or when you get 'REMOTE HOST
IDENTIFICATION HAS CHANGED' error. This won't protect against
Man-In-The-Middle attacks. Other possible solution is to remove the host entry
from ``~/.ssh/known_hosts`` file. Default is ``false``.
* ``look_for_keys`` - Set to ``false`` if you want to disable searching
for discoverable private key files in ``~/.ssh/``
* ``host_key`` - The base64 encoded ssh-rsa public key of the host or
"ssh-<key type> <key data>" (as you would find in the ``known_hosts`` file).
Specifying this allows making the connection if and only if the public key of
the endpoint matches this value.
+ * ``disabled_algorithms`` - A dictionary mapping algorithm type to an
iterable of algorithm identifiers, which will be disabled for the lifetime of
the transport.
Example "extras" field:
@@ -66,6 +67,7 @@ Extra (optional)
"look_for_keys": "false",
"allow_host_key_change": "false",
"host_key": "AAAHD...YDWwq=="
+ "disabled_algorithms": {"pubkeys": ["rsa-sha2-256", "rsa-sha2-512"]}
}
When specifying the connection as URI (in :envvar:`AIRFLOW_CONN_{CONN_ID}`
variable) you should specify it
diff --git a/tests/providers/ssh/hooks/test_ssh.py
b/tests/providers/ssh/hooks/test_ssh.py
index 7362ed918d..195823857e 100644
--- a/tests/providers/ssh/hooks/test_ssh.py
+++ b/tests/providers/ssh/hooks/test_ssh.py
@@ -76,6 +76,8 @@ TEST_CONN_TIMEOUT = 30
PASSPHRASE = ''.join(random.choice(string.ascii_letters) for i in range(10))
TEST_ENCRYPTED_PRIVATE_KEY = generate_key_string(pkey=TEST_PKEY,
passphrase=PASSPHRASE)
+TEST_DISABLED_ALGORITHMS = {"pubkeys": ["rsa-sha2-256", "rsa-sha2-512"]}
+
class TestSSHHook(unittest.TestCase):
CONN_SSH_WITH_NO_EXTRA = 'ssh_with_no_extra'
@@ -96,6 +98,7 @@ class TestSSHHook(unittest.TestCase):
CONN_SSH_WITH_HOST_KEY_AND_ALLOW_HOST_KEY_CHANGES_TRUE = (
'ssh_with_host_key_and_allow_host_key_changes_true'
)
+ CONN_SSH_WITH_EXTRA_DISABLED_ALGORITHMS =
'ssh_with_extra_disabled_algorithms'
@classmethod
def tearDownClass(cls) -> None:
@@ -115,6 +118,7 @@ class TestSSHHook(unittest.TestCase):
cls.CONN_SSH_WITH_HOST_KEY_AND_NO_HOST_KEY_CHECK_TRUE,
cls.CONN_SSH_WITH_NO_HOST_KEY_AND_NO_HOST_KEY_CHECK_FALSE,
cls.CONN_SSH_WITH_NO_HOST_KEY_AND_NO_HOST_KEY_CHECK_TRUE,
+ cls.CONN_SSH_WITH_EXTRA_DISABLED_ALGORITHMS,
]
connections =
session.query(Connection).filter(Connection.conn_id.in_(conns_to_reset))
connections.delete(synchronize_session=False)
@@ -263,6 +267,14 @@ class TestSSHHook(unittest.TestCase):
),
)
)
+ db.merge_conn(
+ Connection(
+ conn_id=cls.CONN_SSH_WITH_EXTRA_DISABLED_ALGORITHMS,
+ host='localhost',
+ conn_type='ssh',
+ extra=json.dumps({"disabled_algorithms":
TEST_DISABLED_ALGORITHMS}),
+ )
+ )
@mock.patch('airflow.providers.ssh.hooks.ssh.paramiko.SSHClient')
def test_ssh_connection_with_password(self, ssh_mock):
@@ -747,6 +759,28 @@ class TestSSHHook(unittest.TestCase):
look_for_keys=True,
)
+ @mock.patch('airflow.providers.ssh.hooks.ssh.paramiko.SSHClient')
+ def test_ssh_with_extra_disabled_algorithms(self, ssh_mock):
+ hook = SSHHook(
+ ssh_conn_id=self.CONN_SSH_WITH_EXTRA_DISABLED_ALGORITHMS,
+ remote_host='remote_host',
+ port='port',
+ username='username',
+ )
+
+ with hook.get_conn():
+ ssh_mock.return_value.connect.assert_called_once_with(
+ banner_timeout=30.0,
+ hostname='remote_host',
+ username='username',
+ compress=True,
+ timeout=10,
+ port='port',
+ sock=None,
+ look_for_keys=True,
+ disabled_algorithms=TEST_DISABLED_ALGORITHMS,
+ )
+
def test_openssh_private_key(self):
# Paramiko behaves differently with OpenSSH generated keys to paramiko
# generated keys, so we need a test one.