##// END OF EJS Templates
added basic tunneling with ssh or paramiko
MinRK -
Show More
@@ -0,0 +1,183 b''
1 #!/usr/bin/env python
2
3 #
4 # This file is adapted from a paramiko demo, and thus LGPL 2.1.
5 # Original Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
6 # Edits Copyright (C) 2010 The IPython Team
7 #
8 # Paramiko is free software; you can redistribute it and/or modify it under the
9 # terms of the GNU Lesser General Public License as published by the Free
10 # Software Foundation; either version 2.1 of the License, or (at your option)
11 # any later version.
12 #
13 # Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
14 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15 # A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
16 # details.
17 #
18 # You should have received a copy of the GNU Lesser General Public License
19 # along with Paramiko; if not, write to the Free Software Foundation, Inc.,
20 # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
21
22 """
23 Sample script showing how to do local port forwarding over paramiko.
24
25 This script connects to the requested SSH server and sets up local port
26 forwarding (the openssh -L option) from a local port through a tunneled
27 connection to a destination reachable from the SSH server machine.
28 """
29
30 from __future__ import print_function
31
32 import getpass
33 import os
34 import socket
35 import select
36 import SocketServer
37 import sys
38 from optparse import OptionParser
39
40 import paramiko
41
42 SSH_PORT = 22
43 DEFAULT_PORT = 4000
44
45 g_verbose = False
46
47
48 class ForwardServer (SocketServer.ThreadingTCPServer):
49 daemon_threads = True
50 allow_reuse_address = True
51
52
53 class Handler (SocketServer.BaseRequestHandler):
54
55 def handle(self):
56 try:
57 chan = self.ssh_transport.open_channel('direct-tcpip',
58 (self.chain_host, self.chain_port),
59 self.request.getpeername())
60 except Exception, e:
61 verbose('Incoming request to %s:%d failed: %s' % (self.chain_host,
62 self.chain_port,
63 repr(e)))
64 return
65 if chan is None:
66 verbose('Incoming request to %s:%d was rejected by the SSH server.' %
67 (self.chain_host, self.chain_port))
68 return
69
70 verbose('Connected! Tunnel open %r -> %r -> %r' % (self.request.getpeername(),
71 chan.getpeername(), (self.chain_host, self.chain_port)))
72 while True:
73 r, w, x = select.select([self.request, chan], [], [])
74 if self.request in r:
75 data = self.request.recv(1024)
76 if len(data) == 0:
77 break
78 chan.send(data)
79 if chan in r:
80 data = chan.recv(1024)
81 if len(data) == 0:
82 break
83 self.request.send(data)
84 chan.close()
85 self.request.close()
86 verbose('Tunnel closed from %r' % (self.request.getpeername(),))
87
88
89 def forward_tunnel(local_port, remote_host, remote_port, transport):
90 # this is a little convoluted, but lets me configure things for the Handler
91 # object. (SocketServer doesn't give Handlers any way to access the outer
92 # server normally.)
93 class SubHander (Handler):
94 chain_host = remote_host
95 chain_port = remote_port
96 ssh_transport = transport
97 ForwardServer(('', local_port), SubHander).serve_forever()
98
99
100 def verbose(s):
101 if g_verbose:
102 print (s)
103
104
105 HELP = """\
106 Set up a forward tunnel across an SSH server, using paramiko. A local port
107 (given with -p) is forwarded across an SSH session to an address:port from
108 the SSH server. This is similar to the openssh -L option.
109 """
110
111
112 def get_host_port(spec, default_port):
113 "parse 'hostname:22' into a host and port, with the port optional"
114 args = (spec.split(':', 1) + [default_port])[:2]
115 args[1] = int(args[1])
116 return args[0], args[1]
117
118
119 def parse_options():
120 global g_verbose
121
122 parser = OptionParser(usage='usage: %prog [options] <ssh-server>[:<server-port>]',
123 version='%prog 1.0', description=HELP)
124 parser.add_option('-q', '--quiet', action='store_false', dest='verbose', default=True,
125 help='squelch all informational output')
126 parser.add_option('-p', '--local-port', action='store', type='int', dest='port',
127 default=DEFAULT_PORT,
128 help='local port to forward (default: %d)' % DEFAULT_PORT)
129 parser.add_option('-u', '--user', action='store', type='string', dest='user',
130 default=getpass.getuser(),
131 help='username for SSH authentication (default: %s)' % getpass.getuser())
132 parser.add_option('-K', '--key', action='store', type='string', dest='keyfile',
133 default=None,
134 help='private key file to use for SSH authentication')
135 parser.add_option('', '--no-key', action='store_false', dest='look_for_keys', default=True,
136 help='don\'t look for or use a private key file')
137 parser.add_option('-P', '--password', action='store_true', dest='readpass', default=False,
138 help='read password (for key or password auth) from stdin')
139 parser.add_option('-r', '--remote', action='store', type='string', dest='remote', default=None, metavar='host:port',
140 help='remote host and port to forward to')
141 options, args = parser.parse_args()
142
143 if len(args) != 1:
144 parser.error('Incorrect number of arguments.')
145 if options.remote is None:
146 parser.error('Remote address required (-r).')
147
148 g_verbose = options.verbose
149 server_host, server_port = get_host_port(args[0], SSH_PORT)
150 remote_host, remote_port = get_host_port(options.remote, SSH_PORT)
151 return options, (server_host, server_port), (remote_host, remote_port)
152
153
154 def main():
155 options, server, remote = parse_options()
156
157 password = None
158 if options.readpass:
159 password = getpass.getpass('Enter SSH password: ')
160
161 client = paramiko.SSHClient()
162 client.load_system_host_keys()
163 client.set_missing_host_key_policy(paramiko.WarningPolicy())
164
165 verbose('Connecting to ssh host %s:%d ...' % (server[0], server[1]))
166 try:
167 client.connect(server[0], server[1], username=options.user, key_filename=options.keyfile,
168 look_for_keys=options.look_for_keys, password=password)
169 except Exception as e:
170 print ('*** Failed to connect to %s:%d: %r' % (server[0], server[1], e))
171 sys.exit(1)
172
173 verbose('Now forwarding port %d to %s:%d ...' % (options.port, remote[0], remote[1]))
174
175 try:
176 forward_tunnel(options.port, remote[0], remote[1], client.get_transport())
177 except KeyboardInterrupt:
178 print ('C-c: Port forwarding stopped.')
179 sys.exit(0)
180
181
182 if __name__ == '__main__':
183 main()
@@ -0,0 +1,217 b''
1 """KernelStarter class that intercepts Control Queue messages, and handles process management."""
2
3 from zmq.eventloop import ioloop
4 from streamsession import StreamSession
5
6 class KernelStarter(object):
7 """Object for resetting/killing the Kernel."""
8
9
10 def __init__(self, session, upstream, downstream, *kernel_args, **kernel_kwargs):
11 self.session = session
12 self.upstream = upstream
13 self.downstream = downstream
14 self.kernel_args = kernel_args
15 self.kernel_kwargs = kernel_kwargs
16 self.handlers = {}
17 for method in 'shutdown_request shutdown_reply'.split():
18 self.handlers[method] = getattr(self, method)
19
20 def start(self):
21 self.upstream.on_recv(self.dispatch_request)
22 self.downstream.on_recv(self.dispatch_reply)
23
24 #--------------------------------------------------------------------------
25 # Dispatch methods
26 #--------------------------------------------------------------------------
27
28 def dispatch_request(self, raw_msg):
29 idents, msg = self.session.feed_identities()
30 try:
31 msg = self.session.unpack_message(msg, content=False)
32 except:
33 print ("bad msg: %s"%msg)
34
35 msgtype = msg['msg_type']
36 handler = self.handlers.get(msgtype, None)
37 if handler is None:
38 self.downstream.send_multipart(raw_msg)
39 else:
40 handler(msg)
41
42 def dispatch_reply(self, raw_msg):
43 idents, msg = self.session.feed_identities()
44 try:
45 msg = self.session.unpack_message(msg, content=False)
46 except:
47 print ("bad msg: %s"%msg)
48
49 msgtype = msg['msg_type']
50 handler = self.handlers.get(msgtype, None)
51 if handler is None:
52 self.upstream.send_multipart(raw_msg)
53 else:
54 handler(msg)
55
56 #--------------------------------------------------------------------------
57 # Handlers
58 #--------------------------------------------------------------------------
59
60 def shutdown_request(self, msg):
61
62
63 #--------------------------------------------------------------------------
64 # Kernel process management methods, from KernelManager:
65 #--------------------------------------------------------------------------
66
67 def _check_local(addr):
68 if isinstance(addr, tuple):
69 addr = addr[0]
70 return addr in LOCAL_IPS
71
72 def start_kernel(self, **kw):
73 """Starts a kernel process and configures the manager to use it.
74
75 If random ports (port=0) are being used, this method must be called
76 before the channels are created.
77
78 Parameters:
79 -----------
80 ipython : bool, optional (default True)
81 Whether to use an IPython kernel instead of a plain Python kernel.
82 """
83 self.kernel = Process(target=make_kernel, args=self.kernel_args,
84 kwargs=self.kernel_kwargs)
85
86 def shutdown_kernel(self, restart=False):
87 """ Attempts to the stop the kernel process cleanly. If the kernel
88 cannot be stopped, it is killed, if possible.
89 """
90 # FIXME: Shutdown does not work on Windows due to ZMQ errors!
91 if sys.platform == 'win32':
92 self.kill_kernel()
93 return
94
95 # Don't send any additional kernel kill messages immediately, to give
96 # the kernel a chance to properly execute shutdown actions. Wait for at
97 # most 1s, checking every 0.1s.
98 self.xreq_channel.shutdown(restart=restart)
99 for i in range(10):
100 if self.is_alive:
101 time.sleep(0.1)
102 else:
103 break
104 else:
105 # OK, we've waited long enough.
106 if self.has_kernel:
107 self.kill_kernel()
108
109 def restart_kernel(self, now=False):
110 """Restarts a kernel with the same arguments that were used to launch
111 it. If the old kernel was launched with random ports, the same ports
112 will be used for the new kernel.
113
114 Parameters
115 ----------
116 now : bool, optional
117 If True, the kernel is forcefully restarted *immediately*, without
118 having a chance to do any cleanup action. Otherwise the kernel is
119 given 1s to clean up before a forceful restart is issued.
120
121 In all cases the kernel is restarted, the only difference is whether
122 it is given a chance to perform a clean shutdown or not.
123 """
124 if self._launch_args is None:
125 raise RuntimeError("Cannot restart the kernel. "
126 "No previous call to 'start_kernel'.")
127 else:
128 if self.has_kernel:
129 if now:
130 self.kill_kernel()
131 else:
132 self.shutdown_kernel(restart=True)
133 self.start_kernel(**self._launch_args)
134
135 # FIXME: Messages get dropped in Windows due to probable ZMQ bug
136 # unless there is some delay here.
137 if sys.platform == 'win32':
138 time.sleep(0.2)
139
140 @property
141 def has_kernel(self):
142 """Returns whether a kernel process has been specified for the kernel
143 manager.
144 """
145 return self.kernel is not None
146
147 def kill_kernel(self):
148 """ Kill the running kernel. """
149 if self.has_kernel:
150 # Pause the heart beat channel if it exists.
151 if self._hb_channel is not None:
152 self._hb_channel.pause()
153
154 # Attempt to kill the kernel.
155 try:
156 self.kernel.kill()
157 except OSError, e:
158 # In Windows, we will get an Access Denied error if the process
159 # has already terminated. Ignore it.
160 if not (sys.platform == 'win32' and e.winerror == 5):
161 raise
162 self.kernel = None
163 else:
164 raise RuntimeError("Cannot kill kernel. No kernel is running!")
165
166 def interrupt_kernel(self):
167 """ Interrupts the kernel. Unlike ``signal_kernel``, this operation is
168 well supported on all platforms.
169 """
170 if self.has_kernel:
171 if sys.platform == 'win32':
172 from parentpoller import ParentPollerWindows as Poller
173 Poller.send_interrupt(self.kernel.win32_interrupt_event)
174 else:
175 self.kernel.send_signal(signal.SIGINT)
176 else:
177 raise RuntimeError("Cannot interrupt kernel. No kernel is running!")
178
179 def signal_kernel(self, signum):
180 """ Sends a signal to the kernel. Note that since only SIGTERM is
181 supported on Windows, this function is only useful on Unix systems.
182 """
183 if self.has_kernel:
184 self.kernel.send_signal(signum)
185 else:
186 raise RuntimeError("Cannot signal kernel. No kernel is running!")
187
188 @property
189 def is_alive(self):
190 """Is the kernel process still running?"""
191 # FIXME: not using a heartbeat means this method is broken for any
192 # remote kernel, it's only capable of handling local kernels.
193 if self.has_kernel:
194 if self.kernel.poll() is None:
195 return True
196 else:
197 return False
198 else:
199 # We didn't start the kernel with this KernelManager so we don't
200 # know if it is running. We should use a heartbeat for this case.
201 return True
202
203
204 def make_starter(up_addr, down_addr, *args, **kwargs):
205 """entry point script for launching a kernelstarter in a subprocess"""
206 loop = ioloop.IOLoop.instance()
207 ctx = zmq.Context()
208 session = StreamSession()
209 upstream = zmqstream.ZMQStream(ctx.socket(zmq.XREQ),loop)
210 upstream.connect(up_addr)
211 downstream = zmqstream.ZMQStream(ctx.socket(zmq.XREQ),loop)
212 downstream.connect(down_addr)
213
214 starter = KernelStarter(session, upstream, downstream, *args, **kwargs)
215 starter.start()
216 loop.start()
217 No newline at end of file
@@ -0,0 +1,123 b''
1
2
3 #-----------------------------------------
4 # Imports
5 #-----------------------------------------
6
7 from __future__ import print_function
8
9 import os,sys
10 from multiprocessing import Process
11 from getpass import getpass, getuser
12
13 try:
14 import paramiko
15 except ImportError:
16 paramiko = None
17 else:
18 from forward import forward_tunnel
19
20 from IPython.external import pexpect
21
22
23 def launch_ssh_tunnel(lport, rport, server, remoteip='127.0.0.1', keyfile=None, timeout=15):
24 """Create an ssh tunnel using command-line ssh that connects port lport
25 on this machine to localhost:rport on server. The tunnel
26 will automatically close when not in use, remaining open
27 for a minimum of timeout seconds for an initial connection.
28 """
29 ssh="ssh "
30 if keyfile:
31 ssh += "-i " + keyfile
32 cmd = ssh + " -f -L %i:127.0.0.1:%i %s sleep %i"%(lport, rport, server, timeout)
33 tunnel = pexpect.spawn(cmd)
34 failed = False
35 while True:
36 try:
37 tunnel.expect('[Pp]assword:', timeout=.1)
38 except pexpect.TIMEOUT:
39 continue
40 except pexpect.EOF:
41 if tunnel.exitstatus:
42 print (tunnel.exitstatus)
43 print (tunnel.before)
44 print (tunnel.after)
45 raise RuntimeError("tunnel '%s' failed to start"%(cmd))
46 else:
47 return tunnel.pid
48 else:
49 if failed:
50 print("Password rejected, try again")
51 tunnel.sendline(getpass())
52 failed = True
53
54 def _split_server(server):
55 if '@' in server:
56 username,server = server.split('@', 1)
57 else:
58 username = getuser()
59 if ':' in server:
60 server, port = server.split(':')
61 port = int(port)
62 else:
63 port = 22
64 return username, server, port
65
66 def launch_paramiko_tunnel(lport, rport, server, remoteip='127.0.0.1', keyfile=None):
67 """launch a tunner with paramiko in a subprocess"""
68 if paramiko is None:
69 raise ImportError("Paramiko not available")
70 server = _split_server(server)
71 if keyfile is None:
72 passwd = getpass("%s@%s's password: "%(server[0], server[1]))
73 else:
74 passwd = None
75 p = Process(target=_paramiko_tunnel,
76 args=(lport, rport, server, remoteip),
77 kwargs=dict(keyfile=keyfile, password=passwd))
78 p.daemon=False
79 p.start()
80 return p
81
82
83 def _paramiko_tunnel(lport, rport, server, remoteip, keyfile=None, password=None):
84 """function for actually starting a paramiko tunnel, to be passed
85 to multiprocessing.Process(target=this).
86 """
87 username, server, port = server
88 client = paramiko.SSHClient()
89 client.load_system_host_keys()
90 client.set_missing_host_key_policy(paramiko.WarningPolicy())
91
92 try:
93 client.connect(server, port, username=username, key_filename=keyfile,
94 look_for_keys=True, password=password)
95 except Exception as e:
96 print ('*** Failed to connect to %s:%d: %r' % (server, port, e))
97 sys.exit(1)
98
99 print ('Now forwarding port %d to %s:%d ...' % (lport, server, rport))
100
101 try:
102 forward_tunnel(lport, remoteip, rport, client.get_transport())
103 except KeyboardInterrupt:
104 print ('C-c: Port forwarding stopped.')
105 sys.exit(0)
106
107
108 __all__ = ['launch_ssh_tunnel', 'launch_paramiko_tunnel']
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
@@ -11,15 +11,7 b' ANYWHERE = 1 << 3'
11 class UnmetDependency(Exception):
11 class UnmetDependency(Exception):
12 pass
12 pass
13
13
14 class depend2(object):
14
15 """dependency decorator"""
16 def __init__(self, f, *args, **kwargs):
17 self.dependency = (f,args,kwargs)
18
19 def __call__(self, f, *args, **kwargs):
20 f._dependency = self.dependency
21 return decorator(_depend_wrapper, f)
22
23 class depend(object):
15 class depend(object):
24 """Dependency decorator, for use with tasks."""
16 """Dependency decorator, for use with tasks."""
25 def __init__(self, f, *args, **kwargs):
17 def __init__(self, f, *args, **kwargs):
General Comments 0
You need to be logged in to leave comments. Login now