rusackas commented on code in PR #40673:
URL: https://github.com/apache/superset/pull/40673#discussion_r3342879132


##########
superset/extensions/ssh.py:
##########
@@ -48,6 +78,68 @@ def build_sqla_url(
             port=server.local_bind_port,
         )
 
+    def _verify_host_key(self, ssh_tunnel: "SSHTunnel") -> None:
+        """
+        Opt-in defense-in-depth: verify the SSH server's host key before 
opening the
+        tunnel, to resist man-in-the-middle attacks (paramiko's ``Transport`` 
does no
+        known-hosts checking by default).
+
+        Behavior:
+
+        - If the tunnel declares an expected ``server_host_key``, connect to 
the SSH
+          server, read the host key it presents, and compare. On mismatch (or 
if the
+          expected key cannot be parsed) raise
+          :class:`SSHTunnelHostKeyVerificationError`.
+        - If no expected key is set and 
``SSH_TUNNEL_STRICT_HOST_KEY_CHECKING`` is
+          enabled, fail closed and raise.
+        - If no expected key is set and strict checking is disabled, do 
nothing,
+          preserving existing (unverified) behavior.
+        """
+        expected_raw = ssh_tunnel.server_host_key
+
+        if not expected_raw or not expected_raw.strip():
+            if self.strict_host_key_checking:
+                raise SSHTunnelHostKeyVerificationError(
+                    message=(
+                        "SSH_TUNNEL_STRICT_HOST_KEY_CHECKING is enabled but no 
"
+                        "expected server host key is configured for this 
tunnel."
+                    )
+                )
+            return
+
+        try:
+            expected_key = _parse_authorized_key(expected_raw)
+        except ValueError as ex:
+            raise SSHTunnelHostKeyVerificationError(
+                message=f"The configured expected server host key is invalid: 
{ex}"
+            ) from ex
+
+        transport = paramiko.Transport(
+            (ssh_tunnel.server_address, ssh_tunnel.server_port)
+        )

Review Comment:
   Good catch — the bare paramiko.Transport((host, port)) constructor 
establishes the socket synchronously with no timeout, so 
start_client(timeout=...) only governs the SSH handshake after the TCP connect 
already succeeded. On an unreachable host this can block for the full OS-level 
TCP timeout. I will switch this to build the socket with 
socket.create_connection((host, port), timeout=SSH_TIMEOUT) and hand that 
socket to Transport so the connect phase is bounded too.



##########
superset/extensions/ssh.py:
##########
@@ -60,6 +152,10 @@ def create_tunnel(
         port = url.port or get_default_port(backend)
         if not port:
             raise SSHTunnelDatabasePortError()
+
+        # Opt-in host-key verification runs before the tunnel is opened.
+        self._verify_host_key(ssh_tunnel)

Review Comment:
   Partly valid. SSHTunnelHostKeyVerificationError is a CommandInvalidError, 
and when create_tunnel raises it during a connection attempt it is caught by 
the broad except in test_connection and re-raised through the normal DBAPIError 
path, so the HTTP status is still 400 rather than a 500 — it does not surface 
as an unhandled error. What is lost is the specific host-key message, which 
gets flattened into the generic connection-failure text. I will add explicit 
handling so the verification error keeps its own message in the 
create/update/test-connection paths rather than wiring it as a hard failure; 
thanks for flagging the message-fidelity gap.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to