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