##// END OF EJS Templates
split open_tunnel part of tunnel_connection into separate method...
MinRK -
Show More
@@ -1,300 +1,315
1 """Basic ssh tunneling utilities."""
1 """Basic ssh tunneling utilities."""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2008-2010 The IPython Development Team
4 # Copyright (C) 2008-2010 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10
10
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 import os,sys, atexit
18 import os,sys, atexit
19 from multiprocessing import Process
19 from multiprocessing import Process
20 from getpass import getpass, getuser
20 from getpass import getpass, getuser
21 import warnings
21 import warnings
22
22
23 try:
23 try:
24 with warnings.catch_warnings():
24 with warnings.catch_warnings():
25 warnings.simplefilter('ignore', DeprecationWarning)
25 warnings.simplefilter('ignore', DeprecationWarning)
26 import paramiko
26 import paramiko
27 except ImportError:
27 except ImportError:
28 paramiko = None
28 paramiko = None
29 else:
29 else:
30 from forward import forward_tunnel
30 from forward import forward_tunnel
31
31
32 try:
32 try:
33 from IPython.external import pexpect
33 from IPython.external import pexpect
34 except ImportError:
34 except ImportError:
35 pexpect = None
35 pexpect = None
36
36
37 from IPython.parallel.util import select_random_ports
37 from IPython.parallel.util import select_random_ports
38
38
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40 # Code
40 # Code
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # Check for passwordless login
44 # Check for passwordless login
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47 def try_passwordless_ssh(server, keyfile, paramiko=None):
47 def try_passwordless_ssh(server, keyfile, paramiko=None):
48 """Attempt to make an ssh connection without a password.
48 """Attempt to make an ssh connection without a password.
49 This is mainly used for requiring password input only once
49 This is mainly used for requiring password input only once
50 when many tunnels may be connected to the same server.
50 when many tunnels may be connected to the same server.
51
51
52 If paramiko is None, the default for the platform is chosen.
52 If paramiko is None, the default for the platform is chosen.
53 """
53 """
54 if paramiko is None:
54 if paramiko is None:
55 paramiko = sys.platform == 'win32'
55 paramiko = sys.platform == 'win32'
56 if not paramiko:
56 if not paramiko:
57 f = _try_passwordless_openssh
57 f = _try_passwordless_openssh
58 else:
58 else:
59 f = _try_passwordless_paramiko
59 f = _try_passwordless_paramiko
60 return f(server, keyfile)
60 return f(server, keyfile)
61
61
62 def _try_passwordless_openssh(server, keyfile):
62 def _try_passwordless_openssh(server, keyfile):
63 """Try passwordless login with shell ssh command."""
63 """Try passwordless login with shell ssh command."""
64 if pexpect is None:
64 if pexpect is None:
65 raise ImportError("pexpect unavailable, use paramiko")
65 raise ImportError("pexpect unavailable, use paramiko")
66 cmd = 'ssh -f '+ server
66 cmd = 'ssh -f '+ server
67 if keyfile:
67 if keyfile:
68 cmd += ' -i ' + keyfile
68 cmd += ' -i ' + keyfile
69 cmd += ' exit'
69 cmd += ' exit'
70 p = pexpect.spawn(cmd)
70 p = pexpect.spawn(cmd)
71 while True:
71 while True:
72 try:
72 try:
73 p.expect('[Ppassword]:', timeout=.1)
73 p.expect('[Ppassword]:', timeout=.1)
74 except pexpect.TIMEOUT:
74 except pexpect.TIMEOUT:
75 continue
75 continue
76 except pexpect.EOF:
76 except pexpect.EOF:
77 return True
77 return True
78 else:
78 else:
79 return False
79 return False
80
80
81 def _try_passwordless_paramiko(server, keyfile):
81 def _try_passwordless_paramiko(server, keyfile):
82 """Try passwordless login with paramiko."""
82 """Try passwordless login with paramiko."""
83 if paramiko is None:
83 if paramiko is None:
84 msg = "Paramiko unavaliable, "
84 msg = "Paramiko unavaliable, "
85 if sys.platform == 'win32':
85 if sys.platform == 'win32':
86 msg += "Paramiko is required for ssh tunneled connections on Windows."
86 msg += "Paramiko is required for ssh tunneled connections on Windows."
87 else:
87 else:
88 msg += "use OpenSSH."
88 msg += "use OpenSSH."
89 raise ImportError(msg)
89 raise ImportError(msg)
90 username, server, port = _split_server(server)
90 username, server, port = _split_server(server)
91 client = paramiko.SSHClient()
91 client = paramiko.SSHClient()
92 client.load_system_host_keys()
92 client.load_system_host_keys()
93 client.set_missing_host_key_policy(paramiko.WarningPolicy())
93 client.set_missing_host_key_policy(paramiko.WarningPolicy())
94 try:
94 try:
95 client.connect(server, port, username=username, key_filename=keyfile,
95 client.connect(server, port, username=username, key_filename=keyfile,
96 look_for_keys=True)
96 look_for_keys=True)
97 except paramiko.AuthenticationException:
97 except paramiko.AuthenticationException:
98 return False
98 return False
99 else:
99 else:
100 client.close()
100 client.close()
101 return True
101 return True
102
102
103
103
104 def tunnel_connection(socket, addr, server, keyfile=None, password=None, paramiko=None):
104 def tunnel_connection(socket, addr, server, keyfile=None, password=None, paramiko=None):
105 """Connect a socket to an address via an ssh tunnel.
105 """Connect a socket to an address via an ssh tunnel.
106
106
107 This is a wrapper for socket.connect(addr), when addr is not accessible
107 This is a wrapper for socket.connect(addr), when addr is not accessible
108 from the local machine. It simply creates an ssh tunnel using the remaining args,
108 from the local machine. It simply creates an ssh tunnel using the remaining args,
109 and calls socket.connect('tcp://localhost:lport') where lport is the randomly
109 and calls socket.connect('tcp://localhost:lport') where lport is the randomly
110 selected local port of the tunnel.
110 selected local port of the tunnel.
111
111
112 """
112 """
113 new_url, tunnel = open_tunnel(addr, server, keyfile=keyfile, password=password, paramiko=paramiko)
114 socket.connect(new_url)
115 return tunnel
116
117
118 def open_tunnel(addr, server, keyfile=None, password=None, paramiko=None):
119 """Open a tunneled connection from a 0MQ url.
120
121 For use inside tunnel_connection.
122
123 Returns
124 -------
125
126 (url, tunnel): The 0MQ url that has been forwarded, and the tunnel object
127 """
128
113 lport = select_random_ports(1)[0]
129 lport = select_random_ports(1)[0]
114 transport, addr = addr.split('://')
130 transport, addr = addr.split('://')
115 ip,rport = addr.split(':')
131 ip,rport = addr.split(':')
116 rport = int(rport)
132 rport = int(rport)
117 if paramiko is None:
133 if paramiko is None:
118 paramiko = sys.platform == 'win32'
134 paramiko = sys.platform == 'win32'
119 if paramiko:
135 if paramiko:
120 tunnelf = paramiko_tunnel
136 tunnelf = paramiko_tunnel
121 else:
137 else:
122 tunnelf = openssh_tunnel
138 tunnelf = openssh_tunnel
123 tunnel = tunnelf(lport, rport, server, remoteip=ip, keyfile=keyfile, password=password)
139 tunnel = tunnelf(lport, rport, server, remoteip=ip, keyfile=keyfile, password=password)
124 socket.connect('tcp://127.0.0.1:%i'%lport)
140 return 'tcp://127.0.0.1:%i'%lport, tunnel
125 return tunnel
126
141
127 def openssh_tunnel(lport, rport, server, remoteip='127.0.0.1', keyfile=None, password=None, timeout=15):
142 def openssh_tunnel(lport, rport, server, remoteip='127.0.0.1', keyfile=None, password=None, timeout=15):
128 """Create an ssh tunnel using command-line ssh that connects port lport
143 """Create an ssh tunnel using command-line ssh that connects port lport
129 on this machine to localhost:rport on server. The tunnel
144 on this machine to localhost:rport on server. The tunnel
130 will automatically close when not in use, remaining open
145 will automatically close when not in use, remaining open
131 for a minimum of timeout seconds for an initial connection.
146 for a minimum of timeout seconds for an initial connection.
132
147
133 This creates a tunnel redirecting `localhost:lport` to `remoteip:rport`,
148 This creates a tunnel redirecting `localhost:lport` to `remoteip:rport`,
134 as seen from `server`.
149 as seen from `server`.
135
150
136 keyfile and password may be specified, but ssh config is checked for defaults.
151 keyfile and password may be specified, but ssh config is checked for defaults.
137
152
138 Parameters
153 Parameters
139 ----------
154 ----------
140
155
141 lport : int
156 lport : int
142 local port for connecting to the tunnel from this machine.
157 local port for connecting to the tunnel from this machine.
143 rport : int
158 rport : int
144 port on the remote machine to connect to.
159 port on the remote machine to connect to.
145 server : str
160 server : str
146 The ssh server to connect to. The full ssh server string will be parsed.
161 The ssh server to connect to. The full ssh server string will be parsed.
147 user@server:port
162 user@server:port
148 remoteip : str [Default: 127.0.0.1]
163 remoteip : str [Default: 127.0.0.1]
149 The remote ip, specifying the destination of the tunnel.
164 The remote ip, specifying the destination of the tunnel.
150 Default is localhost, which means that the tunnel would redirect
165 Default is localhost, which means that the tunnel would redirect
151 localhost:lport on this machine to localhost:rport on the *server*.
166 localhost:lport on this machine to localhost:rport on the *server*.
152
167
153 keyfile : str; path to public key file
168 keyfile : str; path to public key file
154 This specifies a key to be used in ssh login, default None.
169 This specifies a key to be used in ssh login, default None.
155 Regular default ssh keys will be used without specifying this argument.
170 Regular default ssh keys will be used without specifying this argument.
156 password : str;
171 password : str;
157 Your ssh password to the ssh server. Note that if this is left None,
172 Your ssh password to the ssh server. Note that if this is left None,
158 you will be prompted for it if passwordless key based login is unavailable.
173 you will be prompted for it if passwordless key based login is unavailable.
159
174
160 """
175 """
161 if pexpect is None:
176 if pexpect is None:
162 raise ImportError("pexpect unavailable, use paramiko_tunnel")
177 raise ImportError("pexpect unavailable, use paramiko_tunnel")
163 ssh="ssh "
178 ssh="ssh "
164 if keyfile:
179 if keyfile:
165 ssh += "-i " + keyfile
180 ssh += "-i " + keyfile
166 cmd = ssh + " -f -L 127.0.0.1:%i:%s:%i %s sleep %i"%(lport, remoteip, rport, server, timeout)
181 cmd = ssh + " -f -L 127.0.0.1:%i:%s:%i %s sleep %i"%(lport, remoteip, rport, server, timeout)
167 tunnel = pexpect.spawn(cmd)
182 tunnel = pexpect.spawn(cmd)
168 failed = False
183 failed = False
169 while True:
184 while True:
170 try:
185 try:
171 tunnel.expect('[Pp]assword:', timeout=.1)
186 tunnel.expect('[Pp]assword:', timeout=.1)
172 except pexpect.TIMEOUT:
187 except pexpect.TIMEOUT:
173 continue
188 continue
174 except pexpect.EOF:
189 except pexpect.EOF:
175 if tunnel.exitstatus:
190 if tunnel.exitstatus:
176 print (tunnel.exitstatus)
191 print (tunnel.exitstatus)
177 print (tunnel.before)
192 print (tunnel.before)
178 print (tunnel.after)
193 print (tunnel.after)
179 raise RuntimeError("tunnel '%s' failed to start"%(cmd))
194 raise RuntimeError("tunnel '%s' failed to start"%(cmd))
180 else:
195 else:
181 return tunnel.pid
196 return tunnel.pid
182 else:
197 else:
183 if failed:
198 if failed:
184 print("Password rejected, try again")
199 print("Password rejected, try again")
185 password=None
200 password=None
186 if password is None:
201 if password is None:
187 password = getpass("%s's password: "%(server))
202 password = getpass("%s's password: "%(server))
188 tunnel.sendline(password)
203 tunnel.sendline(password)
189 failed = True
204 failed = True
190
205
191 def _split_server(server):
206 def _split_server(server):
192 if '@' in server:
207 if '@' in server:
193 username,server = server.split('@', 1)
208 username,server = server.split('@', 1)
194 else:
209 else:
195 username = getuser()
210 username = getuser()
196 if ':' in server:
211 if ':' in server:
197 server, port = server.split(':')
212 server, port = server.split(':')
198 port = int(port)
213 port = int(port)
199 else:
214 else:
200 port = 22
215 port = 22
201 return username, server, port
216 return username, server, port
202
217
203 def paramiko_tunnel(lport, rport, server, remoteip='127.0.0.1', keyfile=None, password=None, timeout=15):
218 def paramiko_tunnel(lport, rport, server, remoteip='127.0.0.1', keyfile=None, password=None, timeout=15):
204 """launch a tunner with paramiko in a subprocess. This should only be used
219 """launch a tunner with paramiko in a subprocess. This should only be used
205 when shell ssh is unavailable (e.g. Windows).
220 when shell ssh is unavailable (e.g. Windows).
206
221
207 This creates a tunnel redirecting `localhost:lport` to `remoteip:rport`,
222 This creates a tunnel redirecting `localhost:lport` to `remoteip:rport`,
208 as seen from `server`.
223 as seen from `server`.
209
224
210 If you are familiar with ssh tunnels, this creates the tunnel:
225 If you are familiar with ssh tunnels, this creates the tunnel:
211
226
212 ssh server -L localhost:lport:remoteip:rport
227 ssh server -L localhost:lport:remoteip:rport
213
228
214 keyfile and password may be specified, but ssh config is checked for defaults.
229 keyfile and password may be specified, but ssh config is checked for defaults.
215
230
216
231
217 Parameters
232 Parameters
218 ----------
233 ----------
219
234
220 lport : int
235 lport : int
221 local port for connecting to the tunnel from this machine.
236 local port for connecting to the tunnel from this machine.
222 rport : int
237 rport : int
223 port on the remote machine to connect to.
238 port on the remote machine to connect to.
224 server : str
239 server : str
225 The ssh server to connect to. The full ssh server string will be parsed.
240 The ssh server to connect to. The full ssh server string will be parsed.
226 user@server:port
241 user@server:port
227 remoteip : str [Default: 127.0.0.1]
242 remoteip : str [Default: 127.0.0.1]
228 The remote ip, specifying the destination of the tunnel.
243 The remote ip, specifying the destination of the tunnel.
229 Default is localhost, which means that the tunnel would redirect
244 Default is localhost, which means that the tunnel would redirect
230 localhost:lport on this machine to localhost:rport on the *server*.
245 localhost:lport on this machine to localhost:rport on the *server*.
231
246
232 keyfile : str; path to public key file
247 keyfile : str; path to public key file
233 This specifies a key to be used in ssh login, default None.
248 This specifies a key to be used in ssh login, default None.
234 Regular default ssh keys will be used without specifying this argument.
249 Regular default ssh keys will be used without specifying this argument.
235 password : str;
250 password : str;
236 Your ssh password to the ssh server. Note that if this is left None,
251 Your ssh password to the ssh server. Note that if this is left None,
237 you will be prompted for it if passwordless key based login is unavailable.
252 you will be prompted for it if passwordless key based login is unavailable.
238
253
239 """
254 """
240 if paramiko is None:
255 if paramiko is None:
241 raise ImportError("Paramiko not available")
256 raise ImportError("Paramiko not available")
242
257
243 if password is None:
258 if password is None:
244 if not _check_passwordless_paramiko(server, keyfile):
259 if not _check_passwordless_paramiko(server, keyfile):
245 password = getpass("%s's password: "%(server))
260 password = getpass("%s's password: "%(server))
246
261
247 p = Process(target=_paramiko_tunnel,
262 p = Process(target=_paramiko_tunnel,
248 args=(lport, rport, server, remoteip),
263 args=(lport, rport, server, remoteip),
249 kwargs=dict(keyfile=keyfile, password=password))
264 kwargs=dict(keyfile=keyfile, password=password))
250 p.daemon=False
265 p.daemon=False
251 p.start()
266 p.start()
252 atexit.register(_shutdown_process, p)
267 atexit.register(_shutdown_process, p)
253 return p
268 return p
254
269
255 def _shutdown_process(p):
270 def _shutdown_process(p):
256 if p.isalive():
271 if p.isalive():
257 p.terminate()
272 p.terminate()
258
273
259 def _paramiko_tunnel(lport, rport, server, remoteip, keyfile=None, password=None):
274 def _paramiko_tunnel(lport, rport, server, remoteip, keyfile=None, password=None):
260 """Function for actually starting a paramiko tunnel, to be passed
275 """Function for actually starting a paramiko tunnel, to be passed
261 to multiprocessing.Process(target=this), and not called directly.
276 to multiprocessing.Process(target=this), and not called directly.
262 """
277 """
263 username, server, port = _split_server(server)
278 username, server, port = _split_server(server)
264 client = paramiko.SSHClient()
279 client = paramiko.SSHClient()
265 client.load_system_host_keys()
280 client.load_system_host_keys()
266 client.set_missing_host_key_policy(paramiko.WarningPolicy())
281 client.set_missing_host_key_policy(paramiko.WarningPolicy())
267
282
268 try:
283 try:
269 client.connect(server, port, username=username, key_filename=keyfile,
284 client.connect(server, port, username=username, key_filename=keyfile,
270 look_for_keys=True, password=password)
285 look_for_keys=True, password=password)
271 # except paramiko.AuthenticationException:
286 # except paramiko.AuthenticationException:
272 # if password is None:
287 # if password is None:
273 # password = getpass("%s@%s's password: "%(username, server))
288 # password = getpass("%s@%s's password: "%(username, server))
274 # client.connect(server, port, username=username, password=password)
289 # client.connect(server, port, username=username, password=password)
275 # else:
290 # else:
276 # raise
291 # raise
277 except Exception as e:
292 except Exception as e:
278 print ('*** Failed to connect to %s:%d: %r' % (server, port, e))
293 print ('*** Failed to connect to %s:%d: %r' % (server, port, e))
279 sys.exit(1)
294 sys.exit(1)
280
295
281 # print ('Now forwarding port %d to %s:%d ...' % (lport, server, rport))
296 # print ('Now forwarding port %d to %s:%d ...' % (lport, server, rport))
282
297
283 try:
298 try:
284 forward_tunnel(lport, remoteip, rport, client.get_transport())
299 forward_tunnel(lport, remoteip, rport, client.get_transport())
285 except KeyboardInterrupt:
300 except KeyboardInterrupt:
286 print ('SIGINT: Port forwarding stopped cleanly')
301 print ('SIGINT: Port forwarding stopped cleanly')
287 sys.exit(0)
302 sys.exit(0)
288 except Exception as e:
303 except Exception as e:
289 print ("Port forwarding stopped uncleanly: %s"%e)
304 print ("Port forwarding stopped uncleanly: %s"%e)
290 sys.exit(255)
305 sys.exit(255)
291
306
292 if sys.platform == 'win32':
307 if sys.platform == 'win32':
293 ssh_tunnel = paramiko_tunnel
308 ssh_tunnel = paramiko_tunnel
294 else:
309 else:
295 ssh_tunnel = openssh_tunnel
310 ssh_tunnel = openssh_tunnel
296
311
297
312
298 __all__ = ['tunnel_connection', 'ssh_tunnel', 'openssh_tunnel', 'paramiko_tunnel', 'try_passwordless_ssh']
313 __all__ = ['tunnel_connection', 'ssh_tunnel', 'openssh_tunnel', 'paramiko_tunnel', 'try_passwordless_ssh']
299
314
300
315
General Comments 0
You need to be logged in to leave comments. Login now