(feature) Add host key scanning for SSH credentials
Add a host detail action that scans the target SSH host key with ssh-keyscan and stores it on the selected SSH credential. Merge scanned known_hosts entries without duplicates and let the existing runtime config pass them through as UserKnownHostsFile for unattended rsync over SSH. Extend host checks to warn when the selected credential has no known_hosts entries, making host key verification failures actionable from Django.
This commit is contained in:
@@ -108,3 +108,38 @@ def delete_generated_key_files(credential: SshCredential) -> None:
|
||||
path.with_suffix(path.suffix + ".pub").unlink(missing_ok=True)
|
||||
if path.name == "identity":
|
||||
(path.parent / "identity.pub").unlink(missing_ok=True)
|
||||
|
||||
|
||||
def scan_known_host(address: str, *, port: int = 22, timeout: int = 5) -> str:
|
||||
if shutil.which("ssh-keyscan") is None:
|
||||
raise SshKeyError("ssh-keyscan is not available.")
|
||||
|
||||
command = ["ssh-keyscan", "-T", str(timeout), "-p", str(port), address]
|
||||
result = subprocess.run(
|
||||
command,
|
||||
check=False,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
timeout=timeout + 2,
|
||||
)
|
||||
if result.returncode != 0 and not result.stdout.strip():
|
||||
raise SshKeyError(result.stderr.strip() or f"Could not scan SSH host key for {address}.")
|
||||
|
||||
lines = [line.strip() for line in result.stdout.splitlines() if line.strip() and not line.startswith("#")]
|
||||
if not lines:
|
||||
raise SshKeyError(f"ssh-keyscan returned no host keys for {address}.")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def merge_known_hosts(existing: str, scanned: str) -> str:
|
||||
lines: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for line in [*existing.splitlines(), *scanned.splitlines()]:
|
||||
normalized = line.strip()
|
||||
if not normalized or normalized in seen:
|
||||
continue
|
||||
seen.add(normalized)
|
||||
lines.append(normalized)
|
||||
return "\n".join(lines) + ("\n" if lines else "")
|
||||
|
||||
Reference in New Issue
Block a user