Files
pvemanager/backend/tests/test_node_ssh_credentials.py
markmorado 636614955d
Some checks are pending
Backend Tests / pytest (Python 3.12) (push) Waiting to run
Deploy to Production / backend-tests (push) Waiting to run
Deploy to Production / frontend-build (push) Waiting to run
Deploy to Production / Trigger production update (push) Blocked by required conditions
SonarCloud / SonarCloud Analysis (push) Waiting to run
fix(ssh): use stored root password for node SSH with API-token auth
ProxmoxClient.from_server only passed the stored password to the client in
password-auth mode, so servers using an API token (including auto-provisioned
ones that keep the password for SSH fallbacks) connected to the node with no
credentials. Golden template builds, App Store installs and other root SSH
operations failed with "No authentication methods available".

- Keep the stored password on token-auth clients for SSH use only
- Fail fast on "No authentication methods available" with an actionable
  hint instead of retrying with backoff
- Add regression tests
2026-11-15 00:01:42 +05:00

62 lines
2.0 KiB
Python

"""Root SSH to a node must work for servers that use API-token auth.
Token auto-provisioning keeps the root password (encrypted) for SSH, but the
client built by ``from_server`` used to drop it, so App Store / golden-template
SSH failed with "No authentication methods available".
"""
from __future__ import annotations
from types import SimpleNamespace
from unittest.mock import patch
import paramiko
from app import ssh_client as sc
from app.proxmox.client import ProxmoxClient
def _server(**overrides):
base = dict(
ip_address="10.0.0.3", hostname="pve", port=8006, api_user="root@pam",
api_token_name="pvemanager", api_token_value="secret",
use_password=False, password="rootpw", verify_ssl=False,
)
base.update(overrides)
return SimpleNamespace(**base)
def _build(server):
with patch.object(ProxmoxClient, "__init__", lambda self, **kw: setattr(self, "_password", kw.get("password"))):
return ProxmoxClient.from_server(server)
def test_token_client_keeps_stored_password_for_ssh():
assert _build(_server())._password == "rootpw"
def test_token_client_without_password_has_none():
assert _build(_server(password=None))._password is None
def test_password_client_uses_password():
assert _build(_server(use_password=True))._password == "rootpw"
def test_no_auth_methods_fails_fast_with_hint(monkeypatch, tmp_path):
from app.config import settings
monkeypatch.setattr(settings, "SSH_KNOWN_HOSTS_FILE", str(tmp_path / "kh"))
sc.SSHClient._connections.clear()
calls = []
def fake_connect(self, **kwargs):
calls.append(kwargs)
raise paramiko.SSHException("No authentication methods available")
monkeypatch.setattr(paramiko.SSHClient, "connect", fake_connect)
monkeypatch.setattr(sc.time, "sleep", lambda s: None)
ssh = sc.SSHClient("10.0.0.3", username="root")
assert ssh.connect() is False
assert len(calls) == 1 # no pointless retries
assert "no credentials" in ssh.last_error