Multiple entries in UserKnownHostsFile causes connection failure
Describe the bug
Although it's not as commonly used, OpenSSH supports the inclusion of multiple known-hosts key files in the UserKnownHostsFile config entry. For example:
UserKnownHostsFile ~/.ssh/known_hosts ~/.ssh/known_hosts.infra ~/.ssh/known_hosts.webservers
This entry, however, causes pyinfra to fail to connect to any host, as it appears to parse the whole line as the name of a single known-hosts file:
└─> ssh greyhound.domain.lan hostname
greyhound
└─> pyinfra greyhound.domain.lan exec -- echo "hello world"
--> Loading config...
--> Loading inventory...
--> Connecting to hosts...
Failed to load host keys from /Users/castillar/.ssh/known_hosts ~/.ssh/known_hosts.infra ~/.ssh/known_hosts.webservers: [Errno 2] No such file or directory: '/Users/castillar/.ssh/known_hosts ~/.ssh/known_hosts.infra ~/.ssh/known_hosts.webservers'
No host key for greyhound.domain.lan found in known_hosts, do you want to continue [y/n] y
[greyhound.domain.lan] Could not connect ([Errno 2] No such file or directory: '/Users/castillar/.ssh/known_hosts ~/.ssh/known_hosts.infra ~/.ssh/known_hosts.webservers')
--> pyinfra error: No hosts remaining!
It's not entirely clear from a quick perusal of the code whether this is coming from paramiko or if it's endemic to the pyinfra code.
To Reproduce
Steps to reproduce the behavior, please include where possible:
Environment Description
- Client: Mac OS X 14.4.1 with native SSH, python 3.11 installed via Homebrew, pyinfra installed from pip
- Server: Fedora Linux 39
Steps
- Create a
~/.ssh/configfile with multiple file entries in theUserKnownHostsFilekey (see example above) - Validate that connection with SSH works (e.g.,
ssh server.domain.lan hostnamereturns the remote hostname) - Attempt to make a connection to that host with pyinfra.
Expected behavior
Ideally, this connection should just work as long as the host key is in one of the host key files listed. If there's an underlying issue with trying to search all of the files (e.g., paramiko doesn't support doing that yet), pyinfra could opt to do something like "if UserKnownHostsFile has multiple entries, trim the list to the first file and pretend that's the only entry", which would also be reasonable.
Meta
pyinfra --support
└─> pyinfra --support
--> Support information:
If you are having issues with pyinfra or wish to make feature requests, please
check out the GitHub issues at https://github.com/Fizzadar/pyinfra/issues .
When adding an issue, be sure to include the following:
System: Darwin
Platform: macOS-14.4.1-arm64-arm-64bit
Release: 23.4.0
Machine: arm64
pyinfra: v2.9.2
Executable: /opt/homebrew/bin/pyinfra
Python: 3.11.9 (CPython, Clang 15.0.0 (clang-1500.3.9.4))
Installation
pyinfra was installed using pip.
Debugging
No pyinfra-debug.log file was created; the output when running with -vv doesn't differ from running without it.
pyinfra --debug ... produces this:
└─> pyinfra --debug greyhound.domain.lan exec -- echo "hello world"
--> Loading config...
--> Loading inventory...
[pyinfra_cli.inventory] Creating fake inventory...
[pyinfra_cli.inventory] Checking possible group_data directory: /Users/castillar
--> Connecting to hosts...
[pyinfra.connectors.ssh] Connecting to: greyhound.domain.lan ({'allow_agent': True, 'look_for_keys': True, 'hostname': 'greyhound.domain.lan', '_pyinfra_ssh_forward_agent': None, '_pyinfra_ssh_config_file': None, '_pyinfra_ssh_known_hosts_file': None, '_pyinfra_ssh_strict_host_key_checking': None, '_pyinfra_ssh_paramiko_connect_kwargs': None, 'timeout': 10})
[pyinfra.connectors.sshuserclient.client] Loading SSH config: None
Failed to load host keys from /Users/castillar/.ssh/known_hosts ~/.ssh/known_hosts.infra ~/.ssh/known_hosts.webserver: [Errno 2] No such file or directory: '/Users/castillar/.ssh/known_hosts ~/.ssh/known_hosts.infra ~/.ssh/known_hosts.webservers'
No host key for greyhound.domain.lan found in known_hosts, do you want to continue [y/n] y
[greyhound.domain.lan] Could not connect ([Errno 2] No such file or directory: '/Users/castillar/.ssh/known_hosts ~/.ssh/known_hosts.infra ~/.ssh/known_hosts.webservers')
[pyinfra.api.state] Failing hosts: greyhound.domain.lan