Validate SSH when saving machines

This commit is contained in:
2026-05-07 20:59:23 +02:00
parent 0fa97633ce
commit 02f6617f71
5 changed files with 155 additions and 6 deletions
@@ -88,6 +88,42 @@ def _resolve_ssh_client(
return client, host, port
def _raise_ssh_validation_error(host: str, port: int, exc: Exception) -> None:
message = str(exc)
lowered = message.lower()
if "protocol banner" in lowered:
raise HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
detail=(
f"SSH banner not received from {host}:{port}; the backend could not complete the SSH handshake."
),
) from exc
if "no authentication methods available" in lowered or "authentication failed" in lowered:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=(
f"SSH authentication failed for {host}:{port}. "
"Check the selected SSH key, passphrase, username, or password."
),
) from exc
raise HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
detail=f"SSH validation failed for {host}:{port}: {message}",
) from exc
def _validate_saved_machine_ssh(machine: MonitoringMachineInput, store: SettingsStore) -> None:
if str(machine.mode or "").strip().lower() != "ssh":
return
client, host, port = _resolve_ssh_client(machine, store)
try:
client.connect()
except Exception as exc:
_raise_ssh_validation_error(host, port, exc)
finally:
client.close()
@router.post("/machines/test-ssh")
def test_machine_ssh(
machine: MonitoringMachineInput,
@@ -146,8 +182,11 @@ def post_machine(
) -> dict[str, Any]:
saved = store.upsert_machine(machine.model_dump(exclude_none=True), machine.id)
poller = get_monitoring_poller()
poller.start()
poller.kick()
try:
_validate_saved_machine_ssh(MonitoringMachineInput.model_validate(saved), store)
finally:
poller.start()
poller.kick()
return saved
@@ -161,8 +200,11 @@ def put_machine(
raise HTTPException(status_code=404, detail="Machine not found")
saved = store.upsert_machine(machine.model_dump(exclude_none=True), machine_id)
poller = get_monitoring_poller()
poller.start()
poller.kick()
try:
_validate_saved_machine_ssh(MonitoringMachineInput.model_validate(saved), store)
finally:
poller.start()
poller.kick()
return saved