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