diff --git a/dvc/fs/ssh.py b/dvc/fs/ssh.py index 491c06ad84..23f15f8880 100644 --- a/dvc/fs/ssh.py +++ b/dvc/fs/ssh.py @@ -2,7 +2,7 @@ import os.path import threading -from funcy import cached_property, first, memoize, silent, wrap_prop, wrap_with +from funcy import cached_property, memoize, silent, wrap_prop, wrap_with from dvc import prompt from dvc.scheme import Schemes @@ -68,14 +68,17 @@ def _prepare_credentials(self, **config): login_info["password"] = config.get("password") - if user_ssh_config.get("IdentityFile"): - config.setdefault( - "keyfile", first(user_ssh_config.get("IdentityFile")) - ) + raw_keys = [] + if config.get("keyfile"): + raw_keys.append(config.get("keyfile")) + elif user_ssh_config.get("IdentityFile"): + raw_keys.extend(user_ssh_config.get("IdentityFile")) + + if raw_keys: + login_info["client_keys"] = [ + os.path.expanduser(key) for key in raw_keys + ] - keyfile = config.get("keyfile") - if keyfile: - login_info["client_keys"] = [os.path.expanduser(keyfile)] login_info["timeout"] = config.get("timeout", _SSH_TIMEOUT) # These two settings fine tune the asyncssh to use the diff --git a/tests/unit/fs/test_ssh.py b/tests/unit/fs/test_ssh.py index 474a589def..da5a544e6f 100644 --- a/tests/unit/fs/test_ssh.py +++ b/tests/unit/fs/test_ssh.py @@ -135,6 +135,24 @@ def test_ssh_keyfile(mock_file, config, expected_keyfile): assert fs.fs_args.get("client_keys") == expected_keyfile +mock_ssh_multi_key_config = """ +IdentityFile file_1 + +Host example.com + IdentityFile file_2 +""" + + +@patch( + "builtins.open", + new_callable=mock_open, + read_data=mock_ssh_multi_key_config, +) +def test_ssh_multi_identity_files(mock_file): + fs = SSHFileSystem(host="example.com") + assert fs.fs_args.get("client_keys") == ["file_1", "file_2"] + + @pytest.mark.parametrize( "config,expected_gss_auth", [