Skip to content

Commit 3fc3049

Browse files
committed
Added fake SSH server to test command execution via the ParallelSSHClient. Added ParallelSSHClient tests
1 parent 5a6e99f commit 3fc3049

File tree

4 files changed

+170
-0
lines changed

4 files changed

+170
-0
lines changed

fake_server/__init__.py

Whitespace-only changes.

fake_server/fake_server.py

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#!/usr/bin/env python
2+
3+
"""Fake SSH server to test our SSH clients.
4+
Supports execution of commands via exec_command. Does _not_ support interactive shells,
5+
our clients do not use them.
6+
Server private key is hardcoded, server listen code inspired by demo_server.py in paramiko repository"""
7+
8+
import os
9+
import socket
10+
import sys
11+
import threading
12+
import traceback
13+
import logging
14+
import paramiko
15+
import time
16+
17+
logger = logging.getLogger(__name__)
18+
paramiko_logger = logging.getLogger('paramiko.transport')
19+
20+
host_key = paramiko.RSAKey(filename = os.path.sep.join([os.path.dirname(__file__), 'rsa.key']))
21+
22+
class Server (paramiko.ServerInterface):
23+
def __init__(self, cmd_req_response = {}, fail_auth = False):
24+
self.event = threading.Event()
25+
self.cmd_req_response = cmd_req_response
26+
self.fail_auth = fail_auth
27+
28+
def check_channel_request(self, kind, chanid):
29+
return paramiko.OPEN_SUCCEEDED
30+
31+
def check_auth_password(self, username, password):
32+
return paramiko.AUTH_SUCCESSFUL
33+
34+
def check_auth_publickey(self, username, key):
35+
if self.fail_auth: return paramiko.AUTH_FAILED
36+
return paramiko.AUTH_SUCCESSFUL
37+
38+
def get_allowed_auths(self, username):
39+
return 'password,publickey'
40+
41+
def check_channel_shell_request(self, channel):
42+
return False
43+
44+
def check_channel_pty_request(self, channel, term, width, height, pixelwidth,
45+
pixelheight, modes):
46+
return True
47+
48+
def check_channel_exec_request(self, channel, cmd):
49+
logger.debug("Got exec request on channel %s for cmd %s" % (channel, cmd,))
50+
if not cmd in self.cmd_req_response:
51+
return False
52+
channel.send(self.cmd_req_response[cmd])
53+
channel.send_exit_status(0)
54+
self.event.set()
55+
return True
56+
57+
def _make_socket(listen_ip, listen_port):
58+
try:
59+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
60+
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
61+
sock.bind((listen_ip, listen_port))
62+
except Exception, e:
63+
logger.error('Failed to bind to address - %s' % (str(e),))
64+
traceback.print_exc()
65+
return
66+
return sock
67+
68+
def listen(cmd_req_response, listen_ip = '127.0.0.1', listen_port = 2200, fail_auth = False):
69+
"""Run a fake ssh server and given a cmd_to_run, send given response"""
70+
sock = _make_socket(listen_ip, listen_port)
71+
if not sock: return
72+
try:
73+
sock.listen(100)
74+
logger.info('Listening for connection ...')
75+
client, addr = sock.accept()
76+
except Exception, e:
77+
logger.error('*** Listen/accept failed: %s' % (str(e),))
78+
traceback.print_exc()
79+
return
80+
logger.info('Got connection..')
81+
try:
82+
t = paramiko.Transport(client)
83+
try:
84+
t.load_server_moduli()
85+
except:
86+
raise
87+
t.add_server_key(host_key)
88+
server = Server(cmd_req_response = cmd_req_response, fail_auth = fail_auth)
89+
try:
90+
t.start_server(server=server)
91+
except paramiko.SSHException, _:
92+
logger.error('SSH negotiation failed.')
93+
return
94+
chan = t.accept(20)
95+
if not chan:
96+
logger.error("Could not establish channel")
97+
return
98+
logger.info("Authenticated..")
99+
chan.send_ready()
100+
server.event.wait(10)
101+
if not server.event.isSet():
102+
logger.error('Client never sent command')
103+
chan.close()
104+
return
105+
while not chan.send_ready():
106+
time.sleep(.5)
107+
chan.close()
108+
109+
except Exception, e:
110+
logger.error('*** Caught exception: %s: %s' % (str(e.__class__), str(e),))
111+
traceback.print_exc()
112+
try:
113+
t.close()
114+
except:
115+
pass
116+
return
117+
118+
if __name__ == "__main__":
119+
logging.basicConfig()
120+
logger.setLevel(logging.DEBUG)
121+
listen({'fake' : 'fake response' + os.linesep})

fake_server/rsa.key

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-----BEGIN RSA PRIVATE KEY-----
2+
MIICXQIBAAKBgQDI7iK3d8eWYZlYloat94c5VjtFY7c/0zuGl8C7uMnZ3t6i2G99
3+
66hEW0nCFSZkOW5F0XKEVj+EUCHvo8koYC6wiohAqWQnEwIoOoh7GSAcB8gP/qaq
4+
+adIl/Rvlby/mHakj+y05LBND6nFWHAn1y1gOFFKUXSJNRZPXSFy47gqzwIBIwKB
5+
gQCbANjz7q/pCXZLp1Hz6tYHqOvlEmjK1iabB1oqafrMpJ0eibUX/u+FMHq6StR5
6+
M5413BaDWHokPdEJUnabfWXXR3SMlBUKrck0eAer1O8m78yxu3OEdpRk+znVo4DL
7+
guMeCdJB/qcF0kEsx+Q8HP42MZU1oCmk3PbfXNFwaHbWuwJBAOQ/ry/hLD7AqB8x
8+
DmCM82A9E59ICNNlHOhxpJoh6nrNTPCsBAEu/SmqrL8mS6gmbRKUaya5Lx1pkxj2
9+
s/kWOokCQQDhXCcYXjjWiIfxhl6Rlgkk1vmI0l6785XSJNv4P7pXjGmShXfIzroh
10+
S8uWK3tL0GELY7+UAKDTUEVjjQdGxYSXAkEA3bo1JzKCwJ3lJZ1ebGuqmADRO6UP
11+
40xH977aadfN1mEI6cusHmgpISl0nG5YH7BMsvaT+bs1FUH8m+hXDzoqOwJBAK3Z
12+
X/za+KV/REya2z0b+GzgWhkXUGUa/owrEBdHGriQ47osclkUgPUdNqcLmaDilAF4
13+
1Z4PHPrI5RJIONAx+JECQQC/fChqjBgFpk6iJ+BOdSexQpgfxH/u/457W10Y43HR
14+
soS+8btbHqjQkowQ/2NTlUfWvqIlfxs6ZbFsIp/HrhZL
15+
-----END RSA PRIVATE KEY-----

tests/test_pssh_client.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/usr/bin/env python
2+
3+
"""Unittests for parallel-ssh"""
4+
5+
import unittest
6+
import gevent
7+
from gevent import monkey
8+
monkey.patch_all()
9+
from pssh import ParallelSSHClient, UnknownHostException
10+
from paramiko import AuthenticationException
11+
from fake_server.fake_server import listen
12+
13+
class ParallelSSHClientTest(unittest.TestCase):
14+
15+
def setUp(self):
16+
self.fake_cmd = 'fake cmd'
17+
self.fake_resp = 'fake response'
18+
19+
def test_pssh_client_exec_command(self):
20+
server = gevent.spawn(listen, { self.fake_cmd : self.fake_resp })
21+
client = ParallelSSHClient(['localhost'], port = 2200)
22+
cmd = client.exec_command(self.fake_cmd)[0]
23+
output = client.get_stdout(cmd)
24+
expected = {'localhost' : {'exit_code' : 0}}
25+
self.assertEqual(expected, output, msg = "Got unexpected command output - %s" % (output,))
26+
server.kill()
27+
28+
def test_pssh_client_auth_failure(self):
29+
server = gevent.spawn(listen, { self.fake_cmd : self.fake_resp }, listen_port = 2201, fail_auth = True)
30+
client = ParallelSSHClient(['localhost'], port = 2201)
31+
try:
32+
cmd = client.exec_command(self.fake_cmd)[0]
33+
except AuthenticationException:
34+
pass

0 commit comments

Comments
 (0)