summaryrefslogtreecommitdiffhomepage
path: root/tests/test_client.py
diff options
context:
space:
mode:
authorJeff Forcier <jeff@bitprophet.org>2017-06-06 15:15:40 -0700
committerJeff Forcier <jeff@bitprophet.org>2017-06-06 15:15:40 -0700
commit996fb6fd8ffb6df4f56c81e2ff199b9a600ecfc6 (patch)
tree4571659cb5f9320275cfedccc3ae897b0f425343 /tests/test_client.py
parent57394f5199ff75abc87b0373e18be2102540d50d (diff)
parentddb277d4e4989e914b67ff26c14c7c298e7fab9f (diff)
Merge branch 'master' into 471-int
Diffstat (limited to 'tests/test_client.py')
-rw-r--r--tests/test_client.py178
1 files changed, 145 insertions, 33 deletions
diff --git a/tests/test_client.py b/tests/test_client.py
index 3d2e75c9..aa3ff59b 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -22,6 +22,8 @@ Some unit tests for SSHClient.
from __future__ import with_statement
+import gc
+import platform
import socket
from tempfile import mkstemp
import threading
@@ -31,8 +33,9 @@ import warnings
import os
import time
from tests.util import test_path
+
import paramiko
-from paramiko.common import PY2, b
+from paramiko.common import PY2
from paramiko.ssh_exception import SSHException
@@ -40,6 +43,7 @@ FINGERPRINTS = {
'ssh-dss': b'\x44\x78\xf0\xb9\xa2\x3c\xc5\x18\x20\x09\xff\x75\x5b\xc1\xd2\x6c',
'ssh-rsa': b'\x60\x73\x38\x44\xcb\x51\x86\x65\x7f\xde\xda\xa2\x2b\x5a\x57\xd5',
'ecdsa-sha2-nistp256': b'\x25\x19\xeb\x55\xe6\xa1\x47\xff\x4f\x38\xd2\x75\x6f\xa5\xd5\x60',
+ 'ssh-ed25519': b'\xb3\xd5"\xaa\xf9u^\xe8\xcd\x0e\xea\x02\xb9)\xa2\x80',
}
@@ -57,6 +61,9 @@ class NullServer (paramiko.ServerInterface):
def check_auth_password(self, username, password):
if (username == 'slowdive') and (password == 'pygmalion'):
return paramiko.AUTH_SUCCESSFUL
+ if (username == 'slowdive') and (password == 'unresponsive-server'):
+ time.sleep(5)
+ return paramiko.AUTH_SUCCESSFUL
return paramiko.AUTH_FAILED
def check_auth_publickey(self, username, key):
@@ -75,10 +82,20 @@ class NullServer (paramiko.ServerInterface):
return paramiko.OPEN_SUCCEEDED
def check_channel_exec_request(self, channel, command):
- if command != 'yes':
+ if command != b'yes':
return False
return True
+ def check_channel_env_request(self, channel, name, value):
+ if name == 'INVALID_ENV':
+ return False
+
+ if not hasattr(channel, 'env'):
+ setattr(channel, 'env', {})
+
+ channel.env[name] = value
+ return True
+
class SSHClientTest (unittest.TestCase):
@@ -87,6 +104,12 @@ class SSHClientTest (unittest.TestCase):
self.sockl.bind(('localhost', 0))
self.sockl.listen(1)
self.addr, self.port = self.sockl.getsockname()
+ self.connect_kwargs = dict(
+ hostname=self.addr,
+ port=self.port,
+ username='slowdive',
+ look_for_keys=False,
+ )
self.event = threading.Event()
def tearDown(self):
@@ -124,7 +147,7 @@ class SSHClientTest (unittest.TestCase):
self.tc.get_host_keys().add('[%s]:%d' % (self.addr, self.port), 'ssh-rsa', public_host_key)
# Actual connection
- self.tc.connect(self.addr, self.port, username='slowdive', **kwargs)
+ self.tc.connect(**dict(self.connect_kwargs, **kwargs))
# Authentication successful?
self.event.wait(1.0)
@@ -173,7 +196,10 @@ class SSHClientTest (unittest.TestCase):
"""
verify that SSHClient works with an ECDSA key.
"""
- self._test_connection(key_filename=test_path('test_ecdsa.key'))
+ self._test_connection(key_filename=test_path('test_ecdsa_256.key'))
+
+ def test_client_ed25519(self):
+ self._test_connection(key_filename=test_path('test_ed25519.key'))
def test_3_multiple_key_files(self):
"""
@@ -190,15 +216,21 @@ class SSHClientTest (unittest.TestCase):
for attempt, accept in (
(['rsa', 'dss'], ['dss']), # Original test #3
(['dss', 'rsa'], ['dss']), # Ordering matters sometimes, sadly
- (['dss', 'rsa', 'ecdsa'], ['dss']), # Try ECDSA but fail
- (['rsa', 'ecdsa'], ['ecdsa']), # ECDSA success
+ (['dss', 'rsa', 'ecdsa_256'], ['dss']), # Try ECDSA but fail
+ (['rsa', 'ecdsa_256'], ['ecdsa']), # ECDSA success
):
- self._test_connection(
- key_filename=[
- test_path('test_{0}.key'.format(x)) for x in attempt
- ],
- allowed_keys=[types_[x] for x in accept],
- )
+ try:
+ self._test_connection(
+ key_filename=[
+ test_path('test_{0}.key'.format(x)) for x in attempt
+ ],
+ allowed_keys=[types_[x] for x in accept],
+ )
+ finally:
+ # Clean up to avoid occasional gc-related deadlocks.
+ # TODO: use nose test generators after nose port
+ self.tearDown()
+ self.setUp()
def test_multiple_key_files_failure(self):
"""
@@ -223,7 +255,7 @@ class SSHClientTest (unittest.TestCase):
self.tc = paramiko.SSHClient()
self.tc.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.assertEqual(0, len(self.tc.get_host_keys()))
- self.tc.connect(self.addr, self.port, username='slowdive', password='pygmalion')
+ self.tc.connect(password='pygmalion', **self.connect_kwargs)
self.event.wait(1.0)
self.assertTrue(self.event.is_set())
@@ -266,19 +298,18 @@ class SSHClientTest (unittest.TestCase):
transport's packetizer) is closed.
"""
# Unclear why this is borked on Py3, but it is, and does not seem worth
- # pursuing at the moment.
+ # pursuing at the moment. Skipped on PyPy because it fails on travis
+ # for unknown reasons, works fine locally.
# XXX: It's the release of the references to e.g packetizer that fails
# in py3...
- if not PY2:
+ if not PY2 or platform.python_implementation() == "PyPy":
return
threading.Thread(target=self._run).start()
- host_key = paramiko.RSAKey.from_private_key_file(test_path('test_rsa.key'))
- public_host_key = paramiko.RSAKey(data=host_key.asbytes())
self.tc = paramiko.SSHClient()
self.tc.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.assertEqual(0, len(self.tc.get_host_keys()))
- self.tc.connect(self.addr, self.port, username='slowdive', password='pygmalion')
+ self.tc.connect(**dict(self.connect_kwargs, password='pygmalion'))
self.event.wait(1.0)
self.assertTrue(self.event.is_set())
@@ -289,14 +320,10 @@ class SSHClientTest (unittest.TestCase):
self.tc.close()
del self.tc
- # hrm, sometimes p isn't cleared right away. why is that?
- #st = time.time()
- #while (time.time() - st < 5.0) and (p() is not None):
- # time.sleep(0.1)
-
- # instead of dumbly waiting for the GC to collect, force a collection
- # to see whether the SSHClient object is deallocated correctly
- import gc
+ # force a collection to see whether the SSHClient object is deallocated
+ # correctly. 2 GCs are needed to make sure it's really collected on
+ # PyPy
+ gc.collect()
gc.collect()
self.assertTrue(p() is None)
@@ -306,14 +333,12 @@ class SSHClientTest (unittest.TestCase):
verify that an SSHClient can be used a context manager
"""
threading.Thread(target=self._run).start()
- host_key = paramiko.RSAKey.from_private_key_file(test_path('test_rsa.key'))
- public_host_key = paramiko.RSAKey(data=host_key.asbytes())
with paramiko.SSHClient() as tc:
self.tc = tc
self.tc.set_missing_host_key_policy(paramiko.AutoAddPolicy())
self.assertEquals(0, len(self.tc.get_host_keys()))
- self.tc.connect(self.addr, self.port, username='slowdive', password='pygmalion')
+ self.tc.connect(**dict(self.connect_kwargs, password='pygmalion'))
self.event.wait(1.0)
self.assertTrue(self.event.is_set())
@@ -335,12 +360,99 @@ class SSHClientTest (unittest.TestCase):
self.tc = paramiko.SSHClient()
self.tc.get_host_keys().add('[%s]:%d' % (self.addr, self.port), 'ssh-rsa', public_host_key)
# Connect with a half second banner timeout.
+ kwargs = dict(self.connect_kwargs, banner_timeout=0.5)
self.assertRaises(
paramiko.SSHException,
self.tc.connect,
- self.addr,
- self.port,
- username='slowdive',
+ **kwargs
+ )
+
+ def test_8_auth_trickledown(self):
+ """
+ Failed key auth doesn't prevent subsequent pw auth from succeeding
+ """
+ # NOTE: re #387, re #394
+ # If pkey module used within Client._auth isn't correctly handling auth
+ # errors (e.g. if it allows things like ValueError to bubble up as per
+ # midway through #394) client.connect() will fail (at key load step)
+ # instead of succeeding (at password step)
+ kwargs = dict(
+ # Password-protected key whose passphrase is not 'pygmalion' (it's
+ # 'television' as per tests/test_pkey.py). NOTE: must use
+ # key_filename, loading the actual key here with PKey will except
+ # immediately; we're testing the try/except crap within Client.
+ key_filename=[test_path('test_rsa_password.key')],
+ # Actual password for default 'slowdive' user
password='pygmalion',
- banner_timeout=0.5
)
+ self._test_connection(**kwargs)
+
+ def test_9_auth_timeout(self):
+ """
+ verify that the SSHClient has a configurable auth timeout
+ """
+ threading.Thread(target=self._run).start()
+ host_key = paramiko.RSAKey.from_private_key_file(test_path('test_rsa.key'))
+ public_host_key = paramiko.RSAKey(data=host_key.asbytes())
+
+ self.tc = paramiko.SSHClient()
+ self.tc.get_host_keys().add('[%s]:%d' % (self.addr, self.port), 'ssh-rsa', public_host_key)
+ # Connect with a half second auth timeout
+ kwargs = dict(self.connect_kwargs, password='unresponsive-server', auth_timeout=0.5)
+ self.assertRaises(
+ paramiko.AuthenticationException,
+ self.tc.connect,
+ **kwargs
+ )
+
+ def test_update_environment(self):
+ """
+ Verify that environment variables can be set by the client.
+ """
+ threading.Thread(target=self._run).start()
+
+ self.tc = paramiko.SSHClient()
+ self.tc.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ self.assertEqual(0, len(self.tc.get_host_keys()))
+ self.tc.connect(self.addr, self.port, username='slowdive', password='pygmalion')
+
+ self.event.wait(1.0)
+ self.assertTrue(self.event.isSet())
+ self.assertTrue(self.ts.is_active())
+
+ target_env = {b'A': b'B', b'C': b'd'}
+
+ self.tc.exec_command('yes', environment=target_env)
+ schan = self.ts.accept(1.0)
+ self.assertEqual(target_env, getattr(schan, 'env', {}))
+ schan.close()
+
+ # Cannot use assertRaises in context manager mode as it is not supported
+ # in Python 2.6.
+ try:
+ # Verify that a rejection by the server can be detected
+ self.tc.exec_command('yes', environment={b'INVALID_ENV': b''})
+ except SSHException as e:
+ self.assertTrue('INVALID_ENV' in str(e),
+ 'Expected variable name in error message')
+ self.assertTrue(isinstance(e.args[1], SSHException),
+ 'Expected original SSHException in exception')
+ else:
+ self.assertFalse(False, 'SSHException was not thrown.')
+
+
+ def test_missing_key_policy_accepts_classes_or_instances(self):
+ """
+ Client.missing_host_key_policy() can take classes or instances.
+ """
+ # AN ACTUAL UNIT TEST?! GOOD LORD
+ # (But then we have to test a private API...meh.)
+ client = paramiko.SSHClient()
+ # Default
+ assert isinstance(client._policy, paramiko.RejectPolicy)
+ # Hand in an instance (classic behavior)
+ client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ assert isinstance(client._policy, paramiko.AutoAddPolicy)
+ # Hand in just the class (new behavior)
+ client.set_missing_host_key_policy(paramiko.AutoAddPolicy)
+ assert isinstance(client._policy, paramiko.AutoAddPolicy)