/ src / core / ssh / ssh_host_key_scanner.py
ssh_host_key_scanner.py
 1  """
 2  SSH host key scanner — admin-only utility for key pinning.
 3  
 4  Retrieves the server's public host key for a given host:port.
 5  NOT an MCP tool — only used by administrators to pin host keys
 6  in the vault before the first connection.
 7  """
 8  from __future__ import annotations
 9  
10  import logging
11  
12  logger = logging.getLogger(__name__)
13  
14  
15  async def scan_host_key(
16      host: str,
17      port: int = 22,
18      timeout: float = 10.0,
19  ) -> str:
20      """Scan a remote host and return its public key in OpenSSH format.
21  
22      Uses asyncssh.get_server_host_key() to retrieve the server's
23      host key without establishing a full connection.
24  
25      Args:
26          host: Remote hostname or IP address.
27          port: SSH port (default 22).
28          timeout: Connection timeout in seconds.
29  
30      Returns:
31          Public key string in OpenSSH format (e.g., "ssh-ed25519 AAAA...").
32  
33      Raises:
34          ConnectionError: On timeout or network failure.
35          ValueError: If no key is returned by the server.
36      """
37      import asyncio
38  
39      import asyncssh
40  
41      try:
42          key = await asyncio.wait_for(
43              asyncssh.get_server_host_key(host, port),
44              timeout=timeout,
45          )
46      except asyncio.TimeoutError:
47          raise ConnectionError(
48              f"Timeout scanning host key from {host}:{port} "
49              f"(timeout={timeout}s)"
50          )
51      except OSError as exc:
52          raise ConnectionError(
53              f"Failed to connect to {host}:{port} for host key scan: {exc}"
54          ) from exc
55  
56      if key is None:
57          raise ValueError(
58              f"No host key returned by {host}:{port}"
59          )
60  
61      public_key_str = key.export_public_key().decode("utf-8").strip()
62      logger.info(
63          "Scanned host key from %s:%d — type=%s",
64          host, port, key.get_algorithm(),
65      )
66      return public_key_str