def test_key_with_passphrase_success(self): path = os.path.join(get_resources_base_path(), "ssh", "dummy_rsa_passphrase") with open(path, "r") as fp: private_key = fp.read() # Key material provided conn_params = { "hostname": "dummy.host.org", "username": "******", "key_material": private_key, "passphrase": "testphrase", } mock = ParamikoSSHClient(**conn_params) mock.connect() pkey = paramiko.RSAKey.from_private_key(StringIO(private_key), "testphrase") expected_conn = { "username": "******", "allow_agent": False, "hostname": "dummy.host.org", "look_for_keys": False, "pkey": pkey, "timeout": 30, "port": 22, } mock.client.connect.assert_called_once_with(**expected_conn) # Path to private key file provided conn_params = { "hostname": "dummy.host.org", "username": "******", "key_files": path, "passphrase": "testphrase", } mock = ParamikoSSHClient(**conn_params) mock.connect() expected_conn = { "username": "******", "allow_agent": False, "hostname": "dummy.host.org", "look_for_keys": False, "key_filename": path, "password": "******", "timeout": 30, "port": 22, } mock.client.connect.assert_called_once_with(**expected_conn)
def test_key_with_passphrase_success(self): path = os.path.join(get_resources_base_path(), 'ssh', 'dummy_rsa_passphrase') with open(path, 'r') as fp: private_key = fp.read() # Key material provided conn_params = { 'hostname': 'dummy.host.org', 'username': '******', 'key_material': private_key, 'passphrase': 'testphrase' } mock = ParamikoSSHClient(**conn_params) mock.connect() pkey = paramiko.RSAKey.from_private_key(StringIO(private_key), 'testphrase') expected_conn = { 'username': '******', 'allow_agent': False, 'hostname': 'dummy.host.org', 'look_for_keys': False, 'pkey': pkey, 'timeout': 30, 'port': 22 } mock.client.connect.assert_called_once_with(**expected_conn) # Path to private key file provided conn_params = { 'hostname': 'dummy.host.org', 'username': '******', 'key_files': path, 'passphrase': 'testphrase' } mock = ParamikoSSHClient(**conn_params) mock.connect() expected_conn = { 'username': '******', 'allow_agent': False, 'hostname': 'dummy.host.org', 'look_for_keys': False, 'key_filename': path, 'password': '******', 'timeout': 30, 'port': 22 } mock.client.connect.assert_called_once_with(**expected_conn)
def test_key_material_argument(self): path = os.path.join(get_resources_base_path(), "ssh", "dummy_rsa") with open(path, "r") as fp: private_key = fp.read() conn_params = { "hostname": "dummy.host.org", "username": "******", "key_material": private_key, } mock = ParamikoSSHClient(**conn_params) mock.connect() pkey = paramiko.RSAKey.from_private_key(StringIO(private_key)) expected_conn = { "username": "******", "allow_agent": False, "hostname": "dummy.host.org", "look_for_keys": False, "pkey": pkey, "timeout": 30, "port": 22, } mock.client.connect.assert_called_once_with(**expected_conn)
def test_key_material_argument(self): path = os.path.join(get_resources_base_path(), 'ssh', 'dummy_rsa') with open(path, 'r') as fp: private_key = fp.read() conn_params = { 'hostname': 'dummy.host.org', 'username': '******', 'key_material': private_key } mock = ParamikoSSHClient(**conn_params) mock.connect() pkey = paramiko.RSAKey.from_private_key(StringIO(private_key)) expected_conn = { 'username': '******', 'allow_agent': False, 'hostname': 'dummy.host.org', 'look_for_keys': False, 'pkey': pkey, 'timeout': 30, 'port': 22 } mock.client.connect.assert_called_once_with(**expected_conn)
def _connect(self, host, results, raise_on_any_error=False): (hostname, port) = self._get_host_port_info(host) extra = {'host': host, 'port': port, 'user': self._ssh_user} if self._ssh_password: extra['password'] = '******' elif self._ssh_key_file: extra['key_file_path'] = self._ssh_key_file else: extra['private_key'] = '<redacted>' LOG.debug('Connecting to host.', extra=extra) client = ParamikoSSHClient(hostname, username=self._ssh_user, password=self._ssh_password, bastion_host=self._bastion_host, key_files=self._ssh_key_file, key_material=self._ssh_key_material, passphrase=self._passphrase, port=port) try: client.connect() except Exception as ex: error = 'Failed connecting to host %s.' % hostname LOG.exception(error) if raise_on_any_error: raise error_dict = self._generate_error_result(exc=ex, message=error) self._bad_hosts[hostname] = error_dict results[hostname] = error_dict else: self._successful_connects += 1 self._hosts_client[hostname] = client results[hostname] = {'message': 'Connected to host.'}
def test_create_with_key_via_bastion(self): conn_params = {'hostname': 'dummy.host.org', 'bastion_host': 'bastion.host.org', 'username': '******', 'key_files': 'id_rsa'} mock = ParamikoSSHClient(**conn_params) mock.connect() expected_bastion_conn = {'username': '******', 'allow_agent': False, 'hostname': 'bastion.host.org', 'look_for_keys': False, 'key_filename': 'id_rsa', 'timeout': 60, 'port': 22} mock.bastion_client.connect.assert_called_once_with(**expected_bastion_conn) expected_conn = {'username': '******', 'allow_agent': False, 'hostname': 'dummy.host.org', 'look_for_keys': False, 'key_filename': 'id_rsa', 'timeout': 60, 'port': 22, 'sock': mock.bastion_socket} mock.client.connect.assert_called_once_with(**expected_conn)
def test_create_with_key_via_bastion(self): conn_params = { "hostname": "dummy.host.org", "bastion_host": "bastion.host.org", "username": "******", "key_files": "id_rsa", } mock = ParamikoSSHClient(**conn_params) mock.connect() expected_bastion_conn = { "username": "******", "allow_agent": False, "hostname": "bastion.host.org", "look_for_keys": False, "key_filename": "id_rsa", "timeout": 30, "port": 22, } mock.bastion_client.connect.assert_called_once_with( **expected_bastion_conn) expected_conn = { "username": "******", "allow_agent": False, "hostname": "dummy.host.org", "look_for_keys": False, "key_filename": "id_rsa", "timeout": 30, "port": 22, "sock": mock.bastion_socket, } mock.client.connect.assert_called_once_with(**expected_conn)
def test_socket_closed(self): conn_params = { 'hostname': 'dummy.host.org', 'username': '******', 'password': '******', 'timeout': '600' } ssh_client = ParamikoSSHClient(**conn_params) # Make sure .close() doesn't actually call anything real ssh_client.client = Mock() ssh_client.sftp_client = Mock() ssh_client.bastion_client = Mock() ssh_client.socket = Mock() ssh_client.bastion_socket = Mock() # Make sure we havent called any close methods at this point # TODO: Replace these with .assert_not_called() once it's Python 3.6+ only self.assertEqual(ssh_client.socket.close.call_count, 0) self.assertEqual(ssh_client.client.close.call_count, 0) self.assertEqual(ssh_client.sftp_client.close.call_count, 0) self.assertEqual(ssh_client.bastion_socket.close.call_count, 0) self.assertEqual(ssh_client.bastion_client.close.call_count, 0) # Call the function that has changed ssh_client.close() # TODO: Replace these with .assert_called_once() once it's Python 3.6+ only self.assertEqual(ssh_client.socket.close.call_count, 1) self.assertEqual(ssh_client.client.close.call_count, 1) self.assertEqual(ssh_client.sftp_client.close.call_count, 1) self.assertEqual(ssh_client.bastion_socket.close.call_count, 1) self.assertEqual(ssh_client.bastion_client.close.call_count, 1)
def test_passphrase_not_provided_for_encrypted_key_file(self): path = os.path.join(get_resources_base_path(), 'ssh', 'dummy_rsa_passphrase') conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'key_files': path} mock = ParamikoSSHClient(**conn_params) self.assertRaises(paramiko.ssh_exception.PasswordRequiredException, mock.connect)
def test_passphrase_no_key_provided(self): conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'passphrase': 'testphrase'} expected_msg = 'passphrase should accompany private key material' client = ParamikoSSHClient(**conn_params) self.assertRaisesRegexp(ValueError, expected_msg, client.connect)
def test_sftp_connection_is_only_established_if_required(self): # Verify that SFTP connection is lazily established only if and when needed. conn_params = { 'hostname': 'dummy.host.org', 'username': '******', 'password': '******' } # Verify sftp connection and client hasn't been established yet client = ParamikoSSHClient(**conn_params) client.connect() self.assertIsNone(client.sftp_client) # run method doesn't require sftp access so it shouldn't establish connection client = ParamikoSSHClient(**conn_params) client.connect() client.run(cmd='whoami') self.assertIsNone(client.sftp_client) # Methods below require SFTP access so they should cause SFTP connection to be established # put client = ParamikoSSHClient(**conn_params) client.connect() path = '/root/random_script.sh' client.put(path, path, mirror_local_mode=False) self.assertIsNotNone(client.sftp_client) # exists client = ParamikoSSHClient(**conn_params) client.connect() client.exists('/root/somepath.txt') self.assertIsNotNone(client.sftp_client) # mkdir client = ParamikoSSHClient(**conn_params) client.connect() client.mkdir('/root/somedirfoo') self.assertIsNotNone(client.sftp_client) # Verify close doesn't throw if SFTP connection is not established client = ParamikoSSHClient(**conn_params) client.connect() self.assertIsNone(client.sftp_client) client.close() # Verify SFTP connection is closed if it's opened client = ParamikoSSHClient(**conn_params) client.connect() client.mkdir('/root/somedirfoo') self.assertIsNotNone(client.sftp_client) client.close() self.assertEqual(client.sftp_client.close.call_count, 1)
def test_passphrase_no_key_provided(self): conn_params = { "hostname": "dummy.host.org", "username": "******", "passphrase": "testphrase", } expected_msg = "passphrase should accompany private key material" client = ParamikoSSHClient(**conn_params) self.assertRaisesRegexp(ValueError, expected_msg, client.connect)
def test_key_material_argument_invalid_key(self): conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'key_material': 'id_rsa'} mock = ParamikoSSHClient(**conn_params) expected_msg = 'Invalid or unsupported key type' self.assertRaisesRegexp(paramiko.ssh_exception.SSHException, expected_msg, mock.connect)
def test_passphrase_not_provided_for_encrypted_key_file(self): path = os.path.join(get_resources_base_path(), "ssh", "dummy_rsa_passphrase") conn_params = { "hostname": "dummy.host.org", "username": "******", "key_files": path, } mock = ParamikoSSHClient(**conn_params) self.assertRaises(paramiko.ssh_exception.PasswordRequiredException, mock.connect)
def test_key_material_argument_invalid_key(self): conn_params = { "hostname": "dummy.host.org", "username": "******", "key_material": "id_rsa", } mock = ParamikoSSHClient(**conn_params) expected_msg = "Invalid or unsupported key type" self.assertRaisesRegexp(paramiko.ssh_exception.SSHException, expected_msg, mock.connect)
def test_key_files_and_key_material_arguments_are_mutual_exclusive(self): conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'key_files': 'id_rsa', 'key_material': 'key'} expected_msg = ('key_files and key_material arguments are mutually exclusive. ' 'Supply only one.') client = ParamikoSSHClient(**conn_params) self.assertRaisesRegexp(ValueError, expected_msg, client.connect)
def setUp(self): """ Creates the object patching the actual connection. """ cfg.CONF.set_override(name='ssh_key_file', override=None, group='system_user') cfg.CONF.set_override(name='use_ssh_config', override=False, group='ssh_runner') conn_params = {'hostname': 'dummy.host.org', 'port': 8822, 'username': '******', 'key_files': '~/.ssh/ubuntu_ssh', 'timeout': '600'} self.ssh_cli = ParamikoSSHClient(**conn_params)
def test_key_material_contains_path_not_contents(self): conn_params = {'hostname': 'dummy.host.org', 'username': '******'} key_materials = ['~/.ssh/id_rsa', '/tmp/id_rsa', 'C:\\id_rsa'] expected_msg = ( '"private_key" parameter needs to contain private key data / content and ' 'not a path') for key_material in key_materials: conn_params = conn_params.copy() conn_params['key_material'] = key_material mock = ParamikoSSHClient(**conn_params) self.assertRaisesRegexp(paramiko.ssh_exception.SSHException, expected_msg, mock.connect)
def test_deprecated_key_argument(self): conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'key_files': 'id_rsa'} mock = ParamikoSSHClient(**conn_params) mock.connect() expected_conn = {'username': '******', 'allow_agent': False, 'hostname': 'dummy.host.org', 'look_for_keys': False, 'key_filename': 'id_rsa', 'timeout': 60, 'port': 22} mock.client.connect.assert_called_once_with(**expected_conn)
def test_key_material_contains_path_not_contents(self): conn_params = {"hostname": "dummy.host.org", "username": "******"} key_materials = ["~/.ssh/id_rsa", "/tmp/id_rsa", "C:\\id_rsa"] expected_msg = ( '"private_key" parameter needs to contain private key data / content and ' "not a path") for key_material in key_materials: conn_params = conn_params.copy() conn_params["key_material"] = key_material mock = ParamikoSSHClient(**conn_params) self.assertRaisesRegexp(paramiko.ssh_exception.SSHException, expected_msg, mock.connect)
def test_create_without_credentials(self): """ Initialize object with no credentials. Just to have better coverage, initialize the object without 'password' neither 'key'. Now that we only reconcile the final parameters at the last moment when we explicitly try to connect, all the credentials should be set to None. """ conn_params = {'hostname': 'dummy.host.org', 'username': '******'} mock = ParamikoSSHClient(**conn_params) self.assertEqual(mock.password, None) self.assertEqual(mock.key_material, None) self.assertEqual(mock.key_files, None)
def test_key_files_and_key_material_arguments_are_mutual_exclusive(self): conn_params = { "hostname": "dummy.host.org", "username": "******", "key_files": "id_rsa", "key_material": "key", } expected_msg = ( "key_files and key_material arguments are mutually exclusive. " "Supply only one.") client = ParamikoSSHClient(**conn_params) self.assertRaisesRegexp(ValueError, expected_msg, client.connect)
def test_create_with_password(self): conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'password': '******'} mock = ParamikoSSHClient(**conn_params) mock.connect() expected_conn = {'username': '******', 'password': '******', 'allow_agent': False, 'hostname': 'dummy.host.org', 'look_for_keys': False, 'timeout': 60, 'port': 22} mock.client.connect.assert_called_once_with(**expected_conn)
def _connect(self, host, results, raise_on_any_error=False): (hostname, port) = self._get_host_port_info(host) extra = {"host": host, "port": port, "user": self._ssh_user} if self._ssh_password: extra["password"] = "******" elif self._ssh_key_file: extra["key_file_path"] = self._ssh_key_file else: extra["private_key"] = "<redacted>" LOG.debug("Connecting to host.", extra=extra) client = ParamikoSSHClient( hostname=hostname, port=port, username=self._ssh_user, password=self._ssh_password, bastion_host=self._bastion_host, key_files=self._ssh_key_file, key_material=self._ssh_key_material, passphrase=self._passphrase, handle_stdout_line_func=self._handle_stdout_line_func, handle_stderr_line_func=self._handle_stderr_line_func, ) try: client.connect() except SSHException as ex: LOG.exception(ex) if raise_on_any_error: raise error_dict = self._generate_error_result( exc=ex, message="Connection error." ) self._bad_hosts[hostname] = error_dict results[hostname] = error_dict except Exception as ex: error = "Failed connecting to host %s." % hostname LOG.exception(error) if raise_on_any_error: raise error_dict = self._generate_error_result(exc=ex, message=error) self._bad_hosts[hostname] = error_dict results[hostname] = error_dict else: self._successful_connects += 1 self._hosts_client[hostname] = client results[hostname] = {"message": "Connected to host."}
def test_fail_set_proxycommand(self, mock_ProxyCommand): """ Loads proxy commands from ssh config file """ ssh_config_file_path = os.path.join(get_resources_base_path(), 'ssh', 'dummy_ssh_config_fail') cfg.CONF.set_override(name='ssh_config_file_path', override=ssh_config_file_path, group='ssh_runner') cfg.CONF.set_override(name='use_ssh_config', override=True, group='ssh_runner') conn_params = {'hostname': 'dummy.host.org'} mock = ParamikoSSHClient(**conn_params) self.assertRaises(Exception, mock.connect) mock_ProxyCommand.assert_not_called()
def test_set_proxycommand(self, mock_ProxyCommand): """ Loads proxy commands from ssh config file """ ssh_config_file_path = os.path.join(get_resources_base_path(), 'ssh', 'dummy_ssh_config') cfg.CONF.set_override(name='ssh_config_file_path', override=ssh_config_file_path, group='ssh_runner') cfg.CONF.set_override(name='use_ssh_config', override=True, group='ssh_runner') conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'password': '******'} mock = ParamikoSSHClient(**conn_params) mock.connect() mock_ProxyCommand.assert_called_once_with('ssh -q -W dummy.host.org:22 dummy_bastion')
def test_incorrect_passphrase(self): path = os.path.join(get_resources_base_path(), 'ssh', 'dummy_rsa_passphrase') with open(path, 'r') as fp: private_key = fp.read() conn_params = {'hostname': 'dummy.host.org', 'username': '******', 'key_material': private_key, 'passphrase': 'incorrect'} mock = ParamikoSSHClient(**conn_params) expected_msg = 'Invalid passphrase or invalid/unsupported key type' self.assertRaisesRegexp(paramiko.ssh_exception.SSHException, expected_msg, mock.connect)
def test_create_without_credentials_use_default_key(self): # No credentials are provided by default stanley ssh key exists so it should use that cfg.CONF.set_override(name='ssh_key_file', override='stanley_rsa', group='system_user') conn_params = {'hostname': 'dummy.host.org', 'username': '******'} mock = ParamikoSSHClient(**conn_params) mock.connect() expected_conn = {'username': '******', 'hostname': 'dummy.host.org', 'key_filename': 'stanley_rsa', 'allow_agent': False, 'look_for_keys': False, 'timeout': 60, 'port': 22} mock.client.connect.assert_called_once_with(**expected_conn)
def test_consume_stderr(self): # Test utf-8 decoding of ``stderr`` still works fine when reading CHUNK_SIZE splits a # multi-byte utf-8 character in the middle. We should wait to collect all bytes # and finally decode. conn_params = {'hostname': 'dummy.host.org', 'username': '******'} mock = ParamikoSSHClient(**conn_params) mock.CHUNK_SIZE = 1 chan = Mock() chan.recv_stderr_ready.side_effect = [True, True, True, True, False] chan.recv_stderr.side_effect = [b'\xF0', b'\x90', b'\x8D', b'\x88'] try: b'\xF0'.decode('utf-8') self.fail('Test fixture is not right.') except UnicodeDecodeError: pass stderr = mock._consume_stderr(chan) self.assertEqual(u'\U00010348', stderr.getvalue())
def test_consume_stderr(self): # Test utf-8 decoding of ``stderr`` still works fine when reading CHUNK_SIZE splits a # multi-byte utf-8 character in the middle. We should wait to collect all bytes # and finally decode. conn_params = {"hostname": "dummy.host.org", "username": "******"} mock = ParamikoSSHClient(**conn_params) mock.CHUNK_SIZE = 1 chan = Mock() chan.recv_stderr_ready.side_effect = [True, True, True, True, False] chan.recv_stderr.side_effect = [b"\xF0", b"\x90", b"\x8D", b"\x88"] try: b"\xF0".decode("utf-8") self.fail("Test fixture is not right.") except UnicodeDecodeError: pass stderr = mock._consume_stderr(chan) self.assertEqual("\U00010348", stderr.getvalue())