Codebase list sshtunnel / fresh-snapshots/main
New upstream snapshot. Debian Janitor 1 year, 2 months ago
33 changed file(s) with 2368 addition(s) and 1505 deletion(s). Raw diff Collapse all Expand all
0 Copyright (c) 2014-2016 Pahaz Blinov
0 Copyright (c) 2014-2019 Pahaz White
11
22 Permission is hereby granted, free of charge, to any person obtaining a copy
33 of this software and associated documentation files (the "Software"), to deal
00 # Include the data files recursive-include data *
1
2 # If using Python 2.6 or less, then have to include package data, even though
3 # it's already declared in setup.py
4 # include sample/*.dat
51
62 include LICENSE
73 include *.rst
106 include docs/*.rst
117 include docs/*.txt
128 include tests/*
9 include e2e_tests/*
10 include e2e_tests/ssh-server-config/*
11 exclude .github/*
12 exclude .circleci/*
13 exclude *.pyc
14 exclude __pycache__
15 exclude Pipfile*
+491
-404
PKG-INFO less more
0 Metadata-Version: 1.1
0 Metadata-Version: 2.1
11 Name: sshtunnel
2 Version: 0.1.4
2 Version: 0.4.0
33 Summary: Pure python SSH tunnels
44 Home-page: https://github.com/pahaz/sshtunnel
5 Author: Pahaz Blinov
6 Author-email: pahaz.blinov@gmail.com
5 Download-URL: https://pypi.python.org/packages/source/s/sshtunnel/sshtunnel-0.4.0.zip
6 Author: Pahaz White
7 Author-email: pahaz.white@gmail.com
78 License: MIT
8 Download-URL: https://pypi.python.org/packages/source/s/sshtunnel/sshtunnel-0.1.4.zip
9 Description-Content-Type: UNKNOWN
10 Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
11
12 |pyversions| |license|
13
14 **Author**: `Pahaz Blinov`_
15
16 **Repo**: https://github.com/pahaz/sshtunnel/
17
18 Inspired by https://github.com/jmagnusson/bgtunnel, but it doesn't work on
19 Windows.
20
21 See also: https://github.com/paramiko/paramiko/blob/master/demos/forward.py
22
23 Requirements
24 -------------
25
26 * `paramiko`_
27
28 Installation
29 ============
30
31 `sshtunnel`_ is on PyPI, so simply run:
32
33 ::
34
35 pip install sshtunnel
36
37 or ::
38
39 easy_install sshtunnel
40
41 or ::
42
43 conda install -c conda-forge sshtunnel
44
45 to have it installed in your environment.
46
47 For installing from source, clone the
48 `repo <https://github.com/pahaz/sshtunnel>`_ and run::
49
50 python setup.py install
51
52 Testing the package
53 -------------------
54
55 In order to run the tests you first need
56 `tox <https://testrun.org/tox/latest/>`_ and run::
57
58 python setup.py test
59
60 Usage scenarios
61 ===============
62
63 One of the typical scenarios where ``sshtunnel`` is helpful is depicted in the
64 figure below. User may need to connect a port of a remote server (i.e. 8080)
65 where only SSH port (usually port 22) is reachable. ::
66
67 ----------------------------------------------------------------------
68
69 |
70 -------------+ | +----------+
71 LOCAL | | | REMOTE | :22 SSH
72 CLIENT | <== SSH ========> | SERVER | :8080 web service
73 -------------+ | +----------+
74 |
75 FIREWALL (only port 22 is open)
76
77 ----------------------------------------------------------------------
78
79 **Fig1**: How to connect to a service blocked by a firewall through SSH tunnel.
80
81
82 If allowed by the SSH server, it is also possible to reach a private server
83 (from the perspective of ``REMOTE SERVER``) not directly visible from the
84 outside (``LOCAL CLIENT``'s perspective). ::
85
86 ----------------------------------------------------------------------
87
88 |
89 -------------+ | +----------+ +---------
90 LOCAL | | | REMOTE | | PRIVATE
91 CLIENT | <== SSH ========> | SERVER | <== local ==> | SERVER
92 -------------+ | +----------+ +---------
93 |
94 FIREWALL (only port 443 is open)
95
96 ----------------------------------------------------------------------
97
98 **Fig2**: How to connect to ``PRIVATE SERVER`` through SSH tunnel.
99
100
101 Usage examples
102 ==============
103
104 API allows either initializing the tunnel and starting it or using a ``with``
105 context, which will take care of starting **and stopping** the tunnel:
106
107 Example 1
108 ---------
109
110 Code corresponding to **Fig1** above follows, given remote server's address is
111 ``pahaz.urfuclub.ru``, password authentication and randomly assigned local bind
112 port.
113
114 .. code-block:: py
115
116 from sshtunnel import SSHTunnelForwarder
117
118 server = SSHTunnelForwarder(
119 'pahaz.urfuclub.ru',
120 ssh_username="pahaz",
121 ssh_password="secret",
122 remote_bind_address=('127.0.0.1', 8080)
123 )
124
125 server.start()
126
127 print(server.local_bind_port) # show assigned local port
128 # work with `SECRET SERVICE` through `server.local_bind_port`.
129
130 server.stop()
131
132 Example 2
133 ---------
134
135 Example of a port forwarding to a private server not directly reachable,
136 assuming password protected pkey authentication, remote server's SSH service is
137 listening on port 443 and that port is open in the firewall (**Fig2**):
138
139 .. code-block:: py
140
141 import paramiko
142 from sshtunnel import SSHTunnelForwarder
143
144 with SSHTunnelForwarder(
145 (REMOTE_SERVER_IP, 443),
146 ssh_username="",
147 ssh_pkey="/var/ssh/rsa_key",
148 ssh_private_key_password="secret",
149 remote_bind_address=(PRIVATE_SERVER_IP, 22),
150 local_bind_address=('0.0.0.0', 10022)
151 ) as tunnel:
152 client = paramiko.SSHClient()
153 client.load_system_host_keys()
154 client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
155 client.connect('127.0.0.1', 10022)
156 # do some operations with client session
157 client.close()
158
159 print('FINISH!')
160
161 Example 3
162 ---------
163
164 Example of a port forwarding for the Vagrant MySQL local port:
165
166 .. code-block:: py
167
168 from sshtunnel import SSHTunnelForwarder
169 from time import sleep
170
171 with SSHTunnelForwarder(
172 ('localhost', 2222),
173 ssh_username="vagrant",
174 ssh_password="vagrant",
175 remote_bind_address=('127.0.0.1', 3306)
176 ) as server:
177
178 print(server.local_bind_port)
179 while True:
180 # press Ctrl-C for stopping
181 sleep(1)
182
183 print('FINISH!')
184
185 Or simply using the CLI:
186
187 .. code-block:: console
188
189 (bash)$ python -m sshtunnel -U vagrant -P vagrant -L :3306 -R 127.0.0.1:3306 -p 2222 localhost
190
191 CLI usage
192 =========
193
194 ::
195
196 $ sshtunnel --help
197 usage: sshtunnel [-h] [-U SSH_USERNAME] [-p SSH_PORT] [-P SSH_PASSWORD] -R
198 IP:PORT [IP:PORT ...] [-L [IP:PORT [IP:PORT ...]]]
199 [-k SSH_HOST_KEY] [-K KEY_FILE] [-S KEY_PASSWORD] [-t] [-v]
200 [-V] [-x IP:PORT] [-c SSH_CONFIG_FILE] [-z] [-n] [-d [FOLDER [FOLDER ...]]]
201 ssh_address
202
203 Pure python ssh tunnel utils
204 Version 0.1.4
205
206 positional arguments:
207 ssh_address SSH server IP address (GW for SSH tunnels)
208 set with "-- ssh_address" if immediately after -R or -L
209
210 optional arguments:
211 -h, --help show this help message and exit
212 -U SSH_USERNAME, --username SSH_USERNAME
213 SSH server account username
214 -p SSH_PORT, --server_port SSH_PORT
215 SSH server TCP port (default: 22)
216 -P SSH_PASSWORD, --password SSH_PASSWORD
217 SSH server account password
218 -R IP:PORT [IP:PORT ...], --remote_bind_address IP:PORT [IP:PORT ...]
219 Remote bind address sequence: ip_1:port_1 ip_2:port_2 ... ip_n:port_n
220 Equivalent to ssh -Lxxxx:IP_ADDRESS:PORT
221 If port is omitted, defaults to 22.
222 Example: -R 10.10.10.10: 10.10.10.10:5900
223 -L [IP:PORT [IP:PORT ...]], --local_bind_address [IP:PORT [IP:PORT ...]]
224 Local bind address sequence: ip_1:port_1 ip_2:port_2 ... ip_n:port_n
225 Elements may also be valid UNIX socket domains:
226 /tmp/foo.sock /tmp/bar.sock ... /tmp/baz.sock
227 Equivalent to ssh -LPORT:xxxxxxxxx:xxxx, being the local IP address optional.
228 By default it will listen in all interfaces (0.0.0.0) and choose a random port.
229 Example: -L :40000
230 -k SSH_HOST_KEY, --ssh_host_key SSH_HOST_KEY
231 Gateway's host key
232 -K KEY_FILE, --private_key_file KEY_FILE
233 RSA/DSS/ECDSA private key file
234 -S KEY_PASSWORD, --private_key_password KEY_PASSWORD
235 RSA/DSS/ECDSA private key password
236 -t, --threaded Allow concurrent connections to each tunnel
237 -v, --verbose Increase output verbosity (default: ERROR)
238 -V, --version Show version number and quit
239 -x IP:PORT, --proxy IP:PORT
240 IP and port of SSH proxy to destination
241 -c SSH_CONFIG_FILE, --config SSH_CONFIG_FILE
242 SSH configuration file, defaults to ~/.ssh/config
243 -z, --compress Request server for compression over SSH transport
244 -n, --noagent Disable looking for keys from an SSH agent
245 -d [FOLDER [FOLDER ...]], --host_pkey_directories [FOLDER [FOLDER ...]]
246 List of directories where SSH pkeys (in the format `id_*`) may be found
247
248 .. _Pahaz Blinov: https://github.com/pahaz
249 .. _sshtunnel: https://pypi.python.org/pypi/sshtunnel
250 .. _paramiko: http://www.paramiko.org/
251 .. |CircleCI| image:: https://circleci.com/gh/pahaz/sshtunnel.svg?style=svg
252 :target: https://circleci.com/gh/pahaz/sshtunnel
253 .. |AppVeyor| image:: https://ci.appveyor.com/api/projects/status/oxg1vx2ycmnw3xr9?svg=true&passingText=Windows%20-%20OK&failingText=Windows%20-%20Fail
254 :target: https://ci.appveyor.com/project/pahaz/sshtunnel
255 .. |readthedocs| image:: https://readthedocs.org/projects/sshtunnel/badge/?version=latest
256 :target: http://sshtunnel.readthedocs.io/en/latest/?badge=latest
257 :alt: Documentation Status
258 .. |coveralls| image:: https://coveralls.io/repos/github/pahaz/sshtunnel/badge.svg?branch=master
259 :target: https://coveralls.io/github/pahaz/sshtunnel?branch=master
260 .. |pyversions| image:: https://img.shields.io/pypi/pyversions/sshtunnel.svg
261 .. |version| image:: https://img.shields.io/pypi/v/sshtunnel.svg
262 :target: `sshtunnel`_
263 .. |license| image:: https://img.shields.io/pypi/l/sshtunnel.svg
264 :target: https://github.com/pahaz/sshtunnel/blob/master/LICENSE
265
266 Online documentation
267 ====================
268
269 Documentation may be found at `readthedocs`_.
270
271 .. _readthedocs: https://sshtunnel.readthedocs.org/
272
273 CONTRIBUTORS
274 ============
275
276 - `Cameron Maske`_
277 - `Gustavo Machado`_
278 - `Colin Jermain`_
279 - `JM Fernández`_ - (big thanks!)
280 - `Lewis Thompson`_
281 - `Erik Rogers`_
282 - `Mart Sõmermaa`_
283 - `Chronial`_
284 - `Dan Harbin`_
285 - `Ignacio Peluffo`_
286 - `Niels Zeilemaker`_
287
288 CHANGELOG
289 =========
290
291 - v.0.1.4 (`Niels Zeilemaker`_)
292 + Allow loading pkeys from `~/.ssh`
293
294 - v.0.1.3 (`Ignacio Peluffo`_ and others)
295 + ``pkey_file`` parameter updated to accept relative paths to user folder using ``~``
296 + Several bugfixes
297
298 - v.0.1.2 (`JM Fernández`_)
299 + Fix #77
300
301 - v.0.1.1 (`JM Fernández`_)
302 + Fix #72
303
304 - v.0.1.0 (`JM Fernández`_)
305 + Add `tunnel_bindings` property
306 + Several bugfixes (#49, #56, #57, #59, #60, #62, #64, #66, ...)
307 (`Pahaz Blinov`_, `JM Fernández`_)
308 + Add TRACE logging level (`JM Fernández`_)
309 + Code and tests refactoring (`JM Fernández`_)
310 + Drop python3.2 support
311
312 - v.0.0.8 (`JM Fernández`_)
313 + Merge `#31`_: Support Unix domain socket (local) forwarding (`Dan Harbin`_)
314 + Simplify API (`JM Fernández`_)
315 + Add sphinx-based documentation (`JM Fernández`_)
316 + Add ``allow_agent`` (fixes `#36`_, `#46`_) (`JM Fernández`_)
317 + Add ``compression`` (`JM Fernández`_)
318 + Add ``__str__`` method (`JM Fernández`_)
319 + Add test functions (`JM Fernández`_)
320 + Fix default username when not provided and ssh_config file is skipped (`JM Fernández`_)
321 + Fix gateway IP unresolvable exception catching (`JM Fernández`_)
322 + Minor fixes (`JM Fernández`_)
323 + Add AppVeyor support (`JM Fernández`_)
324
325 - v.0.0.7 (`JM Fernández`_)
326 + Tunnels can now be stopped and started safely (`#41`_) (`JM Fernández`_)
327 + Add timeout to SSH gateway and keep-alive messages (`#29`_) (`JM Fernández`_)
328 + Allow sending a pkey directly (`#43`_) (`Chronial`_)
329 + Add ``-V`` CLI option to show current version (`JM Fernández`_)
330 + Add coverage (`JM Fernández`_)
331 + Refactoring (`JM Fernández`_)
332
333 - v.0.0.6 (`Pahaz Blinov`_)
334 + add ``-S`` CLI options for ssh private key password support (`Pahaz Blinov`_)
335
336 - v.0.0.5 (`Pahaz Blinov`_)
337 + add ``ssh_proxy`` argument, as well as ``ssh_config(5)`` ``ProxyCommand`` support (`Lewis Thompson`_)
338 + add some python 2.6 compatibility fixes (`Mart Sõmermaa`_)
339 + ``paramiko.transport`` inherits handlers of loggers passed to ``SSHTunnelForwarder`` (`JM Fernández`_)
340 + fix `#34`_, `#33`_, code style and docs (`JM Fernández`_)
341 + add tests (`Pahaz Blinov`_)
342 + add CI integration (`Pahaz Blinov`_)
343 + normal packaging (`Pahaz Blinov`_)
344 + disable check distenation socket connection by ``SSHTunnelForwarder.local_is_up`` (`Pahaz Blinov`_) [changed default behavior]
345 + use daemon mode = False in all threads by default; detail_ (`Pahaz Blinov`_) [changed default behavior]
346
347 - v.0.0.4.4 (`Pahaz Blinov`_)
348 + fix issue `#24`_ - hide ssh password in logs (`Pahaz Blinov`_)
349
350 - v.0.0.4.3 (`Pahaz Blinov`_)
351 + fix default port issue `#19`_ (`Pahaz Blinov`_)
352
353 - v.0.0.4.2 (`Pahaz Blinov`_)
354 + fix Thread.daemon mode for Python < 3.3 `#16`_, `#21`_ (`Lewis Thompson`_, `Erik Rogers`_)
355
356 - v.0.0.4.1 (`Pahaz Blinov`_)
357 + fix CLI issues `#13`_ (`Pahaz Blinov`_)
358
359 - v.0.0.4 (`Pahaz Blinov`_)
360 + daemon mode by default for all threads (`JM Fernández`_, `Pahaz Blinov`_) - *incompatible*
361 + move ``make_ssh_forward_server`` to ``SSHTunnelForwarder.make_ssh_forward_server`` (`Pahaz Blinov`_, `JM Fernández`_) - *incompatible*
362 + move ``make_ssh_forward_handler`` to ``SSHTunnelForwarder.make_ssh_forward_handler_class`` (`Pahaz Blinov`_, `JM Fernández`_) - *incompatible*
363 + rename ``open`` to ``open_tunnel`` (`JM Fernández`_) - *incompatible*
364 + add CLI interface (`JM Fernández`_)
365 + support opening several tunnels at once (`JM Fernández`_)
366 + improve stability and readability (`JM Fernández`_, `Pahaz Blinov`_)
367 + improve logging (`JM Fernández`_, `Pahaz Blinov`_)
368 + add ``raise_exception_if_any_forwarder_have_a_problem`` argument for opening several tunnels at once (`Pahaz Blinov`_)
369 + add ``ssh_config_file`` argument support (`JM Fernández`_)
370 + add Python 3 support (`JM Fernández`_, `Pahaz Blinov`_)
371
372 - v.0.0.3 (`Pahaz Blinov`_)
373 + add ``threaded`` option (`Cameron Maske`_)
374 + fix exception error message, correctly printing destination address (`Gustavo Machado`_)
375 + fix ``pip install`` failure (`Colin Jermain`_, `Pahaz Blinov`_)
376
377 - v.0.0.1 (`Pahaz Blinov`_)
378 + ``SSHTunnelForwarder`` class (`Pahaz Blinov`_)
379 + ``open`` function (`Pahaz Blinov`_)
380
381
382 .. _Cameron Maske: https://github.com/cameronmaske
383 .. _Gustavo Machado: https://github.com/gdmachado
384 .. _Colin Jermain: https://github.com/cjermain
385 .. _JM Fernández: https://github.com/fernandezcuesta
386 .. _Lewis Thompson: https://github.com/lewisthompson
387 .. _Erik Rogers: https://github.com/ewrogers
388 .. _Mart Sõmermaa: https://github.com/mrts
389 .. _Chronial: https://github.com/Chronial
390 .. _Dan Harbin: https://github.com/RasterBurn
391 .. _Ignacio Peluffo: https://github.com/ipeluffo
392 .. _Niels Zeilemaker: https://github.com/NielsZeilemaker
393 .. _#13: https://github.com/pahaz/sshtunnel/issues/13
394 .. _#16: https://github.com/pahaz/sshtunnel/issues/16
395 .. _#19: https://github.com/pahaz/sshtunnel/issues/19
396 .. _#21: https://github.com/pahaz/sshtunnel/issues/21
397 .. _#24: https://github.com/pahaz/sshtunnel/issues/24
398 .. _#29: https://github.com/pahaz/sshtunnel/issues/29
399 .. _#31: https://github.com/pahaz/sshtunnel/issues/31
400 .. _#33: https://github.com/pahaz/sshtunnel/issues/33
401 .. _#34: https://github.com/pahaz/sshtunnel/issues/34
402 .. _#36: https://github.com/pahaz/sshtunnel/issues/36
403 .. _#41: https://github.com/pahaz/sshtunnel/issues/41
404 .. _#43: https://github.com/pahaz/sshtunnel/issues/43
405 .. _#46: https://github.com/pahaz/sshtunnel/issues/46
406 .. _detail: https://github.com/pahaz/sshtunnel/commit/64af238b799b0e0057c4f9b386cda247e0006da9#diff-76bc1662a114401c2954deb92b740081R127
407
4089 Keywords: ssh tunnel paramiko proxy tcp-forward
40910 Platform: unix
41011 Platform: macos
41920 Classifier: Programming Language :: Python :: 3.4
42021 Classifier: Programming Language :: Python :: 3.5
42122 Classifier: Programming Language :: Python :: 3.6
23 Classifier: Programming Language :: Python :: 3.7
24 Classifier: Programming Language :: Python :: 3.8
25 Description-Content-Type: text/x-rst
26 Provides-Extra: build_sphinx
27 Provides-Extra: dev
28 Provides-Extra: test
29 License-File: LICENSE
30
31 |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
32
33 |pyversions| |license|
34
35 **Author**: `Pahaz`_
36
37 **Repo**: https://github.com/pahaz/sshtunnel/
38
39 Inspired by https://github.com/jmagnusson/bgtunnel, which doesn't work on
40 Windows.
41
42 See also: https://github.com/paramiko/paramiko/blob/master/demos/forward.py
43
44 Requirements
45 -------------
46
47 * `paramiko`_
48
49 Installation
50 ============
51
52 `sshtunnel`_ is on PyPI, so simply run:
53
54 ::
55
56 pip install sshtunnel
57
58 or ::
59
60 easy_install sshtunnel
61
62 or ::
63
64 conda install -c conda-forge sshtunnel
65
66 to have it installed in your environment.
67
68 For installing from source, clone the
69 `repo <https://github.com/pahaz/sshtunnel>`_ and run::
70
71 python setup.py install
72
73 Testing the package
74 -------------------
75
76 In order to run the tests you first need
77 `tox <https://testrun.org/tox/latest/>`_ and run::
78
79 python setup.py test
80
81 Usage scenarios
82 ===============
83
84 One of the typical scenarios where ``sshtunnel`` is helpful is depicted in the
85 figure below. User may need to connect a port of a remote server (i.e. 8080)
86 where only SSH port (usually port 22) is reachable. ::
87
88 ----------------------------------------------------------------------
89
90 |
91 -------------+ | +----------+
92 LOCAL | | | REMOTE | :22 SSH
93 CLIENT | <== SSH ========> | SERVER | :8080 web service
94 -------------+ | +----------+
95 |
96 FIREWALL (only port 22 is open)
97
98 ----------------------------------------------------------------------
99
100 **Fig1**: How to connect to a service blocked by a firewall through SSH tunnel.
101
102
103 If allowed by the SSH server, it is also possible to reach a private server
104 (from the perspective of ``REMOTE SERVER``) not directly visible from the
105 outside (``LOCAL CLIENT``'s perspective). ::
106
107 ----------------------------------------------------------------------
108
109 |
110 -------------+ | +----------+ +---------
111 LOCAL | | | REMOTE | | PRIVATE
112 CLIENT | <== SSH ========> | SERVER | <== local ==> | SERVER
113 -------------+ | +----------+ +---------
114 |
115 FIREWALL (only port 443 is open)
116
117 ----------------------------------------------------------------------
118
119 **Fig2**: How to connect to ``PRIVATE SERVER`` through SSH tunnel.
120
121
122 Usage examples
123 ==============
124
125 API allows either initializing the tunnel and starting it or using a ``with``
126 context, which will take care of starting **and stopping** the tunnel:
127
128 Example 1
129 ---------
130
131 Code corresponding to **Fig1** above follows, given remote server's address is
132 ``pahaz.urfuclub.ru``, password authentication and randomly assigned local bind
133 port.
134
135 .. code-block:: python
136
137 from sshtunnel import SSHTunnelForwarder
138
139 server = SSHTunnelForwarder(
140 'alfa.8iq.dev',
141 ssh_username="pahaz",
142 ssh_password="secret",
143 remote_bind_address=('127.0.0.1', 8080)
144 )
145
146 server.start()
147
148 print(server.local_bind_port) # show assigned local port
149 # work with `SECRET SERVICE` through `server.local_bind_port`.
150
151 server.stop()
152
153 Example 2
154 ---------
155
156 Example of a port forwarding to a private server not directly reachable,
157 assuming password protected pkey authentication, remote server's SSH service is
158 listening on port 443 and that port is open in the firewall (**Fig2**):
159
160 .. code-block:: python
161
162 import paramiko
163 import sshtunnel
164
165 with sshtunnel.open_tunnel(
166 (REMOTE_SERVER_IP, 443),
167 ssh_username="",
168 ssh_pkey="/var/ssh/rsa_key",
169 ssh_private_key_password="secret",
170 remote_bind_address=(PRIVATE_SERVER_IP, 22),
171 local_bind_address=('0.0.0.0', 10022)
172 ) as tunnel:
173 client = paramiko.SSHClient()
174 client.load_system_host_keys()
175 client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
176 client.connect('127.0.0.1', 10022)
177 # do some operations with client session
178 client.close()
179
180 print('FINISH!')
181
182 Example 3
183 ---------
184
185 Example of a port forwarding for the Vagrant MySQL local port:
186
187 .. code-block:: python
188
189 from sshtunnel import open_tunnel
190 from time import sleep
191
192 with open_tunnel(
193 ('localhost', 2222),
194 ssh_username="vagrant",
195 ssh_password="vagrant",
196 remote_bind_address=('127.0.0.1', 3306)
197 ) as server:
198
199 print(server.local_bind_port)
200 while True:
201 # press Ctrl-C for stopping
202 sleep(1)
203
204 print('FINISH!')
205
206 Or simply using the CLI:
207
208 .. code-block:: console
209
210 (bash)$ python -m sshtunnel -U vagrant -P vagrant -L :3306 -R 127.0.0.1:3306 -p 2222 localhost
211
212 Example 4
213 ---------
214
215 Opening an SSH session jumping over two tunnels. SSH transport and tunnels
216 will be daemonised, which will not wait for the connections to stop at close
217 time.
218
219 .. code-block:: python
220
221 import sshtunnel
222 from paramiko import SSHClient
223
224
225 with sshtunnel.open_tunnel(
226 ssh_address_or_host=('GW1_ip', 20022),
227 remote_bind_address=('GW2_ip', 22),
228 ) as tunnel1:
229 print('Connection to tunnel1 (GW1_ip:GW1_port) OK...')
230 with sshtunnel.open_tunnel(
231 ssh_address_or_host=('localhost', tunnel1.local_bind_port),
232 remote_bind_address=('target_ip', 22),
233 ssh_username='GW2_user',
234 ssh_password='GW2_pwd',
235 ) as tunnel2:
236 print('Connection to tunnel2 (GW2_ip:GW2_port) OK...')
237 with SSHClient() as ssh:
238 ssh.connect('localhost',
239 port=tunnel2.local_bind_port,
240 username='target_user',
241 password='target_pwd',
242 )
243 ssh.exec_command(...)
244
245
246 CLI usage
247 =========
248
249 ::
250
251 $ sshtunnel --help
252 usage: sshtunnel [-h] [-U SSH_USERNAME] [-p SSH_PORT] [-P SSH_PASSWORD] -R
253 IP:PORT [IP:PORT ...] [-L [IP:PORT [IP:PORT ...]]]
254 [-k SSH_HOST_KEY] [-K KEY_FILE] [-S KEY_PASSWORD] [-t] [-v]
255 [-V] [-x IP:PORT] [-c SSH_CONFIG_FILE] [-z] [-n]
256 [-d [FOLDER [FOLDER ...]]]
257 ssh_address
258
259 Pure python ssh tunnel utils
260 Version 0.4.0
261
262 positional arguments:
263 ssh_address SSH server IP address (GW for SSH tunnels)
264 set with "-- ssh_address" if immediately after -R or -L
265
266 optional arguments:
267 -h, --help show this help message and exit
268 -U SSH_USERNAME, --username SSH_USERNAME
269 SSH server account username
270 -p SSH_PORT, --server_port SSH_PORT
271 SSH server TCP port (default: 22)
272 -P SSH_PASSWORD, --password SSH_PASSWORD
273 SSH server account password
274 -R IP:PORT [IP:PORT ...], --remote_bind_address IP:PORT [IP:PORT ...]
275 Remote bind address sequence: ip_1:port_1 ip_2:port_2 ... ip_n:port_n
276 Equivalent to ssh -Lxxxx:IP_ADDRESS:PORT
277 If port is omitted, defaults to 22.
278 Example: -R 10.10.10.10: 10.10.10.10:5900
279 -L [IP:PORT [IP:PORT ...]], --local_bind_address [IP:PORT [IP:PORT ...]]
280 Local bind address sequence: ip_1:port_1 ip_2:port_2 ... ip_n:port_n
281 Elements may also be valid UNIX socket domains:
282 /tmp/foo.sock /tmp/bar.sock ... /tmp/baz.sock
283 Equivalent to ssh -LPORT:xxxxxxxxx:xxxx, being the local IP address optional.
284 By default it will listen in all interfaces (0.0.0.0) and choose a random port.
285 Example: -L :40000
286 -k SSH_HOST_KEY, --ssh_host_key SSH_HOST_KEY
287 Gateway's host key
288 -K KEY_FILE, --private_key_file KEY_FILE
289 RSA/DSS/ECDSA private key file
290 -S KEY_PASSWORD, --private_key_password KEY_PASSWORD
291 RSA/DSS/ECDSA private key password
292 -t, --threaded Allow concurrent connections to each tunnel
293 -v, --verbose Increase output verbosity (default: ERROR)
294 -V, --version Show version number and quit
295 -x IP:PORT, --proxy IP:PORT
296 IP and port of SSH proxy to destination
297 -c SSH_CONFIG_FILE, --config SSH_CONFIG_FILE
298 SSH configuration file, defaults to ~/.ssh/config
299 -z, --compress Request server for compression over SSH transport
300 -n, --noagent Disable looking for keys from an SSH agent
301 -d [FOLDER [FOLDER ...]], --host_pkey_directories [FOLDER [FOLDER ...]]
302 List of directories where SSH pkeys (in the format `id_*`) may be found
303
304 .. _Pahaz: https://github.com/pahaz
305 .. _sshtunnel: https://pypi.python.org/pypi/sshtunnel
306 .. _paramiko: http://www.paramiko.org/
307 .. |CircleCI| image:: https://circleci.com/gh/pahaz/sshtunnel.svg?style=svg
308 :target: https://circleci.com/gh/pahaz/sshtunnel
309 .. |AppVeyor| image:: https://ci.appveyor.com/api/projects/status/oxg1vx2ycmnw3xr9?svg=true&passingText=Windows%20-%20OK&failingText=Windows%20-%20Fail
310 :target: https://ci.appveyor.com/project/pahaz/sshtunnel
311 .. |readthedocs| image:: https://readthedocs.org/projects/sshtunnel/badge/?version=latest
312 :target: http://sshtunnel.readthedocs.io/en/latest/?badge=latest
313 :alt: Documentation Status
314 .. |coveralls| image:: https://coveralls.io/repos/github/pahaz/sshtunnel/badge.svg?branch=master
315 :target: https://coveralls.io/github/pahaz/sshtunnel?branch=master
316 .. |pyversions| image:: https://img.shields.io/pypi/pyversions/sshtunnel.svg
317 .. |version| image:: https://img.shields.io/pypi/v/sshtunnel.svg
318 :target: `sshtunnel`_
319 .. |license| image:: https://img.shields.io/pypi/l/sshtunnel.svg
320 :target: https://github.com/pahaz/sshtunnel/blob/master/LICENSE
321
322 Online documentation
323 ====================
324
325 Documentation may be found at `readthedocs`_.
326
327 .. _readthedocs: https://sshtunnel.readthedocs.org/
328
329 CONTRIBUTORS
330 ============
331
332 - `Cameron Maske`_
333 - `Gustavo Machado`_
334 - `Colin Jermain`_
335 - `JM Fernández`_ - (big thanks!)
336 - `Lewis Thompson`_
337 - `Erik Rogers`_
338 - `Mart Sõmermaa`_
339 - `Chronial`_
340 - `Dan Harbin`_
341 - `Ignacio Peluffo`_
342 - `Niels Zeilemaker`_
343 - `Georgy Rylov`_
344 - `Eddie Chiang`_
345 - `kkrasovskii`_
346
347 CHANGELOG
348 =========
349
350 - v.0.4.0 (`Pahaz`_)
351 + Change the daemon mod flag for all tunnel threads (is not fully backward compatible) to prevent unexpected hangs (`#219`_)
352 + Add docker based end to end functinal tests for Mongo/Postgres/MySQL (`#219`_)
353 + Add docker based end to end hangs tests (`#219`_)
354
355 - v.0.3.2 (`Pahaz`_, `JM Fernández`_)
356 + Fix host key directory detection
357 + Unify default ssh config folder to `~/.ssh`
358
359 - v.0.3.1 (`Pahaz`_)
360 + Increase open connection timeout to 10 secods
361
362 - v.0.3.0 (`Pahaz`_)
363 + Change default with context behavior to use `.stop(force=True)` on exit (is not fully backward compatible)
364 + Remove useless `daemon_forward_servers = True` hack for hangs prevention (is not fully backward compatible)
365 + Set transport keepalive to 5 second by default (disabled for version < 0.3.0)
366 + Set default transport timeout to 0.1
367 + Deprecate and remove `block_on_close` option
368 + Fix "deadlocks" / "tunneling hangs" (`#173`_, `#201`_, `#162`_, `#211`_)
369
370 - v.0.2.2 (`Pahaz`_)
371 + Add `.stop(force=True)` for force close active connections (`#201`_)
372
373 - v.0.2.1 (`Pahaz`_, `Eddie Chiang`_ and `kkrasovskii`_)
374 + Fixes bug with orphan thread for a tunnel that is DOWN (`#170`_)
375
376 - v.0.2.0 (`Georgy Rylov`_)
377 + Support IPv6 without proxy command. Use built-in paramiko create socket logic. The logic tries to use ipv6 socket family first, then ipv4 socket family.
378
379 - v.0.1.5 (`JM Fernández`_)
380 + Introduce `block_on_close` attribute
381
382 - v.0.1.4 (`Niels Zeilemaker`_)
383 + Allow loading pkeys from `~/.ssh`
384
385 - v.0.1.3 (`Ignacio Peluffo`_ and others)
386 + ``pkey_file`` parameter updated to accept relative paths to user folder using ``~``
387 + Several bugfixes
388
389 - v.0.1.2 (`JM Fernández`_)
390 + Fix #77
391
392 - v.0.1.1 (`JM Fernández`_)
393 + Fix #72
394
395 - v.0.1.0 (`JM Fernández`_)
396 + Add `tunnel_bindings` property
397 + Several bugfixes (#49, #56, #57, #59, #60, #62, #64, #66, ...)
398 (`Pahaz`_, `JM Fernández`_)
399 + Add TRACE logging level (`JM Fernández`_)
400 + Code and tests refactoring (`JM Fernández`_)
401 + Drop python3.2 support
402
403 - v.0.0.8 (`JM Fernández`_)
404 + Merge `#31`_: Support Unix domain socket (local) forwarding (`Dan Harbin`_)
405 + Simplify API (`JM Fernández`_)
406 + Add sphinx-based documentation (`JM Fernández`_)
407 + Add ``allow_agent`` (fixes `#36`_, `#46`_) (`JM Fernández`_)
408 + Add ``compression`` (`JM Fernández`_)
409 + Add ``__str__`` method (`JM Fernández`_)
410 + Add test functions (`JM Fernández`_)
411 + Fix default username when not provided and ssh_config file is skipped (`JM Fernández`_)
412 + Fix gateway IP unresolvable exception catching (`JM Fernández`_)
413 + Minor fixes (`JM Fernández`_)
414 + Add AppVeyor support (`JM Fernández`_)
415
416 - v.0.0.7 (`JM Fernández`_)
417 + Tunnels can now be stopped and started safely (`#41`_) (`JM Fernández`_)
418 + Add timeout to SSH gateway and keep-alive messages (`#29`_) (`JM Fernández`_)
419 + Allow sending a pkey directly (`#43`_) (`Chronial`_)
420 + Add ``-V`` CLI option to show current version (`JM Fernández`_)
421 + Add coverage (`JM Fernández`_)
422 + Refactoring (`JM Fernández`_)
423
424 - v.0.0.6 (`Pahaz`_)
425 + add ``-S`` CLI options for ssh private key password support (`Pahaz`_)
426
427 - v.0.0.5 (`Pahaz`_)
428 + add ``ssh_proxy`` argument, as well as ``ssh_config(5)`` ``ProxyCommand`` support (`Lewis Thompson`_)
429 + add some python 2.6 compatibility fixes (`Mart Sõmermaa`_)
430 + ``paramiko.transport`` inherits handlers of loggers passed to ``SSHTunnelForwarder`` (`JM Fernández`_)
431 + fix `#34`_, `#33`_, code style and docs (`JM Fernández`_)
432 + add tests (`Pahaz`_)
433 + add CI integration (`Pahaz`_)
434 + normal packaging (`Pahaz`_)
435 + disable check distenation socket connection by ``SSHTunnelForwarder.local_is_up`` (`Pahaz`_) [changed default behavior]
436 + use daemon mode = False in all threads by default; detail_ (`Pahaz`_) [changed default behavior]
437
438 - v.0.0.4.4 (`Pahaz`_)
439 + fix issue `#24`_ - hide ssh password in logs (`Pahaz`_)
440
441 - v.0.0.4.3 (`Pahaz`_)
442 + fix default port issue `#19`_ (`Pahaz`_)
443
444 - v.0.0.4.2 (`Pahaz`_)
445 + fix Thread.daemon mode for Python < 3.3 `#16`_, `#21`_ (`Lewis Thompson`_, `Erik Rogers`_)
446
447 - v.0.0.4.1 (`Pahaz`_)
448 + fix CLI issues `#13`_ (`Pahaz`_)
449
450 - v.0.0.4 (`Pahaz`_)
451 + daemon mode by default for all threads (`JM Fernández`_, `Pahaz`_) - *incompatible*
452 + move ``make_ssh_forward_server`` to ``SSHTunnelForwarder.make_ssh_forward_server`` (`Pahaz`_, `JM Fernández`_) - *incompatible*
453 + move ``make_ssh_forward_handler`` to ``SSHTunnelForwarder.make_ssh_forward_handler_class`` (`Pahaz`_, `JM Fernández`_) - *incompatible*
454 + rename ``open`` to ``open_tunnel`` (`JM Fernández`_) - *incompatible*
455 + add CLI interface (`JM Fernández`_)
456 + support opening several tunnels at once (`JM Fernández`_)
457 + improve stability and readability (`JM Fernández`_, `Pahaz`_)
458 + improve logging (`JM Fernández`_, `Pahaz`_)
459 + add ``raise_exception_if_any_forwarder_have_a_problem`` argument for opening several tunnels at once (`Pahaz`_)
460 + add ``ssh_config_file`` argument support (`JM Fernández`_)
461 + add Python 3 support (`JM Fernández`_, `Pahaz`_)
462
463 - v.0.0.3 (`Pahaz`_)
464 + add ``threaded`` option (`Cameron Maske`_)
465 + fix exception error message, correctly printing destination address (`Gustavo Machado`_)
466 + fix ``pip install`` failure (`Colin Jermain`_, `Pahaz`_)
467
468 - v.0.0.1 (`Pahaz`_)
469 + ``SSHTunnelForwarder`` class (`Pahaz`_)
470 + ``open`` function (`Pahaz`_)
471
472
473 .. _Pahaz: https://github.com/pahaz
474 .. _Cameron Maske: https://github.com/cameronmaske
475 .. _Gustavo Machado: https://github.com/gdmachado
476 .. _Colin Jermain: https://github.com/cjermain
477 .. _JM Fernández: https://github.com/fernandezcuesta
478 .. _Lewis Thompson: https://github.com/lewisthompson
479 .. _Erik Rogers: https://github.com/ewrogers
480 .. _Mart Sõmermaa: https://github.com/mrts
481 .. _Chronial: https://github.com/Chronial
482 .. _Dan Harbin: https://github.com/RasterBurn
483 .. _Ignacio Peluffo: https://github.com/ipeluffo
484 .. _Niels Zeilemaker: https://github.com/NielsZeilemaker
485 .. _Georgy Rylov: https://github.com/g0djan
486 .. _Eddie Chiang: https://github.com/eddie-chiang
487 .. _kkrasovskii: https://github.com/kkrasovskii
488 .. _#13: https://github.com/pahaz/sshtunnel/issues/13
489 .. _#16: https://github.com/pahaz/sshtunnel/issues/16
490 .. _#19: https://github.com/pahaz/sshtunnel/issues/19
491 .. _#21: https://github.com/pahaz/sshtunnel/issues/21
492 .. _#24: https://github.com/pahaz/sshtunnel/issues/24
493 .. _#29: https://github.com/pahaz/sshtunnel/issues/29
494 .. _#31: https://github.com/pahaz/sshtunnel/issues/31
495 .. _#33: https://github.com/pahaz/sshtunnel/issues/33
496 .. _#34: https://github.com/pahaz/sshtunnel/issues/34
497 .. _#36: https://github.com/pahaz/sshtunnel/issues/36
498 .. _#41: https://github.com/pahaz/sshtunnel/issues/41
499 .. _#43: https://github.com/pahaz/sshtunnel/issues/43
500 .. _#46: https://github.com/pahaz/sshtunnel/issues/46
501 .. _#170: https://github.com/pahaz/sshtunnel/issues/170
502 .. _#201: https://github.com/pahaz/sshtunnel/issues/201
503 .. _#162: https://github.com/pahaz/sshtunnel/issues/162
504 .. _#173: https://github.com/pahaz/sshtunnel/issues/173
505 .. _#201: https://github.com/pahaz/sshtunnel/issues/201
506 .. _#211: https://github.com/pahaz/sshtunnel/issues/211
507 .. _#219: https://github.com/pahaz/sshtunnel/issues/219
508 .. _detail: https://github.com/pahaz/sshtunnel/commit/64af238b799b0e0057c4f9b386cda247e0006da9#diff-76bc1662a114401c2954deb92b740081R127
11
22 |pyversions| |license|
33
4 **Author**: `Pahaz Blinov`_
4 **Author**: `Pahaz`_
55
66 **Repo**: https://github.com/pahaz/sshtunnel/
77
8 Inspired by https://github.com/jmagnusson/bgtunnel, but it doesn't work on
8 Inspired by https://github.com/jmagnusson/bgtunnel, which doesn't work on
99 Windows.
1010
1111 See also: https://github.com/paramiko/paramiko/blob/master/demos/forward.py
101101 ``pahaz.urfuclub.ru``, password authentication and randomly assigned local bind
102102 port.
103103
104 .. code-block:: py
104 .. code-block:: python
105105
106106 from sshtunnel import SSHTunnelForwarder
107107
108108 server = SSHTunnelForwarder(
109 'pahaz.urfuclub.ru',
109 'alfa.8iq.dev',
110110 ssh_username="pahaz",
111111 ssh_password="secret",
112112 remote_bind_address=('127.0.0.1', 8080)
126126 assuming password protected pkey authentication, remote server's SSH service is
127127 listening on port 443 and that port is open in the firewall (**Fig2**):
128128
129 .. code-block:: py
129 .. code-block:: python
130130
131131 import paramiko
132 from sshtunnel import SSHTunnelForwarder
133
134 with SSHTunnelForwarder(
132 import sshtunnel
133
134 with sshtunnel.open_tunnel(
135135 (REMOTE_SERVER_IP, 443),
136136 ssh_username="",
137137 ssh_pkey="/var/ssh/rsa_key",
153153
154154 Example of a port forwarding for the Vagrant MySQL local port:
155155
156 .. code-block:: py
157
158 from sshtunnel import SSHTunnelForwarder
156 .. code-block:: python
157
158 from sshtunnel import open_tunnel
159159 from time import sleep
160160
161 with SSHTunnelForwarder(
161 with open_tunnel(
162162 ('localhost', 2222),
163163 ssh_username="vagrant",
164164 ssh_password="vagrant",
178178
179179 (bash)$ python -m sshtunnel -U vagrant -P vagrant -L :3306 -R 127.0.0.1:3306 -p 2222 localhost
180180
181 Example 4
182 ---------
183
184 Opening an SSH session jumping over two tunnels. SSH transport and tunnels
185 will be daemonised, which will not wait for the connections to stop at close
186 time.
187
188 .. code-block:: python
189
190 import sshtunnel
191 from paramiko import SSHClient
192
193
194 with sshtunnel.open_tunnel(
195 ssh_address_or_host=('GW1_ip', 20022),
196 remote_bind_address=('GW2_ip', 22),
197 ) as tunnel1:
198 print('Connection to tunnel1 (GW1_ip:GW1_port) OK...')
199 with sshtunnel.open_tunnel(
200 ssh_address_or_host=('localhost', tunnel1.local_bind_port),
201 remote_bind_address=('target_ip', 22),
202 ssh_username='GW2_user',
203 ssh_password='GW2_pwd',
204 ) as tunnel2:
205 print('Connection to tunnel2 (GW2_ip:GW2_port) OK...')
206 with SSHClient() as ssh:
207 ssh.connect('localhost',
208 port=tunnel2.local_bind_port,
209 username='target_user',
210 password='target_pwd',
211 )
212 ssh.exec_command(...)
213
214
181215 CLI usage
182216 =========
183217
187221 usage: sshtunnel [-h] [-U SSH_USERNAME] [-p SSH_PORT] [-P SSH_PASSWORD] -R
188222 IP:PORT [IP:PORT ...] [-L [IP:PORT [IP:PORT ...]]]
189223 [-k SSH_HOST_KEY] [-K KEY_FILE] [-S KEY_PASSWORD] [-t] [-v]
190 [-V] [-x IP:PORT] [-c SSH_CONFIG_FILE] [-z] [-n] [-d [FOLDER [FOLDER ...]]]
224 [-V] [-x IP:PORT] [-c SSH_CONFIG_FILE] [-z] [-n]
225 [-d [FOLDER [FOLDER ...]]]
191226 ssh_address
192227
193228 Pure python ssh tunnel utils
194 Version 0.1.4
229 Version 0.4.0
195230
196231 positional arguments:
197232 ssh_address SSH server IP address (GW for SSH tunnels)
235270 -d [FOLDER [FOLDER ...]], --host_pkey_directories [FOLDER [FOLDER ...]]
236271 List of directories where SSH pkeys (in the format `id_*`) may be found
237272
238 .. _Pahaz Blinov: https://github.com/pahaz
273 .. _Pahaz: https://github.com/pahaz
239274 .. _sshtunnel: https://pypi.python.org/pypi/sshtunnel
240275 .. _paramiko: http://www.paramiko.org/
241276 .. |CircleCI| image:: https://circleci.com/gh/pahaz/sshtunnel.svg?style=svg
1111 - `Dan Harbin`_
1212 - `Ignacio Peluffo`_
1313 - `Niels Zeilemaker`_
14 - `Georgy Rylov`_
15 - `Eddie Chiang`_
16 - `kkrasovskii`_
1417
1518 CHANGELOG
1619 =========
20
21 - v.0.4.0 (`Pahaz`_)
22 + Change the daemon mod flag for all tunnel threads (is not fully backward compatible) to prevent unexpected hangs (`#219`_)
23 + Add docker based end to end functinal tests for Mongo/Postgres/MySQL (`#219`_)
24 + Add docker based end to end hangs tests (`#219`_)
25
26 - v.0.3.2 (`Pahaz`_, `JM Fernández`_)
27 + Fix host key directory detection
28 + Unify default ssh config folder to `~/.ssh`
29
30 - v.0.3.1 (`Pahaz`_)
31 + Increase open connection timeout to 10 secods
32
33 - v.0.3.0 (`Pahaz`_)
34 + Change default with context behavior to use `.stop(force=True)` on exit (is not fully backward compatible)
35 + Remove useless `daemon_forward_servers = True` hack for hangs prevention (is not fully backward compatible)
36 + Set transport keepalive to 5 second by default (disabled for version < 0.3.0)
37 + Set default transport timeout to 0.1
38 + Deprecate and remove `block_on_close` option
39 + Fix "deadlocks" / "tunneling hangs" (`#173`_, `#201`_, `#162`_, `#211`_)
40
41 - v.0.2.2 (`Pahaz`_)
42 + Add `.stop(force=True)` for force close active connections (`#201`_)
43
44 - v.0.2.1 (`Pahaz`_, `Eddie Chiang`_ and `kkrasovskii`_)
45 + Fixes bug with orphan thread for a tunnel that is DOWN (`#170`_)
46
47 - v.0.2.0 (`Georgy Rylov`_)
48 + Support IPv6 without proxy command. Use built-in paramiko create socket logic. The logic tries to use ipv6 socket family first, then ipv4 socket family.
49
50 - v.0.1.5 (`JM Fernández`_)
51 + Introduce `block_on_close` attribute
1752
1853 - v.0.1.4 (`Niels Zeilemaker`_)
1954 + Allow loading pkeys from `~/.ssh`
3166 - v.0.1.0 (`JM Fernández`_)
3267 + Add `tunnel_bindings` property
3368 + Several bugfixes (#49, #56, #57, #59, #60, #62, #64, #66, ...)
34 (`Pahaz Blinov`_, `JM Fernández`_)
69 (`Pahaz`_, `JM Fernández`_)
3570 + Add TRACE logging level (`JM Fernández`_)
3671 + Code and tests refactoring (`JM Fernández`_)
3772 + Drop python3.2 support
5792 + Add coverage (`JM Fernández`_)
5893 + Refactoring (`JM Fernández`_)
5994
60 - v.0.0.6 (`Pahaz Blinov`_)
61 + add ``-S`` CLI options for ssh private key password support (`Pahaz Blinov`_)
95 - v.0.0.6 (`Pahaz`_)
96 + add ``-S`` CLI options for ssh private key password support (`Pahaz`_)
6297
63 - v.0.0.5 (`Pahaz Blinov`_)
98 - v.0.0.5 (`Pahaz`_)
6499 + add ``ssh_proxy`` argument, as well as ``ssh_config(5)`` ``ProxyCommand`` support (`Lewis Thompson`_)
65100 + add some python 2.6 compatibility fixes (`Mart Sõmermaa`_)
66101 + ``paramiko.transport`` inherits handlers of loggers passed to ``SSHTunnelForwarder`` (`JM Fernández`_)
67102 + fix `#34`_, `#33`_, code style and docs (`JM Fernández`_)
68 + add tests (`Pahaz Blinov`_)
69 + add CI integration (`Pahaz Blinov`_)
70 + normal packaging (`Pahaz Blinov`_)
71 + disable check distenation socket connection by ``SSHTunnelForwarder.local_is_up`` (`Pahaz Blinov`_) [changed default behavior]
72 + use daemon mode = False in all threads by default; detail_ (`Pahaz Blinov`_) [changed default behavior]
103 + add tests (`Pahaz`_)
104 + add CI integration (`Pahaz`_)
105 + normal packaging (`Pahaz`_)
106 + disable check distenation socket connection by ``SSHTunnelForwarder.local_is_up`` (`Pahaz`_) [changed default behavior]
107 + use daemon mode = False in all threads by default; detail_ (`Pahaz`_) [changed default behavior]
73108
74 - v.0.0.4.4 (`Pahaz Blinov`_)
75 + fix issue `#24`_ - hide ssh password in logs (`Pahaz Blinov`_)
109 - v.0.0.4.4 (`Pahaz`_)
110 + fix issue `#24`_ - hide ssh password in logs (`Pahaz`_)
76111
77 - v.0.0.4.3 (`Pahaz Blinov`_)
78 + fix default port issue `#19`_ (`Pahaz Blinov`_)
112 - v.0.0.4.3 (`Pahaz`_)
113 + fix default port issue `#19`_ (`Pahaz`_)
79114
80 - v.0.0.4.2 (`Pahaz Blinov`_)
115 - v.0.0.4.2 (`Pahaz`_)
81116 + fix Thread.daemon mode for Python < 3.3 `#16`_, `#21`_ (`Lewis Thompson`_, `Erik Rogers`_)
82117
83 - v.0.0.4.1 (`Pahaz Blinov`_)
84 + fix CLI issues `#13`_ (`Pahaz Blinov`_)
118 - v.0.0.4.1 (`Pahaz`_)
119 + fix CLI issues `#13`_ (`Pahaz`_)
85120
86 - v.0.0.4 (`Pahaz Blinov`_)
87 + daemon mode by default for all threads (`JM Fernández`_, `Pahaz Blinov`_) - *incompatible*
88 + move ``make_ssh_forward_server`` to ``SSHTunnelForwarder.make_ssh_forward_server`` (`Pahaz Blinov`_, `JM Fernández`_) - *incompatible*
89 + move ``make_ssh_forward_handler`` to ``SSHTunnelForwarder.make_ssh_forward_handler_class`` (`Pahaz Blinov`_, `JM Fernández`_) - *incompatible*
121 - v.0.0.4 (`Pahaz`_)
122 + daemon mode by default for all threads (`JM Fernández`_, `Pahaz`_) - *incompatible*
123 + move ``make_ssh_forward_server`` to ``SSHTunnelForwarder.make_ssh_forward_server`` (`Pahaz`_, `JM Fernández`_) - *incompatible*
124 + move ``make_ssh_forward_handler`` to ``SSHTunnelForwarder.make_ssh_forward_handler_class`` (`Pahaz`_, `JM Fernández`_) - *incompatible*
90125 + rename ``open`` to ``open_tunnel`` (`JM Fernández`_) - *incompatible*
91126 + add CLI interface (`JM Fernández`_)
92127 + support opening several tunnels at once (`JM Fernández`_)
93 + improve stability and readability (`JM Fernández`_, `Pahaz Blinov`_)
94 + improve logging (`JM Fernández`_, `Pahaz Blinov`_)
95 + add ``raise_exception_if_any_forwarder_have_a_problem`` argument for opening several tunnels at once (`Pahaz Blinov`_)
128 + improve stability and readability (`JM Fernández`_, `Pahaz`_)
129 + improve logging (`JM Fernández`_, `Pahaz`_)
130 + add ``raise_exception_if_any_forwarder_have_a_problem`` argument for opening several tunnels at once (`Pahaz`_)
96131 + add ``ssh_config_file`` argument support (`JM Fernández`_)
97 + add Python 3 support (`JM Fernández`_, `Pahaz Blinov`_)
132 + add Python 3 support (`JM Fernández`_, `Pahaz`_)
98133
99 - v.0.0.3 (`Pahaz Blinov`_)
134 - v.0.0.3 (`Pahaz`_)
100135 + add ``threaded`` option (`Cameron Maske`_)
101136 + fix exception error message, correctly printing destination address (`Gustavo Machado`_)
102 + fix ``pip install`` failure (`Colin Jermain`_, `Pahaz Blinov`_)
137 + fix ``pip install`` failure (`Colin Jermain`_, `Pahaz`_)
103138
104 - v.0.0.1 (`Pahaz Blinov`_)
105 + ``SSHTunnelForwarder`` class (`Pahaz Blinov`_)
106 + ``open`` function (`Pahaz Blinov`_)
139 - v.0.0.1 (`Pahaz`_)
140 + ``SSHTunnelForwarder`` class (`Pahaz`_)
141 + ``open`` function (`Pahaz`_)
107142
108143
144 .. _Pahaz: https://github.com/pahaz
109145 .. _Cameron Maske: https://github.com/cameronmaske
110146 .. _Gustavo Machado: https://github.com/gdmachado
111147 .. _Colin Jermain: https://github.com/cjermain
117153 .. _Dan Harbin: https://github.com/RasterBurn
118154 .. _Ignacio Peluffo: https://github.com/ipeluffo
119155 .. _Niels Zeilemaker: https://github.com/NielsZeilemaker
156 .. _Georgy Rylov: https://github.com/g0djan
157 .. _Eddie Chiang: https://github.com/eddie-chiang
158 .. _kkrasovskii: https://github.com/kkrasovskii
120159 .. _#13: https://github.com/pahaz/sshtunnel/issues/13
121160 .. _#16: https://github.com/pahaz/sshtunnel/issues/16
122161 .. _#19: https://github.com/pahaz/sshtunnel/issues/19
130169 .. _#41: https://github.com/pahaz/sshtunnel/issues/41
131170 .. _#43: https://github.com/pahaz/sshtunnel/issues/43
132171 .. _#46: https://github.com/pahaz/sshtunnel/issues/46
172 .. _#170: https://github.com/pahaz/sshtunnel/issues/170
173 .. _#201: https://github.com/pahaz/sshtunnel/issues/201
174 .. _#162: https://github.com/pahaz/sshtunnel/issues/162
175 .. _#173: https://github.com/pahaz/sshtunnel/issues/173
176 .. _#201: https://github.com/pahaz/sshtunnel/issues/201
177 .. _#211: https://github.com/pahaz/sshtunnel/issues/211
178 .. _#219: https://github.com/pahaz/sshtunnel/issues/219
133179 .. _detail: https://github.com/pahaz/sshtunnel/commit/64af238b799b0e0057c4f9b386cda247e0006da9#diff-76bc1662a114401c2954deb92b740081R127
0 sshtunnel (0.1.4-4) UNRELEASED; urgency=medium
0 sshtunnel (0.4.0+git20221028.1.d2167a2-1) UNRELEASED; urgency=medium
11
22 * Bump debhelper from old 12 to 13.
33 * Update standards version to 4.1.5, no changes needed.
4 * New upstream snapshot.
45
5 -- Debian Janitor <janitor@jelmer.uk> Fri, 06 Jan 2023 09:34:08 -0000
6 -- Debian Janitor <janitor@jelmer.uk> Fri, 06 Jan 2023 15:36:11 -0000
67
78 sshtunnel (0.1.4-3) unstable; urgency=medium
89
1414
1515 import sys
1616 import os
17
18 import sshtunnel
1719
1820 # Patch to disable warning on non-local image
1921 import sphinx.environment
6062
6163 # General information about the project.
6264 project = 'sshtunnel'
63 copyright = '2014-2016, Pahaz Blinov and contributors'
64 author = 'Pahaz Blinov'
65 copyright = '2014-2020, Pahaz White and contributors'
66 author = 'Pahaz White'
6567
6668 # The version info for the project you're documenting, acts as replacement for
6769 # |version| and |release|, also used in various other places throughout the
6870 # built documents.
6971 #
7072 # The short X.Y version.
71 version = '0.0.8'
73 version = sshtunnel.__version__
7274 # The full version, including alpha/beta/rc tags.
73 release = '0.0.8'
75 release = sshtunnel.__version__
7476
7577 # The language for content autogenerated by Sphinx. Refer to documentation
7678 # for a list of supported languages.
66
77 API
88 ===
9
10 .. toctree::
11 :maxdepth: 3
129
1310 .. automodule:: sshtunnel
1411 :members:
2017 License
2118 =======
2219
23 .. include:: ../LICENSE
20 .. include:: ../LICENSE
+0
-3
docs/requirements-docs.txt less more
0 docutils==0.12
1 sphinx==1.3.5
2 sphinxcontrib-napoleon==0.5.0
0 docutils
1 sphinx
2 sphinxcontrib-napoleon
0 ---
1 version: "2.1"
2 services:
3 ssh:
4 image: ghcr.io/linuxserver/openssh-server
5 container_name: openssh-server
6 hostname: openssh-server
7 environment:
8 - PUID=1000
9 - PGID=1000
10 - TZ=Europe/London
11 - PUBLIC_KEY_FILE=/config/ssh_host_keys/ssh_host_rsa_key.pub
12 - SUDO_ACCESS=false
13 - PASSWORD_ACCESS=false
14 - USER_NAME=linuxserver
15 volumes:
16 - ./ssh-server-config:/config/ssh_host_keys:ro
17 ports:
18 - "127.0.0.1:2223:2222"
19 networks:
20 - inner
21
22 postgresdb:
23 image: postgres:13.0
24 environment:
25 POSTGRES_USER: postgres
26 POSTGRES_PASSWORD: postgres
27 POSTGRES_DB: main
28 networks:
29 inner:
30 ipv4_address: 10.5.0.5
31
32 mysqldb:
33 image: mysql:8
34 environment:
35 MYSQL_DATABASE: main
36 MYSQL_USER: mysql
37 MYSQL_PASSWORD: mysql
38 MYSQL_ROOT_PASSWORD: mysqlroot
39 networks:
40 inner:
41 ipv4_address: 10.5.0.6
42
43 mongodb:
44 image: mongo:3.6
45 environment:
46 MONGO_INITDB_ROOT_USERNAME: mongo
47 MONGO_INITDB_ROOT_PASSWORD: mongo
48 MONGO_INITDB_DATABASE: main
49 networks:
50 inner:
51 ipv4_address: 10.5.0.7
52
53 networks:
54 inner:
55 driver: bridge
56 ipam:
57 config:
58 - subnet: 10.5.0.0/16
59 gateway: 10.5.0.1
0 import select
1 import traceback
2 import sys
3 import os
4 import time
5 from sshtunnel import SSHTunnelForwarder
6 import sshtunnel
7 import logging
8 import threading
9 import paramiko
10
11 sshtunnel.DEFAULT_LOGLEVEL = 1
12 logging.basicConfig(
13 format='%(asctime)s| %(levelname)-4.3s|%(threadName)10.9s/%(lineno)04d@%(module)-10.9s| %(message)s', level=1)
14
15 SSH_SERVER_ADDRESS = ('127.0.0.1', 2223)
16 SSH_SERVER_USERNAME = 'linuxserver'
17 SSH_PKEY = os.path.join(os.path.dirname(__file__), 'ssh-server-config', 'ssh_host_rsa_key')
18 SSH_SERVER_REMOTE_SIDE_ADDRESS_PG = ('10.5.0.5', 5432)
19 SSH_SERVER_REMOTE_SIDE_ADDRESS_MYSQL = ('10.5.0.6', 3306)
20 SSH_SERVER_REMOTE_SIDE_ADDRESS_MONGO = ('10.5.0.7', 27017)
21
22 PG_DATABASE_NAME = 'main'
23 PG_USERNAME = 'postgres'
24 PG_PASSWORD = 'postgres'
25 PG_QUERY = 'select version()'
26 PG_EXPECT = eval(
27 """('PostgreSQL 13.0 (Debian 13.0-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit',)""")
28
29 MYSQL_DATABASE_NAME = 'main'
30 MYSQL_USERNAME = 'mysql'
31 MYSQL_PASSWORD = 'mysql'
32 MYSQL_QUERY = 'select version()'
33 MYSQL_EXPECT = (('8.0.22',),)
34
35 MONGO_DATABASE_NAME = 'main'
36 MONGO_USERNAME = 'mongo'
37 MONGO_PASSWORD = 'mongo'
38 MONGO_QUERY = lambda client, db: client.server_info()
39 MONGO_EXPECT = eval(
40 """{'version': '3.6.21', 'gitVersion': '1cd2db51dce4b16f4bc97a75056269df0dc0bddb', 'modules': [], 'allocator': 'tcmalloc', 'javascriptEngine': 'mozjs', 'sysInfo': 'deprecated', 'versionArray': [3, 6, 21, 0], 'openssl': {'running': 'OpenSSL 1.0.2g 1 Mar 2016', 'compiled': 'OpenSSL 1.0.2g 1 Mar 2016'}, 'buildEnvironment': {'distmod': 'ubuntu1604', 'distarch': 'x86_64', 'cc': '/opt/mongodbtoolchain/v2/bin/gcc: gcc (GCC) 5.4.0', 'ccflags': '-fno-omit-frame-pointer -fno-strict-aliasing -ggdb -pthread -Wall -Wsign-compare -Wno-unknown-pragmas -Winvalid-pch -Werror -O2 -Wno-unused-local-typedefs -Wno-unused-function -Wno-deprecated-declarations -Wno-unused-but-set-variable -Wno-missing-braces -fstack-protector-strong -fno-builtin-memcmp', 'cxx': '/opt/mongodbtoolchain/v2/bin/g++: g++ (GCC) 5.4.0', 'cxxflags': '-Woverloaded-virtual -Wno-maybe-uninitialized -std=c++14', 'linkflags': '-pthread -Wl,-z,now -rdynamic -Wl,--fatal-warnings -fstack-protector-strong -fuse-ld=gold -Wl,--build-id -Wl,--hash-style=gnu -Wl,-z,noexecstack -Wl,--warn-execstack -Wl,-z,relro', 'target_arch': 'x86_64', 'target_os': 'linux'}, 'bits': 64, 'debug': False, 'maxBsonObjectSize': 16777216, 'storageEngines': ['devnull', 'ephemeralForTest', 'mmapv1', 'wiredTiger'], 'ok': 1.0}""")
41
42
43 def run_postgres_query(port, query=PG_QUERY):
44 import psycopg2
45
46 ASYNC_OK = 1
47 ASYNC_READ_TIMEOUT = 2
48 ASYNC_WRITE_TIMEOUT = 3
49 ASYNC_TIMEOUT = 0.2
50
51 def wait(conn):
52 while 1:
53 state = conn.poll()
54 if state == psycopg2.extensions.POLL_OK:
55 break
56 elif state == psycopg2.extensions.POLL_WRITE:
57 select.select([], [conn.fileno()], [])
58 elif state == psycopg2.extensions.POLL_READ:
59 select.select([conn.fileno()], [], [])
60 else:
61 raise psycopg2.OperationalError(
62 "poll() returned %s from _wait function" % state)
63
64 def wait_timeout(conn):
65 while 1:
66 state = conn.poll()
67 if state == psycopg2.extensions.POLL_OK:
68 return ASYNC_OK
69 elif state == psycopg2.extensions.POLL_WRITE:
70 # Wait for the given time and then check the return status
71 # If three empty lists are returned then the time-out is
72 # reached.
73 timeout_status = select.select(
74 [], [conn.fileno()], [], ASYNC_TIMEOUT
75 )
76 if timeout_status == ([], [], []):
77 return ASYNC_WRITE_TIMEOUT
78 elif state == psycopg2.extensions.POLL_READ:
79 # Wait for the given time and then check the return status
80 # If three empty lists are returned then the time-out is
81 # reached.
82 timeout_status = select.select(
83 [conn.fileno()], [], [], ASYNC_TIMEOUT
84 )
85 if timeout_status == ([], [], []):
86 return ASYNC_READ_TIMEOUT
87 else:
88 raise psycopg2.OperationalError(
89 "poll() returned %s from _wait_timeout function" % state
90 )
91
92 pg_conn = psycopg2.connect(
93 host='127.0.0.1',
94 hostaddr='127.0.0.1',
95 port=port,
96 database=PG_DATABASE_NAME,
97 user=PG_USERNAME,
98 password=PG_PASSWORD,
99 sslmode='disable',
100 async_=1
101 )
102 wait(pg_conn)
103 cur = pg_conn.cursor()
104 cur.execute(query)
105 res = wait_timeout(cur.connection)
106 while res != ASYNC_OK:
107 res = wait_timeout(cur.connection)
108 return cur.fetchone()
109
110
111 def run_mysql_query(port, query=MYSQL_QUERY):
112 import pymysql
113 conn = pymysql.connect(
114 host='127.0.0.1',
115 port=port,
116 user=MYSQL_USERNAME,
117 password=MYSQL_PASSWORD,
118 database=MYSQL_DATABASE_NAME,
119 connect_timeout=5,
120 read_timeout=5)
121 cursor = conn.cursor()
122 cursor.execute(query)
123 return cursor.fetchall()
124
125
126 def run_mongo_query(port, query=MONGO_QUERY):
127 import pymongo
128 client = pymongo.MongoClient('127.0.0.1', port)
129 db = client[MONGO_DATABASE_NAME]
130 return query(client, db)
131
132
133 def create_tunnel():
134 logging.info('Creating SSHTunnelForwarder... (sshtunnel v%s, paramiko v%s)',
135 sshtunnel.__version__, paramiko.__version__)
136 tunnel = SSHTunnelForwarder(
137 SSH_SERVER_ADDRESS,
138 ssh_username=SSH_SERVER_USERNAME,
139 ssh_pkey=SSH_PKEY,
140 remote_bind_addresses=[
141 SSH_SERVER_REMOTE_SIDE_ADDRESS_PG, SSH_SERVER_REMOTE_SIDE_ADDRESS_MYSQL,
142 SSH_SERVER_REMOTE_SIDE_ADDRESS_MONGO,
143 ],
144 )
145 return tunnel
146
147
148 def start(tunnel):
149 try:
150 logging.info('Trying to start ssh tunnel...')
151 tunnel.start()
152 except Exception as e:
153 logging.exception('Tunnel start exception: %r', e)
154 raise
155
156
157 def run_db_queries(tunnel):
158 result1, result2, result3 = None, None, None
159
160 try:
161 logging.info('Trying to run PG query...')
162 result1 = run_postgres_query(tunnel.local_bind_ports[0])
163 logging.info('PG query: %r', result1)
164 except Exception as e:
165 logging.exception('PG query exception: %r', e)
166 raise
167
168 try:
169 logging.info('Trying to run MYSQL query...')
170 result2 = run_mysql_query(tunnel.local_bind_ports[1])
171 logging.info('MYSQL query: %r', result2)
172 except Exception as e:
173 logging.exception('MYSQL query exception: %r', e)
174 raise
175
176 try:
177 logging.info('Trying to run MONGO query...')
178 result3 = run_mongo_query(tunnel.local_bind_ports[2])
179 logging.info('MONGO query: %r', result3)
180 except Exception as e:
181 logging.exception('MONGO query exception: %r', e)
182 raise
183
184 return result1, result2, result3
185
186
187 def wait_and_check_or_restart_if_required(tunnel, i=1):
188 logging.warning('Sleeping for %s second...', i)
189 while i:
190 time.sleep(1)
191 if i % 10 == 0:
192 logging.info('Running tunnel.check_tunnels... (i=%s)', i)
193 tunnel.check_tunnels()
194 logging.info('Check result: %r (i=%s)', tunnel.tunnel_is_up, i)
195 if not tunnel.is_active:
196 logging.warning('Tunnel is DOWN! restarting ...')
197 tunnel.restart()
198 i -= 1
199
200
201 def stop(tunnel, force=True):
202 try:
203 logging.info('Trying to stop resources...')
204 tunnel.stop(force=force)
205 except Exception as e:
206 logging.exception('Tunnel stop exception: %r', e)
207 raise
208
209
210 def show_threading_state_if_required():
211 current_threads = list(threading.enumerate())
212 if len(current_threads) > 1:
213 logging.warning('[1] THREAD INFO')
214 logging.info('Threads: %r', current_threads)
215 logging.info('Threads.daemon: %r', [x.daemon for x in current_threads])
216
217 if len(current_threads) > 1:
218 logging.warning('[2] STACK INFO')
219 code = ["\n\n*** STACKTRACE - START ***\n"]
220 for threadId, stack in sys._current_frames().items():
221 code.append("\n# ThreadID: %s" % threadId)
222 for filename, lineno, name, line in traceback.extract_stack(stack):
223 code.append('File: "%s", line %d, in %s' % (filename, lineno, name))
224 if line:
225 code.append(" %s" % (line.strip()))
226 code.append("\n*** STACKTRACE - END ***\n\n")
227 logging.info('\n'.join(code))
228
229
230 if __name__ == '__main__':
231 logging.warning('RUN')
232 tunnel = create_tunnel()
233 start(tunnel)
234 res = run_db_queries(tunnel)
235 stop(tunnel)
236 wait_and_check_or_restart_if_required(tunnel)
237 show_threading_state_if_required()
238 logging.warning('EOF')
239
240 assert res == (PG_EXPECT, MYSQL_EXPECT, MONGO_EXPECT)
0 import logging
1 import sshtunnel
2 import os
3
4
5 if __name__ == '__main__':
6 path = os.path.join(os.path.dirname(__file__), 'run_docker_e2e_db_tests.py')
7 with open(path) as f:
8 exec(f.read())
9 logging.warning('RUN')
10 tunnel = create_tunnel()
11 start(tunnel)
12 logging.warning('EOF')
0 -----BEGIN OPENSSH PRIVATE KEY-----
1 b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsQAAAAdzc2gtZH
2 NzAAAAgQDSZjQVKBCj57wXTZTFusc/Amp5wet2ugo/Mh+86+v2WDbluFztNZXTA3EtX8p6
3 zZtoLZJ/+VCtLqZD7MjJIt4/bPhOjyXOlbtIwL7w80drTxMFBOvuBQkD+TqIzaONwzsN5b
4 GcQNACpyz4C2eSUP4KOmOrKXovFI6pMQ22lbqrrQAAABUAv4qw6qJkET1T4J8o0RgzoxNI
5 TFkAAACAQQ5w7+2rPlC/GP9ScUCQZTicgzAYlTNOCvIcO4pRj7E1NwNMuafl6xNRjrIYBp
6 OqMhDLIBx15Yob0J/6PpE65oeQ8Lq8QboZxO8bio0FGt4qE6mXB4vJq2oOwQkWHzH64x9l
7 fmFQNe8KRpd0G/daXBgeF+FEqV2vVsjsjKXxwncAAACAFRvMwvnkzX/c2MaWx78+HJEjjf
8 ATYt2acoLAH2YRwnhavQyEScNQDiZnBbIr2J21ccvGvFyZT2dtcz83pwFDa9o7Y41EWQG7
9 ifRPYrj9aHd3TyxeiSGSZlna9ekcfXbIF7+aRHSyEie/YIYUGm73jCW+TDcXK1nQHu7tGL
10 1KkBQAAAHox++oGsfvqBoAAAAHc3NoLWRzcwAAAIEA0mY0FSgQo+e8F02UxbrHPwJqecHr
11 droKPzIfvOvr9lg25bhc7TWV0wNxLV/Kes2baC2Sf/lQrS6mQ+zIySLeP2z4To8lzpW7SM
12 C+8PNHa08TBQTr7gUJA/k6iM2jjcM7DeWxnEDQAqcs+AtnklD+Cjpjqyl6LxSOqTENtpW6
13 q60AAAAVAL+KsOqiZBE9U+CfKNEYM6MTSExZAAAAgEEOcO/tqz5Qvxj/UnFAkGU4nIMwGJ
14 UzTgryHDuKUY+xNTcDTLmn5esTUY6yGAaTqjIQyyAcdeWKG9Cf+j6ROuaHkPC6vEG6GcTv
15 G4qNBRreKhOplweLyatqDsEJFh8x+uMfZX5hUDXvCkaXdBv3WlwYHhfhRKldr1bI7Iyl8c
16 J3AAAAgBUbzML55M1/3NjGlse/PhyRI43wE2LdmnKCwB9mEcJ4Wr0MhEnDUA4mZwWyK9id
17 tXHLxrxcmU9nbXM/N6cBQ2vaO2ONRFkBu4n0T2K4/Wh3d08sXokhkmZZ2vXpHH12yBe/mk
18 R0shInv2CGFBpu94wlvkw3FytZ0B7u7Ri9SpAUAAAAFAZscEj14jPPE+Znbk4FflEe6t2r
19 AAAAE3Jvb3RAb3BlbnNzaC1zZXJ2ZXI=
20 -----END OPENSSH PRIVATE KEY-----
0 ssh-dss AAAAB3NzaC1kc3MAAACBANJmNBUoEKPnvBdNlMW6xz8CannB63a6Cj8yH7zr6/ZYNuW4XO01ldMDcS1fynrNm2gtkn/5UK0upkPsyMki3j9s+E6PJc6Vu0jAvvDzR2tPEwUE6+4FCQP5OojNo43DOw3lsZxA0AKnLPgLZ5JQ/go6Y6spei8UjqkxDbaVuqutAAAAFQC/irDqomQRPVPgnyjRGDOjE0hMWQAAAIBBDnDv7as+UL8Y/1JxQJBlOJyDMBiVM04K8hw7ilGPsTU3A0y5p+XrE1GOshgGk6oyEMsgHHXlihvQn/o+kTrmh5DwurxBuhnE7xuKjQUa3ioTqZcHi8mrag7BCRYfMfrjH2V+YVA17wpGl3Qb91pcGB4X4USpXa9WyOyMpfHCdwAAAIAVG8zC+eTNf9zYxpbHvz4ckSON8BNi3ZpygsAfZhHCeFq9DIRJw1AOJmcFsivYnbVxy8a8XJlPZ21zPzenAUNr2jtjjURZAbuJ9E9iuP1od3dPLF6JIZJmWdr16Rx9dsgXv5pEdLISJ79ghhQabveMJb5MNxcrWdAe7u0YvUqQFA== root@openssh-server
0 -----BEGIN OPENSSH PRIVATE KEY-----
1 b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
2 1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQS9T8ajwjHV1Xl705ShFqry77rS7wrh
3 lCN0a4Hf33yapuCBKCRDr+/Y7gISoiER3rez56TQCvIFuKUEgCUsTMSWAAAAsFXWKa1V1i
4 mtAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL1PxqPCMdXVeXvT
5 lKEWqvLvutLvCuGUI3Rrgd/ffJqm4IEoJEOv79juAhKiIRHet7PnpNAK8gW4pQSAJSxMxJ
6 YAAAAhAOe2L6cmoGh4gZ++o+GiqMQ2WQ3RUfle/gc0G1nhLPhWAAAAE3Jvb3RAb3BlbnNz
7 aC1zZXJ2ZXIBAgME
8 -----END OPENSSH PRIVATE KEY-----
0 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBL1PxqPCMdXVeXvTlKEWqvLvutLvCuGUI3Rrgd/ffJqm4IEoJEOv79juAhKiIRHet7PnpNAK8gW4pQSAJSxMxJY= root@openssh-server
0 -----BEGIN OPENSSH PRIVATE KEY-----
1 b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
2 QyNTUxOQAAACCjD/qc+T3Cc/k2pbjOUFW0OobeLnoWUAoaBhTQchI26wAAAJgismUyIrJl
3 MgAAAAtzc2gtZWQyNTUxOQAAACCjD/qc+T3Cc/k2pbjOUFW0OobeLnoWUAoaBhTQchI26w
4 AAAECV2axXFduGtP3RS1f97smocwVLphmETzpWdwi89jWrJaMP+pz5PcJz+TaluM5QVbQ6
5 ht4uehZQChoGFNByEjbrAAAAE3Jvb3RAb3BlbnNzaC1zZXJ2ZXIBAg==
6 -----END OPENSSH PRIVATE KEY-----
0 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKMP+pz5PcJz+TaluM5QVbQ6ht4uehZQChoGFNByEjbr root@openssh-server
0 -----BEGIN OPENSSH PRIVATE KEY-----
1 b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
2 NhAAAAAwEAAQAAAYEAvIGU0pRpThhIcaSPrg2+v7cXl+QcG0icb45hfD44yrCoXkpJp7nh
3 Hv0ObZL2Y1cG7eeayYF4AqD3kwQ7W89GN6YO9b/mkJgawk0/YLUyojTS9dbcTbdkfPzyUa
4 vTMDjly+PIjfiWOEnUgPf1y3xONLkJU0ILyTmgTzSIMNdKngtdCGfytBCuNiPKU8hEdEVt
5 82ebqgtLoSYn9cUcVVz6LewzUh8+YtoPb8Z/BIVEzU37HiE9MOYIBXjo1AEJSnOCkjwlVl
6 PzLhcXKTPht0iwv/KnZNNg0LDmnU/z0n+nPq/EMflum8jRYbgp0C5hksPdc8e0eEKd9gak
7 t7B0ta3Mjt5b8HPQdBGZI/QFufEnSOxfJmoK4Bvjy/oUwE0hGU6po5g+4T2j6Bqqm2I+yV
8 EbkP/UiuD/kEiT0C3yCV547gIDjN2ME9tGJDkd023BFvqn3stFVVZ5WsisRKGc+lvTfqeA
9 JyKFaVt5a23y68ztjEMVrMLksRuEF8gG5kV7EGyjAAAFiCzGBRksxgUZAAAAB3NzaC1yc2
10 EAAAGBALyBlNKUaU4YSHGkj64Nvr+3F5fkHBtInG+OYXw+OMqwqF5KSae54R79Dm2S9mNX
11 Bu3nmsmBeAKg95MEO1vPRjemDvW/5pCYGsJNP2C1MqI00vXW3E23ZHz88lGr0zA45cvjyI
12 34ljhJ1ID39ct8TjS5CVNCC8k5oE80iDDXSp4LXQhn8rQQrjYjylPIRHRFbfNnm6oLS6Em
13 J/XFHFVc+i3sM1IfPmLaD2/GfwSFRM1N+x4hPTDmCAV46NQBCUpzgpI8JVZT8y4XFykz4b
14 dIsL/yp2TTYNCw5p1P89J/pz6vxDH5bpvI0WG4KdAuYZLD3XPHtHhCnfYGpLewdLWtzI7e
15 W/Bz0HQRmSP0BbnxJ0jsXyZqCuAb48v6FMBNIRlOqaOYPuE9o+gaqptiPslRG5D/1Irg/5
16 BIk9At8gleeO4CA4zdjBPbRiQ5HdNtwRb6p97LRVVWeVrIrEShnPpb036ngCcihWlbeWtt
17 8uvM7YxDFazC5LEbhBfIBuZFexBsowAAAAMBAAEAAAGAflHjdb2oV4HkQetBsSRa18QM1m
18 cxAoOE+SiTYRudGQ6KtSzY8MGZ/xca7QiXfXhbF1+llTTiQ/i0Dtu+H0blyfLIgZwIGIsl
19 G2GCf/7MoG//kmhaFuY3O56Rj3MyQVVPgHLy+VhE6hFniske+C4jhicc/aL7nOu15n3Qad
20 JLmV8KB9EIjevDoloXgk9ot/WyuXKLmMaa9rFIA+UDmJyGtfFbbsOrHbj8sS11/oSD14RT
21 LBygEb2EUI52j2LmY/LEvUL+59oCuJ6Y/h+pMdFeuHJzGjrVb573KnGwejzY24HHzzebrC
22 Q+9NyVCTyizPHNu9w52/GPEZQFQBi7o9cDMd3ITZEPIaIvDHsUwPXaHUBHy/XHQTs8pDqk
23 zCMcAs5zdzao2I0LQ+ZFYyvl1rue82ITjDISX1WK6nFYLBVXugi0rLGEdH6P+Psfl3uCIf
24 aW7c12/BpZz2Pql5AuO1wsu4rmz2th68vaC/0IDqWekIbW9qihFbqnhfAxRsIURjpBAAAA
25 wDhIQPsj9T9Vud3Z/TZjiAKCPbg3zi082u1GMMxXnNQtKO3J35wU7VUcAxAzosWr+emMqS
26 U0qW+a5RXr3sqUOqH85b5+Xw0yv2sTr2pL0ALFW7Tq1mesCc3K0So3Yo30pWRIOxYM9ihm
27 E4ci/3mN5kcKWwvLLomFPRU9u0XtIGKnF/cNByTuz9fceR6Pi6mQXZawv+OOMiBeu0gbyp
28 F1uVe8PCshzCrWTE3UjRpQxy9gizvSbGZyGQi1Lm42JXKG3wAAAMEA4r4CLM1xsyxBBMld
29 rxiTqy6bfrZjKkT5MPjBjp+57i5kW9NVqGCnIy/m98pLTuKjTCDmUuWQXS+oqhHw5vq/wj
30 RvQYqkJDz1UGmC1lD2qyqERjOiWa8/iy4dXSLeHCT70+/xR2dBb0z8cT++yZEqLdEZSnHG
31 yRaZMHot1OohVDqJS8nEbxOzgPGdopRMiX6ws/p5/k9YAGkHx0hszA8cn/Tk2/mdS5lugw
32 Y7mdXzfcKvxkgoFrG7XowqRVrozcvDAAAAwQDU1ITasquNLaQhKNqiHx/N7bvKVO33icAx
33 NdShqJEWx/g9idvQ25sA1Ubc1a+Ot5Lgfrs2OBKe+LgSmPAZOjv4ShqBHtsSh3am8/K1xR
34 gQKgojLL4FhtgxtwoZrVvovZHGV3g2A28BRGbKIGVGPsOszJALU7jlLlcTHlB7SCQBI8FQ
35 vTi2UEsfTmA22NnuVPITeqbmAQQXkSZcZbpbvdc0vQzp/3iOb/OCrIMET3HqVEMyQVsVs6
36 xa9026AMTGLaEAAAATcm9vdEBvcGVuc3NoLXNlcnZlcg==
37 -----END OPENSSH PRIVATE KEY-----
0 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC8gZTSlGlOGEhxpI+uDb6/txeX5BwbSJxvjmF8PjjKsKheSkmnueEe/Q5tkvZjVwbt55rJgXgCoPeTBDtbz0Y3pg71v+aQmBrCTT9gtTKiNNL11txNt2R8/PJRq9MwOOXL48iN+JY4SdSA9/XLfE40uQlTQgvJOaBPNIgw10qeC10IZ/K0EK42I8pTyER0RW3zZ5uqC0uhJif1xRxVXPot7DNSHz5i2g9vxn8EhUTNTfseIT0w5ggFeOjUAQlKc4KSPCVWU/MuFxcpM+G3SLC/8qdk02DQsOadT/PSf6c+r8Qx+W6byNFhuCnQLmGSw91zx7R4Qp32BqS3sHS1rcyO3lvwc9B0EZkj9AW58SdI7F8magrgG+PL+hTATSEZTqmjmD7hPaPoGqqbYj7JURuQ/9SK4P+QSJPQLfIJXnjuAgOM3YwT20YkOR3TbcEW+qfey0VVVnlayKxEoZz6W9N+p4AnIoVpW3lrbfLrzO2MQxWswuSxG4QXyAbmRXsQbKM= root@openssh-server
0 # $OpenBSD: sshd_config,v 1.103 2018/04/09 20:41:22 tj Exp $
1
2 # This is the sshd server system-wide configuration file. See
3 # sshd_config(5) for more information.
4
5 # This sshd was compiled with PATH=/bin:/usr/bin:/sbin:/usr/sbin
6
7 # The strategy used for options in the default sshd_config shipped with
8 # OpenSSH is to specify options with their default value where
9 # possible, but leave them commented. Uncommented options override the
10 # default value.
11
12 #Port 22
13 #AddressFamily any
14 #ListenAddress 0.0.0.0
15 #ListenAddress ::
16
17 #HostKey /etc/ssh/ssh_host_rsa_key
18 #HostKey /etc/ssh/ssh_host_ecdsa_key
19 #HostKey /etc/ssh/ssh_host_ed25519_key
20
21 # Ciphers and keying
22 #RekeyLimit default none
23
24 # Logging
25 #SyslogFacility AUTH
26 #LogLevel INFO
27
28 # Authentication:
29
30 #LoginGraceTime 2m
31 #PermitRootLogin prohibit-password
32 #StrictModes yes
33 #MaxAuthTries 6
34 #MaxSessions 10
35
36 #PubkeyAuthentication yes
37
38 # The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2
39 # but this is overridden so installations will only check .ssh/authorized_keys
40 AuthorizedKeysFile .ssh/authorized_keys
41
42 #AuthorizedPrincipalsFile none
43
44 #AuthorizedKeysCommand none
45 #AuthorizedKeysCommandUser nobody
46
47 # For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
48 #HostbasedAuthentication no
49 # Change to yes if you don't trust ~/.ssh/known_hosts for
50 # HostbasedAuthentication
51 #IgnoreUserKnownHosts no
52 # Don't read the user's ~/.rhosts and ~/.shosts files
53 #IgnoreRhosts yes
54
55 # To disable tunneled clear text passwords, change to no here!
56 PasswordAuthentication no
57 #PermitEmptyPasswords no
58
59 # Change to no to disable s/key passwords
60 #ChallengeResponseAuthentication yes
61
62 # Kerberos options
63 #KerberosAuthentication no
64 #KerberosOrLocalPasswd yes
65 #KerberosTicketCleanup yes
66 #KerberosGetAFSToken no
67
68 # GSSAPI options
69 #GSSAPIAuthentication no
70 #GSSAPICleanupCredentials yes
71
72 # Set this to 'yes' to enable PAM authentication, account processing,
73 # and session processing. If this is enabled, PAM authentication will
74 # be allowed through the ChallengeResponseAuthentication and
75 # PasswordAuthentication. Depending on your PAM configuration,
76 # PAM authentication via ChallengeResponseAuthentication may bypass
77 # the setting of "PermitRootLogin without-password".
78 # If you just want the PAM account and session checks to run without
79 # PAM authentication, then enable this but set PasswordAuthentication
80 # and ChallengeResponseAuthentication to 'no'.
81 #UsePAM no
82
83 #AllowAgentForwarding yes
84 # Feel free to re-enable these if your use case requires them.
85 AllowTcpForwarding yes
86 GatewayPorts no
87 X11Forwarding no
88 #X11DisplayOffset 10
89 #X11UseLocalhost yes
90 #PermitTTY yes
91 #PrintMotd yes
92 #PrintLastLog yes
93 #TCPKeepAlive yes
94 #PermitUserEnvironment no
95 #Compression delayed
96 #ClientAliveInterval 0
97 #ClientAliveCountMax 3
98 #UseDNS no
99 PidFile /config/sshd.pid
100 #MaxStartups 10:30:100
101 #PermitTunnel no
102 #ChrootDirectory none
103 #VersionAddendum none
104
105 # no default banner path
106 #Banner none
107
108 # override default of no subsystems
109 Subsystem sftp /usr/lib/ssh/sftp-server -u 022
110
111 # Example of overriding settings on a per-user basis
112 #Match User anoncvs
113 # X11Forwarding no
114 # AllowTcpForwarding no
115 # PermitTTY no
116 # ForceCommand cvs server
0 [build-system]
1 requires = ["setuptools", "wheel"]
2 build-backend = "setuptools.build_meta:__legacy__"
55 """
66
77 import re
8 import sys
98 from os import path
109 from codecs import open # To use a consistent encoding
11
1210 from setuptools import setup # Always prefer setuptools over distutils
13 from setuptools.command.test import test as TestCommand
1411
1512 here = path.abspath(path.dirname(__file__))
1613 name = 'sshtunnel'
3128 version = eval(re.search("__version__[ ]*=[ ]*([^\r\n]+)", data).group(1))
3229
3330
34 class Tox(TestCommand):
35 """ Integration with tox """
36
37 def finalize_options(self):
38 TestCommand.finalize_options(self)
39 self.test_args = ['--recreate', '-v']
40 self.test_suite = True
41
42 def run_tests(self):
43 # import here, otherwise eggs aren't loaded
44 import tox
45 errcode = tox.cmdline(self.test_args)
46 sys.exit(errcode)
47
48
4931 setup(
5032 name=name,
5133
5638
5739 description=description,
5840 long_description='\n'.join((long_description, documentation, changelog)),
41 long_description_content_type='text/x-rst',
5942
6043 # The project's main homepage.
6144 url=url,
6245 download_url=ppa + version + '.zip', # noqa
6346
6447 # Author details
65 author='Pahaz Blinov',
66 author_email='pahaz.blinov@gmail.com',
48 author='Pahaz White',
49 author_email='pahaz.white@gmail.com',
6750
6851 # Choose your license
6952 license='MIT',
9174 'Programming Language :: Python :: 3.4',
9275 'Programming Language :: Python :: 3.5',
9376 'Programming Language :: Python :: 3.6',
77 'Programming Language :: Python :: 3.7',
78 'Programming Language :: Python :: 3.8',
9479 ],
9580
9681 platforms=['unix', 'macos', 'windows'],
11196 # requirements files see:
11297 # https://packaging.python.org/en/latest/requirements.html
11398 install_requires=[
114 'paramiko>=1.15.2',
99 'paramiko>=2.7.2',
115100 ],
116101
117102 # List additional groups of dependencies here (e.g. development
118103 # dependencies). You can install these using the following syntax,
119104 # for example:
120105 # $ pip install -e .[dev,test]
106 tests_require=[
107 'tox>=1.8.1',
108 ],
121109 extras_require={
122110 'dev': ['check-manifest'],
123111 'test': [
145133 ]
146134 },
147135
148 # Integrate tox with setuptools
149 cmdclass={'test': Tox},
150136 )
0 Metadata-Version: 1.1
0 Metadata-Version: 2.1
11 Name: sshtunnel
2 Version: 0.1.4
2 Version: 0.4.0
33 Summary: Pure python SSH tunnels
44 Home-page: https://github.com/pahaz/sshtunnel
5 Author: Pahaz Blinov
6 Author-email: pahaz.blinov@gmail.com
5 Download-URL: https://pypi.python.org/packages/source/s/sshtunnel/sshtunnel-0.4.0.zip
6 Author: Pahaz White
7 Author-email: pahaz.white@gmail.com
78 License: MIT
8 Download-URL: https://pypi.python.org/packages/source/s/sshtunnel/sshtunnel-0.1.4.zip
9 Description-Content-Type: UNKNOWN
10 Description: |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
11
12 |pyversions| |license|
13
14 **Author**: `Pahaz Blinov`_
15
16 **Repo**: https://github.com/pahaz/sshtunnel/
17
18 Inspired by https://github.com/jmagnusson/bgtunnel, but it doesn't work on
19 Windows.
20
21 See also: https://github.com/paramiko/paramiko/blob/master/demos/forward.py
22
23 Requirements
24 -------------
25
26 * `paramiko`_
27
28 Installation
29 ============
30
31 `sshtunnel`_ is on PyPI, so simply run:
32
33 ::
34
35 pip install sshtunnel
36
37 or ::
38
39 easy_install sshtunnel
40
41 or ::
42
43 conda install -c conda-forge sshtunnel
44
45 to have it installed in your environment.
46
47 For installing from source, clone the
48 `repo <https://github.com/pahaz/sshtunnel>`_ and run::
49
50 python setup.py install
51
52 Testing the package
53 -------------------
54
55 In order to run the tests you first need
56 `tox <https://testrun.org/tox/latest/>`_ and run::
57
58 python setup.py test
59
60 Usage scenarios
61 ===============
62
63 One of the typical scenarios where ``sshtunnel`` is helpful is depicted in the
64 figure below. User may need to connect a port of a remote server (i.e. 8080)
65 where only SSH port (usually port 22) is reachable. ::
66
67 ----------------------------------------------------------------------
68
69 |
70 -------------+ | +----------+
71 LOCAL | | | REMOTE | :22 SSH
72 CLIENT | <== SSH ========> | SERVER | :8080 web service
73 -------------+ | +----------+
74 |
75 FIREWALL (only port 22 is open)
76
77 ----------------------------------------------------------------------
78
79 **Fig1**: How to connect to a service blocked by a firewall through SSH tunnel.
80
81
82 If allowed by the SSH server, it is also possible to reach a private server
83 (from the perspective of ``REMOTE SERVER``) not directly visible from the
84 outside (``LOCAL CLIENT``'s perspective). ::
85
86 ----------------------------------------------------------------------
87
88 |
89 -------------+ | +----------+ +---------
90 LOCAL | | | REMOTE | | PRIVATE
91 CLIENT | <== SSH ========> | SERVER | <== local ==> | SERVER
92 -------------+ | +----------+ +---------
93 |
94 FIREWALL (only port 443 is open)
95
96 ----------------------------------------------------------------------
97
98 **Fig2**: How to connect to ``PRIVATE SERVER`` through SSH tunnel.
99
100
101 Usage examples
102 ==============
103
104 API allows either initializing the tunnel and starting it or using a ``with``
105 context, which will take care of starting **and stopping** the tunnel:
106
107 Example 1
108 ---------
109
110 Code corresponding to **Fig1** above follows, given remote server's address is
111 ``pahaz.urfuclub.ru``, password authentication and randomly assigned local bind
112 port.
113
114 .. code-block:: py
115
116 from sshtunnel import SSHTunnelForwarder
117
118 server = SSHTunnelForwarder(
119 'pahaz.urfuclub.ru',
120 ssh_username="pahaz",
121 ssh_password="secret",
122 remote_bind_address=('127.0.0.1', 8080)
123 )
124
125 server.start()
126
127 print(server.local_bind_port) # show assigned local port
128 # work with `SECRET SERVICE` through `server.local_bind_port`.
129
130 server.stop()
131
132 Example 2
133 ---------
134
135 Example of a port forwarding to a private server not directly reachable,
136 assuming password protected pkey authentication, remote server's SSH service is
137 listening on port 443 and that port is open in the firewall (**Fig2**):
138
139 .. code-block:: py
140
141 import paramiko
142 from sshtunnel import SSHTunnelForwarder
143
144 with SSHTunnelForwarder(
145 (REMOTE_SERVER_IP, 443),
146 ssh_username="",
147 ssh_pkey="/var/ssh/rsa_key",
148 ssh_private_key_password="secret",
149 remote_bind_address=(PRIVATE_SERVER_IP, 22),
150 local_bind_address=('0.0.0.0', 10022)
151 ) as tunnel:
152 client = paramiko.SSHClient()
153 client.load_system_host_keys()
154 client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
155 client.connect('127.0.0.1', 10022)
156 # do some operations with client session
157 client.close()
158
159 print('FINISH!')
160
161 Example 3
162 ---------
163
164 Example of a port forwarding for the Vagrant MySQL local port:
165
166 .. code-block:: py
167
168 from sshtunnel import SSHTunnelForwarder
169 from time import sleep
170
171 with SSHTunnelForwarder(
172 ('localhost', 2222),
173 ssh_username="vagrant",
174 ssh_password="vagrant",
175 remote_bind_address=('127.0.0.1', 3306)
176 ) as server:
177
178 print(server.local_bind_port)
179 while True:
180 # press Ctrl-C for stopping
181 sleep(1)
182
183 print('FINISH!')
184
185 Or simply using the CLI:
186
187 .. code-block:: console
188
189 (bash)$ python -m sshtunnel -U vagrant -P vagrant -L :3306 -R 127.0.0.1:3306 -p 2222 localhost
190
191 CLI usage
192 =========
193
194 ::
195
196 $ sshtunnel --help
197 usage: sshtunnel [-h] [-U SSH_USERNAME] [-p SSH_PORT] [-P SSH_PASSWORD] -R
198 IP:PORT [IP:PORT ...] [-L [IP:PORT [IP:PORT ...]]]
199 [-k SSH_HOST_KEY] [-K KEY_FILE] [-S KEY_PASSWORD] [-t] [-v]
200 [-V] [-x IP:PORT] [-c SSH_CONFIG_FILE] [-z] [-n] [-d [FOLDER [FOLDER ...]]]
201 ssh_address
202
203 Pure python ssh tunnel utils
204 Version 0.1.4
205
206 positional arguments:
207 ssh_address SSH server IP address (GW for SSH tunnels)
208 set with "-- ssh_address" if immediately after -R or -L
209
210 optional arguments:
211 -h, --help show this help message and exit
212 -U SSH_USERNAME, --username SSH_USERNAME
213 SSH server account username
214 -p SSH_PORT, --server_port SSH_PORT
215 SSH server TCP port (default: 22)
216 -P SSH_PASSWORD, --password SSH_PASSWORD
217 SSH server account password
218 -R IP:PORT [IP:PORT ...], --remote_bind_address IP:PORT [IP:PORT ...]
219 Remote bind address sequence: ip_1:port_1 ip_2:port_2 ... ip_n:port_n
220 Equivalent to ssh -Lxxxx:IP_ADDRESS:PORT
221 If port is omitted, defaults to 22.
222 Example: -R 10.10.10.10: 10.10.10.10:5900
223 -L [IP:PORT [IP:PORT ...]], --local_bind_address [IP:PORT [IP:PORT ...]]
224 Local bind address sequence: ip_1:port_1 ip_2:port_2 ... ip_n:port_n
225 Elements may also be valid UNIX socket domains:
226 /tmp/foo.sock /tmp/bar.sock ... /tmp/baz.sock
227 Equivalent to ssh -LPORT:xxxxxxxxx:xxxx, being the local IP address optional.
228 By default it will listen in all interfaces (0.0.0.0) and choose a random port.
229 Example: -L :40000
230 -k SSH_HOST_KEY, --ssh_host_key SSH_HOST_KEY
231 Gateway's host key
232 -K KEY_FILE, --private_key_file KEY_FILE
233 RSA/DSS/ECDSA private key file
234 -S KEY_PASSWORD, --private_key_password KEY_PASSWORD
235 RSA/DSS/ECDSA private key password
236 -t, --threaded Allow concurrent connections to each tunnel
237 -v, --verbose Increase output verbosity (default: ERROR)
238 -V, --version Show version number and quit
239 -x IP:PORT, --proxy IP:PORT
240 IP and port of SSH proxy to destination
241 -c SSH_CONFIG_FILE, --config SSH_CONFIG_FILE
242 SSH configuration file, defaults to ~/.ssh/config
243 -z, --compress Request server for compression over SSH transport
244 -n, --noagent Disable looking for keys from an SSH agent
245 -d [FOLDER [FOLDER ...]], --host_pkey_directories [FOLDER [FOLDER ...]]
246 List of directories where SSH pkeys (in the format `id_*`) may be found
247
248 .. _Pahaz Blinov: https://github.com/pahaz
249 .. _sshtunnel: https://pypi.python.org/pypi/sshtunnel
250 .. _paramiko: http://www.paramiko.org/
251 .. |CircleCI| image:: https://circleci.com/gh/pahaz/sshtunnel.svg?style=svg
252 :target: https://circleci.com/gh/pahaz/sshtunnel
253 .. |AppVeyor| image:: https://ci.appveyor.com/api/projects/status/oxg1vx2ycmnw3xr9?svg=true&passingText=Windows%20-%20OK&failingText=Windows%20-%20Fail
254 :target: https://ci.appveyor.com/project/pahaz/sshtunnel
255 .. |readthedocs| image:: https://readthedocs.org/projects/sshtunnel/badge/?version=latest
256 :target: http://sshtunnel.readthedocs.io/en/latest/?badge=latest
257 :alt: Documentation Status
258 .. |coveralls| image:: https://coveralls.io/repos/github/pahaz/sshtunnel/badge.svg?branch=master
259 :target: https://coveralls.io/github/pahaz/sshtunnel?branch=master
260 .. |pyversions| image:: https://img.shields.io/pypi/pyversions/sshtunnel.svg
261 .. |version| image:: https://img.shields.io/pypi/v/sshtunnel.svg
262 :target: `sshtunnel`_
263 .. |license| image:: https://img.shields.io/pypi/l/sshtunnel.svg
264 :target: https://github.com/pahaz/sshtunnel/blob/master/LICENSE
265
266 Online documentation
267 ====================
268
269 Documentation may be found at `readthedocs`_.
270
271 .. _readthedocs: https://sshtunnel.readthedocs.org/
272
273 CONTRIBUTORS
274 ============
275
276 - `Cameron Maske`_
277 - `Gustavo Machado`_
278 - `Colin Jermain`_
279 - `JM Fernández`_ - (big thanks!)
280 - `Lewis Thompson`_
281 - `Erik Rogers`_
282 - `Mart Sõmermaa`_
283 - `Chronial`_
284 - `Dan Harbin`_
285 - `Ignacio Peluffo`_
286 - `Niels Zeilemaker`_
287
288 CHANGELOG
289 =========
290
291 - v.0.1.4 (`Niels Zeilemaker`_)
292 + Allow loading pkeys from `~/.ssh`
293
294 - v.0.1.3 (`Ignacio Peluffo`_ and others)
295 + ``pkey_file`` parameter updated to accept relative paths to user folder using ``~``
296 + Several bugfixes
297
298 - v.0.1.2 (`JM Fernández`_)
299 + Fix #77
300
301 - v.0.1.1 (`JM Fernández`_)
302 + Fix #72
303
304 - v.0.1.0 (`JM Fernández`_)
305 + Add `tunnel_bindings` property
306 + Several bugfixes (#49, #56, #57, #59, #60, #62, #64, #66, ...)
307 (`Pahaz Blinov`_, `JM Fernández`_)
308 + Add TRACE logging level (`JM Fernández`_)
309 + Code and tests refactoring (`JM Fernández`_)
310 + Drop python3.2 support
311
312 - v.0.0.8 (`JM Fernández`_)
313 + Merge `#31`_: Support Unix domain socket (local) forwarding (`Dan Harbin`_)
314 + Simplify API (`JM Fernández`_)
315 + Add sphinx-based documentation (`JM Fernández`_)
316 + Add ``allow_agent`` (fixes `#36`_, `#46`_) (`JM Fernández`_)
317 + Add ``compression`` (`JM Fernández`_)
318 + Add ``__str__`` method (`JM Fernández`_)
319 + Add test functions (`JM Fernández`_)
320 + Fix default username when not provided and ssh_config file is skipped (`JM Fernández`_)
321 + Fix gateway IP unresolvable exception catching (`JM Fernández`_)
322 + Minor fixes (`JM Fernández`_)
323 + Add AppVeyor support (`JM Fernández`_)
324
325 - v.0.0.7 (`JM Fernández`_)
326 + Tunnels can now be stopped and started safely (`#41`_) (`JM Fernández`_)
327 + Add timeout to SSH gateway and keep-alive messages (`#29`_) (`JM Fernández`_)
328 + Allow sending a pkey directly (`#43`_) (`Chronial`_)
329 + Add ``-V`` CLI option to show current version (`JM Fernández`_)
330 + Add coverage (`JM Fernández`_)
331 + Refactoring (`JM Fernández`_)
332
333 - v.0.0.6 (`Pahaz Blinov`_)
334 + add ``-S`` CLI options for ssh private key password support (`Pahaz Blinov`_)
335
336 - v.0.0.5 (`Pahaz Blinov`_)
337 + add ``ssh_proxy`` argument, as well as ``ssh_config(5)`` ``ProxyCommand`` support (`Lewis Thompson`_)
338 + add some python 2.6 compatibility fixes (`Mart Sõmermaa`_)
339 + ``paramiko.transport`` inherits handlers of loggers passed to ``SSHTunnelForwarder`` (`JM Fernández`_)
340 + fix `#34`_, `#33`_, code style and docs (`JM Fernández`_)
341 + add tests (`Pahaz Blinov`_)
342 + add CI integration (`Pahaz Blinov`_)
343 + normal packaging (`Pahaz Blinov`_)
344 + disable check distenation socket connection by ``SSHTunnelForwarder.local_is_up`` (`Pahaz Blinov`_) [changed default behavior]
345 + use daemon mode = False in all threads by default; detail_ (`Pahaz Blinov`_) [changed default behavior]
346
347 - v.0.0.4.4 (`Pahaz Blinov`_)
348 + fix issue `#24`_ - hide ssh password in logs (`Pahaz Blinov`_)
349
350 - v.0.0.4.3 (`Pahaz Blinov`_)
351 + fix default port issue `#19`_ (`Pahaz Blinov`_)
352
353 - v.0.0.4.2 (`Pahaz Blinov`_)
354 + fix Thread.daemon mode for Python < 3.3 `#16`_, `#21`_ (`Lewis Thompson`_, `Erik Rogers`_)
355
356 - v.0.0.4.1 (`Pahaz Blinov`_)
357 + fix CLI issues `#13`_ (`Pahaz Blinov`_)
358
359 - v.0.0.4 (`Pahaz Blinov`_)
360 + daemon mode by default for all threads (`JM Fernández`_, `Pahaz Blinov`_) - *incompatible*
361 + move ``make_ssh_forward_server`` to ``SSHTunnelForwarder.make_ssh_forward_server`` (`Pahaz Blinov`_, `JM Fernández`_) - *incompatible*
362 + move ``make_ssh_forward_handler`` to ``SSHTunnelForwarder.make_ssh_forward_handler_class`` (`Pahaz Blinov`_, `JM Fernández`_) - *incompatible*
363 + rename ``open`` to ``open_tunnel`` (`JM Fernández`_) - *incompatible*
364 + add CLI interface (`JM Fernández`_)
365 + support opening several tunnels at once (`JM Fernández`_)
366 + improve stability and readability (`JM Fernández`_, `Pahaz Blinov`_)
367 + improve logging (`JM Fernández`_, `Pahaz Blinov`_)
368 + add ``raise_exception_if_any_forwarder_have_a_problem`` argument for opening several tunnels at once (`Pahaz Blinov`_)
369 + add ``ssh_config_file`` argument support (`JM Fernández`_)
370 + add Python 3 support (`JM Fernández`_, `Pahaz Blinov`_)
371
372 - v.0.0.3 (`Pahaz Blinov`_)
373 + add ``threaded`` option (`Cameron Maske`_)
374 + fix exception error message, correctly printing destination address (`Gustavo Machado`_)
375 + fix ``pip install`` failure (`Colin Jermain`_, `Pahaz Blinov`_)
376
377 - v.0.0.1 (`Pahaz Blinov`_)
378 + ``SSHTunnelForwarder`` class (`Pahaz Blinov`_)
379 + ``open`` function (`Pahaz Blinov`_)
380
381
382 .. _Cameron Maske: https://github.com/cameronmaske
383 .. _Gustavo Machado: https://github.com/gdmachado
384 .. _Colin Jermain: https://github.com/cjermain
385 .. _JM Fernández: https://github.com/fernandezcuesta
386 .. _Lewis Thompson: https://github.com/lewisthompson
387 .. _Erik Rogers: https://github.com/ewrogers
388 .. _Mart Sõmermaa: https://github.com/mrts
389 .. _Chronial: https://github.com/Chronial
390 .. _Dan Harbin: https://github.com/RasterBurn
391 .. _Ignacio Peluffo: https://github.com/ipeluffo
392 .. _Niels Zeilemaker: https://github.com/NielsZeilemaker
393 .. _#13: https://github.com/pahaz/sshtunnel/issues/13
394 .. _#16: https://github.com/pahaz/sshtunnel/issues/16
395 .. _#19: https://github.com/pahaz/sshtunnel/issues/19
396 .. _#21: https://github.com/pahaz/sshtunnel/issues/21
397 .. _#24: https://github.com/pahaz/sshtunnel/issues/24
398 .. _#29: https://github.com/pahaz/sshtunnel/issues/29
399 .. _#31: https://github.com/pahaz/sshtunnel/issues/31
400 .. _#33: https://github.com/pahaz/sshtunnel/issues/33
401 .. _#34: https://github.com/pahaz/sshtunnel/issues/34
402 .. _#36: https://github.com/pahaz/sshtunnel/issues/36
403 .. _#41: https://github.com/pahaz/sshtunnel/issues/41
404 .. _#43: https://github.com/pahaz/sshtunnel/issues/43
405 .. _#46: https://github.com/pahaz/sshtunnel/issues/46
406 .. _detail: https://github.com/pahaz/sshtunnel/commit/64af238b799b0e0057c4f9b386cda247e0006da9#diff-76bc1662a114401c2954deb92b740081R127
407
4089 Keywords: ssh tunnel paramiko proxy tcp-forward
40910 Platform: unix
41011 Platform: macos
41920 Classifier: Programming Language :: Python :: 3.4
42021 Classifier: Programming Language :: Python :: 3.5
42122 Classifier: Programming Language :: Python :: 3.6
23 Classifier: Programming Language :: Python :: 3.7
24 Classifier: Programming Language :: Python :: 3.8
25 Description-Content-Type: text/x-rst
26 Provides-Extra: build_sphinx
27 Provides-Extra: dev
28 Provides-Extra: test
29 License-File: LICENSE
30
31 |CircleCI| |AppVeyor| |readthedocs| |coveralls| |version|
32
33 |pyversions| |license|
34
35 **Author**: `Pahaz`_
36
37 **Repo**: https://github.com/pahaz/sshtunnel/
38
39 Inspired by https://github.com/jmagnusson/bgtunnel, which doesn't work on
40 Windows.
41
42 See also: https://github.com/paramiko/paramiko/blob/master/demos/forward.py
43
44 Requirements
45 -------------
46
47 * `paramiko`_
48
49 Installation
50 ============
51
52 `sshtunnel`_ is on PyPI, so simply run:
53
54 ::
55
56 pip install sshtunnel
57
58 or ::
59
60 easy_install sshtunnel
61
62 or ::
63
64 conda install -c conda-forge sshtunnel
65
66 to have it installed in your environment.
67
68 For installing from source, clone the
69 `repo <https://github.com/pahaz/sshtunnel>`_ and run::
70
71 python setup.py install
72
73 Testing the package
74 -------------------
75
76 In order to run the tests you first need
77 `tox <https://testrun.org/tox/latest/>`_ and run::
78
79 python setup.py test
80
81 Usage scenarios
82 ===============
83
84 One of the typical scenarios where ``sshtunnel`` is helpful is depicted in the
85 figure below. User may need to connect a port of a remote server (i.e. 8080)
86 where only SSH port (usually port 22) is reachable. ::
87
88 ----------------------------------------------------------------------
89
90 |
91 -------------+ | +----------+
92 LOCAL | | | REMOTE | :22 SSH
93 CLIENT | <== SSH ========> | SERVER | :8080 web service
94 -------------+ | +----------+
95 |
96 FIREWALL (only port 22 is open)
97
98 ----------------------------------------------------------------------
99
100 **Fig1**: How to connect to a service blocked by a firewall through SSH tunnel.
101
102
103 If allowed by the SSH server, it is also possible to reach a private server
104 (from the perspective of ``REMOTE SERVER``) not directly visible from the
105 outside (``LOCAL CLIENT``'s perspective). ::
106
107 ----------------------------------------------------------------------
108
109 |
110 -------------+ | +----------+ +---------
111 LOCAL | | | REMOTE | | PRIVATE
112 CLIENT | <== SSH ========> | SERVER | <== local ==> | SERVER
113 -------------+ | +----------+ +---------
114 |
115 FIREWALL (only port 443 is open)
116
117 ----------------------------------------------------------------------
118
119 **Fig2**: How to connect to ``PRIVATE SERVER`` through SSH tunnel.
120
121
122 Usage examples
123 ==============
124
125 API allows either initializing the tunnel and starting it or using a ``with``
126 context, which will take care of starting **and stopping** the tunnel:
127
128 Example 1
129 ---------
130
131 Code corresponding to **Fig1** above follows, given remote server's address is
132 ``pahaz.urfuclub.ru``, password authentication and randomly assigned local bind
133 port.
134
135 .. code-block:: python
136
137 from sshtunnel import SSHTunnelForwarder
138
139 server = SSHTunnelForwarder(
140 'alfa.8iq.dev',
141 ssh_username="pahaz",
142 ssh_password="secret",
143 remote_bind_address=('127.0.0.1', 8080)
144 )
145
146 server.start()
147
148 print(server.local_bind_port) # show assigned local port
149 # work with `SECRET SERVICE` through `server.local_bind_port`.
150
151 server.stop()
152
153 Example 2
154 ---------
155
156 Example of a port forwarding to a private server not directly reachable,
157 assuming password protected pkey authentication, remote server's SSH service is
158 listening on port 443 and that port is open in the firewall (**Fig2**):
159
160 .. code-block:: python
161
162 import paramiko
163 import sshtunnel
164
165 with sshtunnel.open_tunnel(
166 (REMOTE_SERVER_IP, 443),
167 ssh_username="",
168 ssh_pkey="/var/ssh/rsa_key",
169 ssh_private_key_password="secret",
170 remote_bind_address=(PRIVATE_SERVER_IP, 22),
171 local_bind_address=('0.0.0.0', 10022)
172 ) as tunnel:
173 client = paramiko.SSHClient()
174 client.load_system_host_keys()
175 client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
176 client.connect('127.0.0.1', 10022)
177 # do some operations with client session
178 client.close()
179
180 print('FINISH!')
181
182 Example 3
183 ---------
184
185 Example of a port forwarding for the Vagrant MySQL local port:
186
187 .. code-block:: python
188
189 from sshtunnel import open_tunnel
190 from time import sleep
191
192 with open_tunnel(
193 ('localhost', 2222),
194 ssh_username="vagrant",
195 ssh_password="vagrant",
196 remote_bind_address=('127.0.0.1', 3306)
197 ) as server:
198
199 print(server.local_bind_port)
200 while True:
201 # press Ctrl-C for stopping
202 sleep(1)
203
204 print('FINISH!')
205
206 Or simply using the CLI:
207
208 .. code-block:: console
209
210 (bash)$ python -m sshtunnel -U vagrant -P vagrant -L :3306 -R 127.0.0.1:3306 -p 2222 localhost
211
212 Example 4
213 ---------
214
215 Opening an SSH session jumping over two tunnels. SSH transport and tunnels
216 will be daemonised, which will not wait for the connections to stop at close
217 time.
218
219 .. code-block:: python
220
221 import sshtunnel
222 from paramiko import SSHClient
223
224
225 with sshtunnel.open_tunnel(
226 ssh_address_or_host=('GW1_ip', 20022),
227 remote_bind_address=('GW2_ip', 22),
228 ) as tunnel1:
229 print('Connection to tunnel1 (GW1_ip:GW1_port) OK...')
230 with sshtunnel.open_tunnel(
231 ssh_address_or_host=('localhost', tunnel1.local_bind_port),
232 remote_bind_address=('target_ip', 22),
233 ssh_username='GW2_user',
234 ssh_password='GW2_pwd',
235 ) as tunnel2:
236 print('Connection to tunnel2 (GW2_ip:GW2_port) OK...')
237 with SSHClient() as ssh:
238 ssh.connect('localhost',
239 port=tunnel2.local_bind_port,
240 username='target_user',
241 password='target_pwd',
242 )
243 ssh.exec_command(...)
244
245
246 CLI usage
247 =========
248
249 ::
250
251 $ sshtunnel --help
252 usage: sshtunnel [-h] [-U SSH_USERNAME] [-p SSH_PORT] [-P SSH_PASSWORD] -R
253 IP:PORT [IP:PORT ...] [-L [IP:PORT [IP:PORT ...]]]
254 [-k SSH_HOST_KEY] [-K KEY_FILE] [-S KEY_PASSWORD] [-t] [-v]
255 [-V] [-x IP:PORT] [-c SSH_CONFIG_FILE] [-z] [-n]
256 [-d [FOLDER [FOLDER ...]]]
257 ssh_address
258
259 Pure python ssh tunnel utils
260 Version 0.4.0
261
262 positional arguments:
263 ssh_address SSH server IP address (GW for SSH tunnels)
264 set with "-- ssh_address" if immediately after -R or -L
265
266 optional arguments:
267 -h, --help show this help message and exit
268 -U SSH_USERNAME, --username SSH_USERNAME
269 SSH server account username
270 -p SSH_PORT, --server_port SSH_PORT
271 SSH server TCP port (default: 22)
272 -P SSH_PASSWORD, --password SSH_PASSWORD
273 SSH server account password
274 -R IP:PORT [IP:PORT ...], --remote_bind_address IP:PORT [IP:PORT ...]
275 Remote bind address sequence: ip_1:port_1 ip_2:port_2 ... ip_n:port_n
276 Equivalent to ssh -Lxxxx:IP_ADDRESS:PORT
277 If port is omitted, defaults to 22.
278 Example: -R 10.10.10.10: 10.10.10.10:5900
279 -L [IP:PORT [IP:PORT ...]], --local_bind_address [IP:PORT [IP:PORT ...]]
280 Local bind address sequence: ip_1:port_1 ip_2:port_2 ... ip_n:port_n
281 Elements may also be valid UNIX socket domains:
282 /tmp/foo.sock /tmp/bar.sock ... /tmp/baz.sock
283 Equivalent to ssh -LPORT:xxxxxxxxx:xxxx, being the local IP address optional.
284 By default it will listen in all interfaces (0.0.0.0) and choose a random port.
285 Example: -L :40000
286 -k SSH_HOST_KEY, --ssh_host_key SSH_HOST_KEY
287 Gateway's host key
288 -K KEY_FILE, --private_key_file KEY_FILE
289 RSA/DSS/ECDSA private key file
290 -S KEY_PASSWORD, --private_key_password KEY_PASSWORD
291 RSA/DSS/ECDSA private key password
292 -t, --threaded Allow concurrent connections to each tunnel
293 -v, --verbose Increase output verbosity (default: ERROR)
294 -V, --version Show version number and quit
295 -x IP:PORT, --proxy IP:PORT
296 IP and port of SSH proxy to destination
297 -c SSH_CONFIG_FILE, --config SSH_CONFIG_FILE
298 SSH configuration file, defaults to ~/.ssh/config
299 -z, --compress Request server for compression over SSH transport
300 -n, --noagent Disable looking for keys from an SSH agent
301 -d [FOLDER [FOLDER ...]], --host_pkey_directories [FOLDER [FOLDER ...]]
302 List of directories where SSH pkeys (in the format `id_*`) may be found
303
304 .. _Pahaz: https://github.com/pahaz
305 .. _sshtunnel: https://pypi.python.org/pypi/sshtunnel
306 .. _paramiko: http://www.paramiko.org/
307 .. |CircleCI| image:: https://circleci.com/gh/pahaz/sshtunnel.svg?style=svg
308 :target: https://circleci.com/gh/pahaz/sshtunnel
309 .. |AppVeyor| image:: https://ci.appveyor.com/api/projects/status/oxg1vx2ycmnw3xr9?svg=true&passingText=Windows%20-%20OK&failingText=Windows%20-%20Fail
310 :target: https://ci.appveyor.com/project/pahaz/sshtunnel
311 .. |readthedocs| image:: https://readthedocs.org/projects/sshtunnel/badge/?version=latest
312 :target: http://sshtunnel.readthedocs.io/en/latest/?badge=latest
313 :alt: Documentation Status
314 .. |coveralls| image:: https://coveralls.io/repos/github/pahaz/sshtunnel/badge.svg?branch=master
315 :target: https://coveralls.io/github/pahaz/sshtunnel?branch=master
316 .. |pyversions| image:: https://img.shields.io/pypi/pyversions/sshtunnel.svg
317 .. |version| image:: https://img.shields.io/pypi/v/sshtunnel.svg
318 :target: `sshtunnel`_
319 .. |license| image:: https://img.shields.io/pypi/l/sshtunnel.svg
320 :target: https://github.com/pahaz/sshtunnel/blob/master/LICENSE
321
322 Online documentation
323 ====================
324
325 Documentation may be found at `readthedocs`_.
326
327 .. _readthedocs: https://sshtunnel.readthedocs.org/
328
329 CONTRIBUTORS
330 ============
331
332 - `Cameron Maske`_
333 - `Gustavo Machado`_
334 - `Colin Jermain`_
335 - `JM Fernández`_ - (big thanks!)
336 - `Lewis Thompson`_
337 - `Erik Rogers`_
338 - `Mart Sõmermaa`_
339 - `Chronial`_
340 - `Dan Harbin`_
341 - `Ignacio Peluffo`_
342 - `Niels Zeilemaker`_
343 - `Georgy Rylov`_
344 - `Eddie Chiang`_
345 - `kkrasovskii`_
346
347 CHANGELOG
348 =========
349
350 - v.0.4.0 (`Pahaz`_)
351 + Change the daemon mod flag for all tunnel threads (is not fully backward compatible) to prevent unexpected hangs (`#219`_)
352 + Add docker based end to end functinal tests for Mongo/Postgres/MySQL (`#219`_)
353 + Add docker based end to end hangs tests (`#219`_)
354
355 - v.0.3.2 (`Pahaz`_, `JM Fernández`_)
356 + Fix host key directory detection
357 + Unify default ssh config folder to `~/.ssh`
358
359 - v.0.3.1 (`Pahaz`_)
360 + Increase open connection timeout to 10 secods
361
362 - v.0.3.0 (`Pahaz`_)
363 + Change default with context behavior to use `.stop(force=True)` on exit (is not fully backward compatible)
364 + Remove useless `daemon_forward_servers = True` hack for hangs prevention (is not fully backward compatible)
365 + Set transport keepalive to 5 second by default (disabled for version < 0.3.0)
366 + Set default transport timeout to 0.1
367 + Deprecate and remove `block_on_close` option
368 + Fix "deadlocks" / "tunneling hangs" (`#173`_, `#201`_, `#162`_, `#211`_)
369
370 - v.0.2.2 (`Pahaz`_)
371 + Add `.stop(force=True)` for force close active connections (`#201`_)
372
373 - v.0.2.1 (`Pahaz`_, `Eddie Chiang`_ and `kkrasovskii`_)
374 + Fixes bug with orphan thread for a tunnel that is DOWN (`#170`_)
375
376 - v.0.2.0 (`Georgy Rylov`_)
377 + Support IPv6 without proxy command. Use built-in paramiko create socket logic. The logic tries to use ipv6 socket family first, then ipv4 socket family.
378
379 - v.0.1.5 (`JM Fernández`_)
380 + Introduce `block_on_close` attribute
381
382 - v.0.1.4 (`Niels Zeilemaker`_)
383 + Allow loading pkeys from `~/.ssh`
384
385 - v.0.1.3 (`Ignacio Peluffo`_ and others)
386 + ``pkey_file`` parameter updated to accept relative paths to user folder using ``~``
387 + Several bugfixes
388
389 - v.0.1.2 (`JM Fernández`_)
390 + Fix #77
391
392 - v.0.1.1 (`JM Fernández`_)
393 + Fix #72
394
395 - v.0.1.0 (`JM Fernández`_)
396 + Add `tunnel_bindings` property
397 + Several bugfixes (#49, #56, #57, #59, #60, #62, #64, #66, ...)
398 (`Pahaz`_, `JM Fernández`_)
399 + Add TRACE logging level (`JM Fernández`_)
400 + Code and tests refactoring (`JM Fernández`_)
401 + Drop python3.2 support
402
403 - v.0.0.8 (`JM Fernández`_)
404 + Merge `#31`_: Support Unix domain socket (local) forwarding (`Dan Harbin`_)
405 + Simplify API (`JM Fernández`_)
406 + Add sphinx-based documentation (`JM Fernández`_)
407 + Add ``allow_agent`` (fixes `#36`_, `#46`_) (`JM Fernández`_)
408 + Add ``compression`` (`JM Fernández`_)
409 + Add ``__str__`` method (`JM Fernández`_)
410 + Add test functions (`JM Fernández`_)
411 + Fix default username when not provided and ssh_config file is skipped (`JM Fernández`_)
412 + Fix gateway IP unresolvable exception catching (`JM Fernández`_)
413 + Minor fixes (`JM Fernández`_)
414 + Add AppVeyor support (`JM Fernández`_)
415
416 - v.0.0.7 (`JM Fernández`_)
417 + Tunnels can now be stopped and started safely (`#41`_) (`JM Fernández`_)
418 + Add timeout to SSH gateway and keep-alive messages (`#29`_) (`JM Fernández`_)
419 + Allow sending a pkey directly (`#43`_) (`Chronial`_)
420 + Add ``-V`` CLI option to show current version (`JM Fernández`_)
421 + Add coverage (`JM Fernández`_)
422 + Refactoring (`JM Fernández`_)
423
424 - v.0.0.6 (`Pahaz`_)
425 + add ``-S`` CLI options for ssh private key password support (`Pahaz`_)
426
427 - v.0.0.5 (`Pahaz`_)
428 + add ``ssh_proxy`` argument, as well as ``ssh_config(5)`` ``ProxyCommand`` support (`Lewis Thompson`_)
429 + add some python 2.6 compatibility fixes (`Mart Sõmermaa`_)
430 + ``paramiko.transport`` inherits handlers of loggers passed to ``SSHTunnelForwarder`` (`JM Fernández`_)
431 + fix `#34`_, `#33`_, code style and docs (`JM Fernández`_)
432 + add tests (`Pahaz`_)
433 + add CI integration (`Pahaz`_)
434 + normal packaging (`Pahaz`_)
435 + disable check distenation socket connection by ``SSHTunnelForwarder.local_is_up`` (`Pahaz`_) [changed default behavior]
436 + use daemon mode = False in all threads by default; detail_ (`Pahaz`_) [changed default behavior]
437
438 - v.0.0.4.4 (`Pahaz`_)
439 + fix issue `#24`_ - hide ssh password in logs (`Pahaz`_)
440
441 - v.0.0.4.3 (`Pahaz`_)
442 + fix default port issue `#19`_ (`Pahaz`_)
443
444 - v.0.0.4.2 (`Pahaz`_)
445 + fix Thread.daemon mode for Python < 3.3 `#16`_, `#21`_ (`Lewis Thompson`_, `Erik Rogers`_)
446
447 - v.0.0.4.1 (`Pahaz`_)
448 + fix CLI issues `#13`_ (`Pahaz`_)
449
450 - v.0.0.4 (`Pahaz`_)
451 + daemon mode by default for all threads (`JM Fernández`_, `Pahaz`_) - *incompatible*
452 + move ``make_ssh_forward_server`` to ``SSHTunnelForwarder.make_ssh_forward_server`` (`Pahaz`_, `JM Fernández`_) - *incompatible*
453 + move ``make_ssh_forward_handler`` to ``SSHTunnelForwarder.make_ssh_forward_handler_class`` (`Pahaz`_, `JM Fernández`_) - *incompatible*
454 + rename ``open`` to ``open_tunnel`` (`JM Fernández`_) - *incompatible*
455 + add CLI interface (`JM Fernández`_)
456 + support opening several tunnels at once (`JM Fernández`_)
457 + improve stability and readability (`JM Fernández`_, `Pahaz`_)
458 + improve logging (`JM Fernández`_, `Pahaz`_)
459 + add ``raise_exception_if_any_forwarder_have_a_problem`` argument for opening several tunnels at once (`Pahaz`_)
460 + add ``ssh_config_file`` argument support (`JM Fernández`_)
461 + add Python 3 support (`JM Fernández`_, `Pahaz`_)
462
463 - v.0.0.3 (`Pahaz`_)
464 + add ``threaded`` option (`Cameron Maske`_)
465 + fix exception error message, correctly printing destination address (`Gustavo Machado`_)
466 + fix ``pip install`` failure (`Colin Jermain`_, `Pahaz`_)
467
468 - v.0.0.1 (`Pahaz`_)
469 + ``SSHTunnelForwarder`` class (`Pahaz`_)
470 + ``open`` function (`Pahaz`_)
471
472
473 .. _Pahaz: https://github.com/pahaz
474 .. _Cameron Maske: https://github.com/cameronmaske
475 .. _Gustavo Machado: https://github.com/gdmachado
476 .. _Colin Jermain: https://github.com/cjermain
477 .. _JM Fernández: https://github.com/fernandezcuesta
478 .. _Lewis Thompson: https://github.com/lewisthompson
479 .. _Erik Rogers: https://github.com/ewrogers
480 .. _Mart Sõmermaa: https://github.com/mrts
481 .. _Chronial: https://github.com/Chronial
482 .. _Dan Harbin: https://github.com/RasterBurn
483 .. _Ignacio Peluffo: https://github.com/ipeluffo
484 .. _Niels Zeilemaker: https://github.com/NielsZeilemaker
485 .. _Georgy Rylov: https://github.com/g0djan
486 .. _Eddie Chiang: https://github.com/eddie-chiang
487 .. _kkrasovskii: https://github.com/kkrasovskii
488 .. _#13: https://github.com/pahaz/sshtunnel/issues/13
489 .. _#16: https://github.com/pahaz/sshtunnel/issues/16
490 .. _#19: https://github.com/pahaz/sshtunnel/issues/19
491 .. _#21: https://github.com/pahaz/sshtunnel/issues/21
492 .. _#24: https://github.com/pahaz/sshtunnel/issues/24
493 .. _#29: https://github.com/pahaz/sshtunnel/issues/29
494 .. _#31: https://github.com/pahaz/sshtunnel/issues/31
495 .. _#33: https://github.com/pahaz/sshtunnel/issues/33
496 .. _#34: https://github.com/pahaz/sshtunnel/issues/34
497 .. _#36: https://github.com/pahaz/sshtunnel/issues/36
498 .. _#41: https://github.com/pahaz/sshtunnel/issues/41
499 .. _#43: https://github.com/pahaz/sshtunnel/issues/43
500 .. _#46: https://github.com/pahaz/sshtunnel/issues/46
501 .. _#170: https://github.com/pahaz/sshtunnel/issues/170
502 .. _#201: https://github.com/pahaz/sshtunnel/issues/201
503 .. _#162: https://github.com/pahaz/sshtunnel/issues/162
504 .. _#173: https://github.com/pahaz/sshtunnel/issues/173
505 .. _#201: https://github.com/pahaz/sshtunnel/issues/201
506 .. _#211: https://github.com/pahaz/sshtunnel/issues/211
507 .. _#219: https://github.com/pahaz/sshtunnel/issues/219
508 .. _detail: https://github.com/pahaz/sshtunnel/commit/64af238b799b0e0057c4f9b386cda247e0006da9#diff-76bc1662a114401c2954deb92b740081R127
33 Troubleshoot.rst
44 changelog.rst
55 docs.rst
6 pyproject.toml
67 setup.cfg
78 setup.py
89 sshtunnel.py
910 docs/Makefile
1011 docs/conf.py
1112 docs/index.rst
12 docs/requirements-docs.txt
13 docs/requirements.txt
14 e2e_tests/docker-compose.yaml
15 e2e_tests/run_docker_e2e_db_tests.py
16 e2e_tests/run_docker_e2e_hangs_tests.py
17 e2e_tests/ssh-server-config/ssh_host_dsa_key
18 e2e_tests/ssh-server-config/ssh_host_dsa_key.pub
19 e2e_tests/ssh-server-config/ssh_host_ecdsa_key
20 e2e_tests/ssh-server-config/ssh_host_ecdsa_key.pub
21 e2e_tests/ssh-server-config/ssh_host_ed25519_key
22 e2e_tests/ssh-server-config/ssh_host_ed25519_key.pub
23 e2e_tests/ssh-server-config/ssh_host_rsa_key
24 e2e_tests/ssh-server-config/ssh_host_rsa_key.pub
25 e2e_tests/ssh-server-config/sshd_config
1326 sshtunnel.egg-info/PKG-INFO
1427 sshtunnel.egg-info/SOURCES.txt
1528 sshtunnel.egg-info/dependency_links.txt
1730 sshtunnel.egg-info/requires.txt
1831 sshtunnel.egg-info/top_level.txt
1932 tests/__init__.py
20 tests/__init__.pyc
33 tests/requirements-syntax.txt
34 tests/requirements.txt
2135 tests/test_forwarder.py
2236 tests/testconfig
2337 tests/testrsa.key
00 [console_scripts]
11 sshtunnel = sshtunnel:_cli_main
2
0 paramiko>=1.15.2
0 paramiko>=2.7.2
11
22 [build_sphinx]
33 sphinx
2828 import SocketServer as socketserver
2929 string_types = basestring, # noqa
3030 input_ = raw_input # noqa
31 else:
31 else: # pragma: no cover
3232 import queue
3333 import socketserver
3434 string_types = str
3535 input_ = input
3636
3737
38 __version__ = '0.1.4'
38 __version__ = '0.4.0'
3939 __author__ = 'pahaz'
4040
4141
42 DEFAULT_LOGLEVEL = logging.ERROR #: default level if no logger passed (ERROR)
43 TUNNEL_TIMEOUT = 1.0 #: Timeout (seconds) for tunnel connection
44 DAEMON = False
45 TRACE_LEVEL = 1
42 #: Timeout (seconds) for transport socket (``socket.settimeout``)
43 SSH_TIMEOUT = 0.1 # ``None`` may cause a block of transport thread
44 #: Timeout (seconds) for tunnel connection (open_channel timeout)
45 TUNNEL_TIMEOUT = 10.0
46
47 _DAEMON = True #: Use daemon threads in connections
4648 _CONNECTION_COUNTER = 1
4749 _LOCK = threading.Lock()
48 #: Timeout (seconds) for the connection to the SSH gateway, ``None`` to disable
49 SSH_TIMEOUT = None
50 DEPRECATIONS = {
50 _DEPRECATIONS = {
5151 'ssh_address': 'ssh_address_or_host',
5252 'ssh_host': 'ssh_address_or_host',
5353 'ssh_private_key': 'ssh_pkey',
5454 'raise_exception_if_any_forwarder_have_a_problem': 'mute_exceptions'
5555 }
5656
57 # logging
58 DEFAULT_LOGLEVEL = logging.ERROR #: default level if no logger passed (ERROR)
59 TRACE_LEVEL = 1
5760 logging.addLevelName(TRACE_LEVEL, 'TRACE')
58
59 if os.name == 'posix':
60 DEFAULT_SSH_DIRECTORY = '~/.ssh'
61 UnixStreamServer = socketserver.UnixStreamServer
62 else:
63 DEFAULT_SSH_DIRECTORY = '~/ssh'
64 UnixStreamServer = socketserver.TCPServer
61 DEFAULT_SSH_DIRECTORY = '~/.ssh'
62
63 _StreamServer = socketserver.UnixStreamServer if os.name == 'posix' \
64 else socketserver.TCPServer
6565
6666 #: Path of optional ssh configuration file
67 DEFAULT_SSH_DIRECTORY = '~/.ssh'
6768 SSH_CONFIG_FILE = os.path.join(DEFAULT_SSH_DIRECTORY, 'config')
6869
6970 ########################
189190 :class:`logging.Logger`
190191 """
191192 logger = logger or logging.getLogger(
192 '{0}.SSHTunnelForwarder'.format(__name__)
193 'sshtunnel.SSHTunnelForwarder'
193194 )
194195 if not any(isinstance(x, logging.Handler) for x in logger.handlers):
195196 logger.setLevel(loglevel or DEFAULT_LOGLEVEL)
304305 while chan.active:
305306 rqst, _, _ = select([self.request, chan], [], [], 5)
306307 if self.request in rqst:
307 data = self.request.recv(1024)
308 data = self.request.recv(16384)
308309 if not data:
310 self.logger.log(
311 TRACE_LEVEL,
312 '>>> OUT {0} recv empty data >>>'.format(self.info)
313 )
309314 break
310 self.logger.log(TRACE_LEVEL,
311 '>>> OUT {0} send to {1}: {2} >>>'.format(
312 self.info,
313 self.remote_address,
314 hexlify(data)
315 ))
316 chan.send(data)
315 if self.logger.isEnabledFor(TRACE_LEVEL):
316 self.logger.log(
317 TRACE_LEVEL,
318 '>>> OUT {0} send to {1}: {2} >>>'.format(
319 self.info,
320 self.remote_address,
321 hexlify(data)
322 )
323 )
324 chan.sendall(data)
317325 if chan in rqst: # else
318326 if not chan.recv_ready():
327 self.logger.log(
328 TRACE_LEVEL,
329 '<<< IN {0} recv is not ready <<<'.format(self.info)
330 )
319331 break
320 data = chan.recv(1024)
321 self.logger.log(
322 TRACE_LEVEL,
323 '<<< IN {0} recv: {1} <<<'.format(self.info, hexlify(data))
324 )
325 self.request.send(data)
332 data = chan.recv(16384)
333 if self.logger.isEnabledFor(TRACE_LEVEL):
334 self.logger.log(
335 TRACE_LEVEL,
336 '<<< IN {0} recv: {1} <<<'.format(self.info, hexlify(data))
337 )
338 self.request.sendall(data)
326339
327340 def handle(self):
328341 uid = get_connection_id()
338351 src_addr=src_address,
339352 timeout=TUNNEL_TIMEOUT
340353 )
341 except paramiko.SSHException:
342 chan = None
343 if chan is None:
344 msg = '{0} to {1} was rejected by the SSH server'.format(
345 self.info,
346 self.remote_address
347 )
348 self.logger.log(TRACE_LEVEL, msg)
349 raise HandlerSSHTunnelForwarderError(msg)
354 except Exception as e: # pragma: no cover
355 msg_tupe = 'ssh ' if isinstance(e, paramiko.SSHException) else ''
356 exc_msg = 'open new channel {0}error: {1}'.format(msg_tupe, e)
357 log_msg = '{0} {1}'.format(self.info, exc_msg)
358 self.logger.log(TRACE_LEVEL, log_msg)
359 raise HandlerSSHTunnelForwarderError(exc_msg)
350360
351361 self.logger.log(TRACE_LEVEL, '{0} connected'.format(self.info))
352362 try:
375385
376386 def __init__(self, *args, **kwargs):
377387 self.logger = create_logger(kwargs.pop('logger', None))
378 self.tunnel_ok = queue.Queue()
388 self.tunnel_ok = queue.Queue(1)
379389 socketserver.TCPServer.__init__(self, *args, **kwargs)
380390
381391 def handle_error(self, request, client_address):
382392 (exc_class, exc, tb) = sys.exc_info()
383 self.logger.error('Could not establish connection from {0} to remote '
384 'side of the tunnel'.format(request.getsockname()))
385 self.tunnel_ok.put(False)
393 local_side = request.getsockname()
394 remote_side = self.remote_address
395 self.logger.error('Could not establish connection from local {0} '
396 'to remote {1} side of the tunnel: {2}'
397 .format(local_side, remote_side, exc))
398 try:
399 self.tunnel_ok.put(False, block=False, timeout=0.1)
400 except queue.Full:
401 # wait untill tunnel_ok.get is called
402 pass
403 except exc:
404 self.logger.error('unexpected internal error: {0}'.format(exc))
386405
387406 @property
388407 def local_address(self):
414433 Allow concurrent connections to each tunnel
415434 """
416435 # If True, cleanly stop threads created by ThreadingMixIn when quitting
417 daemon_threads = DAEMON
418
419
420 class _UnixStreamForwardServer(UnixStreamServer):
421 """
422 Serve over UNIX domain sockets (does not work on Windows)
436 # This value is overrides by SSHTunnelForwarder.daemon_forward_servers
437 daemon_threads = _DAEMON
438
439
440 class _StreamForwardServer(_StreamServer):
441 """
442 Serve over domain sockets (does not work on Windows)
423443 """
424444
425445 def __init__(self, *args, **kwargs):
426446 self.logger = create_logger(kwargs.pop('logger', None))
427 self.tunnel_ok = queue.Queue()
428 UnixStreamServer.__init__(self, *args, **kwargs)
447 self.tunnel_ok = queue.Queue(1)
448 _StreamServer.__init__(self, *args, **kwargs)
429449
430450 @property
431451 def local_address(self):
452472 return self.RequestHandlerClass.remote_address[1]
453473
454474
455 class _ThreadingUnixStreamForwardServer(socketserver.ThreadingMixIn,
456 _UnixStreamForwardServer):
475 class _ThreadingStreamForwardServer(socketserver.ThreadingMixIn,
476 _StreamForwardServer):
457477 """
458478 Allow concurrent connections to each tunnel
459479 """
460480 # If True, cleanly stop threads created by ThreadingMixIn when quitting
461 daemon_threads = DAEMON
481 # This value is overrides by SSHTunnelForwarder.daemon_forward_servers
482 daemon_threads = _DAEMON
462483
463484
464485 class SSHTunnelForwarder(object):
613634
614635 host_pkey_directories (list):
615636 Look for pkeys in folders on this list, for example ['~/.ssh'].
616 An empty list disables this feature
617
618 Default: ``None``
637
638 Default: ``None`` (disabled)
619639
620640 .. versionadded:: 0.1.4
621641
648668 Interval in seconds defining the period in which, if no data
649669 was sent over the connection, a *'keepalive'* packet will be
650670 sent (and ignored by the remote host). This can be useful to
651 keep connections alive over a NAT
652
653 Default: 0.0 (no keepalive packets are sent)
671 keep connections alive over a NAT. You can set to 0.0 for
672 disable keepalive.
673
674 Default: 5.0 (no keepalive packets are sent)
654675
655676 .. versionadded:: 0.0.7
656677
719740
720741 """
721742 skip_tunnel_checkup = True
722 daemon_forward_servers = DAEMON #: flag tunnel threads in daemon mode
723 daemon_transport = DAEMON #: flag SSH transport thread in daemon mode
743 # This option affects the `ForwardServer` and all his threads
744 daemon_forward_servers = _DAEMON #: flag tunnel threads in daemon mode
745 # This option affect only `Transport` thread
746 daemon_transport = _DAEMON #: flag SSH transport thread in daemon mode
724747
725748 def local_is_up(self, target):
726749 """
746769 'target can be a valid UNIX domain socket.')
747770 return False
748771
749 if self.skip_tunnel_checkup: # force tunnel check at this point
772 self.check_tunnels()
773 return self.tunnel_is_up.get(target, True)
774
775 def check_tunnels(self):
776 """
777 Check that if all tunnels are established and populates
778 :attr:`.tunnel_is_up`
779 """
780 skip_tunnel_checkup = self.skip_tunnel_checkup
781 try:
782 # force tunnel check at this point
750783 self.skip_tunnel_checkup = False
751 self.check_tunnels()
752 self.skip_tunnel_checkup = True # roll it back
753 return self.tunnel_is_up.get(target, True)
754
755 def _make_ssh_forward_handler_class(self, remote_address_):
756 """
757 Make SSH Handler class
758 """
759 class Handler(_ForwardHandler):
760 remote_address = remote_address_
761 ssh_transport = self._transport
762 logger = self.logger
763 return Handler
764
765 def _make_ssh_forward_server_class(self, remote_address_):
766 return _ThreadingForwardServer if self._threaded else _ForwardServer
767
768 def _make_unix_ssh_forward_server_class(self, remote_address_):
769 return _ThreadingUnixStreamForwardServer if \
770 self._threaded else _UnixStreamForwardServer
771
772 def _make_ssh_forward_server(self, remote_address, local_bind_address):
773 """
774 Make SSH forward proxy Server class
775 """
776 _Handler = self._make_ssh_forward_handler_class(remote_address)
777 try:
778 if isinstance(local_bind_address, string_types):
779 forward_maker_class = self._make_unix_ssh_forward_server_class
780 else:
781 forward_maker_class = self._make_ssh_forward_server_class
782 _Server = forward_maker_class(remote_address)
783 ssh_forward_server = _Server(
784 local_bind_address,
785 _Handler,
786 logger=self.logger,
787 )
788
789 if ssh_forward_server:
790 ssh_forward_server.daemon_threads = self.daemon_forward_servers
791 self._server_list.append(ssh_forward_server)
792 self.tunnel_is_up[ssh_forward_server.server_address] = False
793 else:
794 self._raise(
795 BaseSSHTunnelForwarderError,
796 'Problem setting up ssh {0} <> {1} forwarder. You can '
797 'suppress this exception by using the `mute_exceptions`'
798 'argument'.format(address_to_str(local_bind_address),
799 address_to_str(remote_address))
800 )
801 except IOError:
802 self._raise(
803 BaseSSHTunnelForwarderError,
804 "Couldn't open tunnel {0} <> {1} might be in use or "
805 "destination not reachable".format(
806 address_to_str(local_bind_address),
807 address_to_str(remote_address)
808 )
809 )
810
811 def __init__(
812 self,
813 ssh_address_or_host=None,
814 ssh_config_file=SSH_CONFIG_FILE,
815 ssh_host_key=None,
816 ssh_password=None,
817 ssh_pkey=None,
818 ssh_private_key_password=None,
819 ssh_proxy=None,
820 ssh_proxy_enabled=True,
821 ssh_username=None,
822 local_bind_address=None,
823 local_bind_addresses=None,
824 logger=None,
825 mute_exceptions=False,
826 remote_bind_address=None,
827 remote_bind_addresses=None,
828 set_keepalive=0.0,
829 threaded=True, # old version False
830 compression=None,
831 allow_agent=True, # look for keys from an SSH agent
832 host_pkey_directories=None, # look for keys in ~/.ssh
833 *args,
834 **kwargs # for backwards compatibility
835 ):
836 self.logger = logger or create_logger()
837
838 # Ensure paramiko.transport has a console handler
839 _check_paramiko_handlers(logger=logger)
840
841 self.ssh_host_key = ssh_host_key
842 self.set_keepalive = set_keepalive
843 self._server_list = [] # reset server list
844 self.tunnel_is_up = {} # handle tunnel status
845 self._threaded = threaded
846 self.is_alive = False
847 # Check if deprecated arguments ssh_address or ssh_host were used
848 for deprecated_argument in ['ssh_address', 'ssh_host']:
849 ssh_address_or_host = self._process_deprecated(ssh_address_or_host,
850 deprecated_argument,
851 kwargs)
852 # other deprecated arguments
853 ssh_pkey = self._process_deprecated(ssh_pkey,
854 'ssh_private_key',
855 kwargs)
856
857 self._raise_fwd_exc = self._process_deprecated(
858 None,
859 'raise_exception_if_any_forwarder_have_a_problem',
860 kwargs) or not mute_exceptions
861
862 if isinstance(ssh_address_or_host, tuple):
863 check_address(ssh_address_or_host)
864 (ssh_host, ssh_port) = ssh_address_or_host
865 else:
866 ssh_host = ssh_address_or_host
867 ssh_port = kwargs.pop('ssh_port', None)
868
869 if kwargs:
870 raise ValueError('Unknown arguments: {0}'.format(kwargs))
871
872 # remote binds
873 self._remote_binds = self._get_binds(remote_bind_address,
874 remote_bind_addresses,
875 is_remote=True)
876 # local binds
877 self._local_binds = self._get_binds(local_bind_address,
878 local_bind_addresses)
879 self._local_binds = self._consolidate_binds(self._local_binds,
880 self._remote_binds)
881
882 (self.ssh_host,
883 self.ssh_username,
884 ssh_pkey, # still needs to go through _consolidate_auth
885 self.ssh_port,
886 self.ssh_proxy,
887 self.compression) = self._read_ssh_config(
888 ssh_host,
889 ssh_config_file,
890 ssh_username,
891 ssh_pkey,
892 ssh_port,
893 ssh_proxy if ssh_proxy_enabled else None,
894 compression,
895 self.logger
896 )
897
898 (self.ssh_password, self.ssh_pkeys) = self._consolidate_auth(
899 ssh_password=ssh_password,
900 ssh_pkey=ssh_pkey,
901 ssh_pkey_password=ssh_private_key_password,
902 allow_agent=allow_agent,
903 host_pkey_directories=host_pkey_directories,
904 logger=self.logger
905 )
906
907 check_host(self.ssh_host)
908 check_port(self.ssh_port)
909
910 self.logger.info("Connecting to gateway: {0}:{1} as user '{2}'"
911 .format(self.ssh_host,
912 self.ssh_port,
913 self.ssh_username))
914
915 self.logger.debug('Concurrent connections allowed: {0}'
916 .format(self._threaded))
917
918 @staticmethod
919 def _read_ssh_config(ssh_host,
920 ssh_config_file,
921 ssh_username=None,
922 ssh_pkey=None,
923 ssh_port=None,
924 ssh_proxy=None,
925 compression=None,
926 logger=None):
927 """
928 Read ssh_config_file and tries to look for user (ssh_username),
929 identityfile (ssh_pkey), port (ssh_port) and proxycommand
930 (ssh_proxy) entries for ssh_host
931 """
932 ssh_config = paramiko.SSHConfig()
933 if not ssh_config_file: # handle case where it's an empty string
934 ssh_config_file = None
935
936 # Try to read SSH_CONFIG_FILE
937 try:
938 # open the ssh config file
939 with open(os.path.expanduser(ssh_config_file), 'r') as f:
940 ssh_config.parse(f)
941 # looks for information for the destination system
942 hostname_info = ssh_config.lookup(ssh_host)
943 # gather settings for user, port and identity file
944 # last resort: use the 'login name' of the user
945 ssh_username = (
946 ssh_username or
947 hostname_info.get('user')
948 )
949 ssh_pkey = (
950 ssh_pkey or
951 hostname_info.get('identityfile', [None])[0]
952 )
953 ssh_host = hostname_info.get('hostname')
954 ssh_port = ssh_port or hostname_info.get('port')
955
956 proxycommand = hostname_info.get('proxycommand')
957 ssh_proxy = ssh_proxy or (paramiko.ProxyCommand(proxycommand) if
958 proxycommand else None)
959 if compression is None:
960 compression = hostname_info.get('compression', '')
961 compression = True if compression.upper() == 'YES' else False
962 except IOError:
963 if logger:
964 logger.warning(
965 'Could not read SSH configuration file: {0}'
966 .format(ssh_config_file)
967 )
968 except (AttributeError, TypeError): # ssh_config_file is None
969 if logger:
970 logger.info('Skipping loading of ssh configuration file')
784 for _srv in self._server_list:
785 self._check_tunnel(_srv)
971786 finally:
972 return (ssh_host,
973 ssh_username or getpass.getuser(),
974 ssh_pkey,
975 int(ssh_port) if ssh_port else 22, # fallback value
976 ssh_proxy,
977 compression)
978
979 @staticmethod
980 def get_agent_keys(logger=None):
981 """ Load public keys from any available SSH agent
982
983 Arguments:
984 logger (Optional[logging.Logger])
985
986 Return:
987 list
988 """
989 paramiko_agent = paramiko.Agent()
990 agent_keys = paramiko_agent.get_keys()
991 if logger:
992 logger.info('{0} keys loaded from agent'.format(len(agent_keys)))
993 return list(agent_keys)
994
995 @staticmethod
996 def get_keys(logger=None, host_pkey_directories=None, allow_agent=False):
997 """
998 Load public keys from any available SSH agent or local
999 .ssh directory.
1000
1001 Arguments:
1002 logger (Optional[logging.Logger])
1003
1004 host_pkey_directories (Optional[list[str]]):
1005 List of local directories where host SSH pkeys in the format
1006 "id_*" are searched. For example, ['~/.ssh']
1007
1008 .. versionadded:: 0.1.0
1009
1010 allow_agent (Optional[boolean]):
1011 Whether or not load keys from agent
1012
1013 Default: False
1014
1015 Return:
1016 list
1017 """
1018 keys = SSHTunnelForwarder.get_agent_keys(logger=logger) \
1019 if allow_agent else []
1020
1021 if host_pkey_directories is not None:
1022 paramiko_key_types = {'rsa': paramiko.RSAKey,
1023 'dsa': paramiko.DSSKey,
1024 'ecdsa': paramiko.ECDSAKey,
1025 'ed25519': paramiko.Ed25519Key}
1026 for directory in host_pkey_directories or [DEFAULT_SSH_DIRECTORY]:
1027 for keytype in paramiko_key_types.keys():
1028 ssh_pkey_expanded = os.path.expanduser(
1029 os.path.join(directory, 'id_{}'.format(keytype))
1030 )
1031 if os.path.isfile(ssh_pkey_expanded):
1032 ssh_pkey = SSHTunnelForwarder.read_private_key_file(
1033 pkey_file=ssh_pkey_expanded,
1034 logger=logger,
1035 key_type=paramiko_key_types[keytype]
1036 )
1037 if ssh_pkey:
1038 keys.append(ssh_pkey)
1039 if logger:
1040 logger.info('{0} keys loaded from host directory'.format(
1041 len(keys))
1042 )
1043
1044 return keys
1045
1046 @staticmethod
1047 def _consolidate_binds(local_binds, remote_binds):
1048 """
1049 Fill local_binds with defaults when no value/s were specified,
1050 leaving paramiko to decide in which local port the tunnel will be open
1051 """
1052 count = len(remote_binds) - len(local_binds)
1053 if count < 0:
1054 raise ValueError('Too many local bind addresses '
1055 '(local_bind_addresses > remote_bind_addresses)')
1056 local_binds.extend([('0.0.0.0', 0) for x in range(count)])
1057 return local_binds
1058
1059 @staticmethod
1060 def _consolidate_auth(ssh_password=None,
1061 ssh_pkey=None,
1062 ssh_pkey_password=None,
1063 allow_agent=True,
1064 host_pkey_directories=None,
1065 logger=None):
1066 """
1067 Get sure authentication information is in place.
1068 ``ssh_pkey`` may be of classes:
1069 - ``str`` - in this case it represents a private key file; public
1070 key will be obtained from it
1071 - ``paramiko.Pkey`` - it will be transparently added to loaded keys
1072
1073 """
1074 ssh_loaded_pkeys = SSHTunnelForwarder.get_keys(
1075 logger=logger,
1076 host_pkey_directories=host_pkey_directories,
1077 allow_agent=allow_agent
1078 )
1079
1080 if isinstance(ssh_pkey, string_types):
1081 ssh_pkey_expanded = os.path.expanduser(ssh_pkey)
1082 if os.path.exists(ssh_pkey_expanded):
1083 ssh_pkey = SSHTunnelForwarder.read_private_key_file(
1084 pkey_file=ssh_pkey_expanded,
1085 pkey_password=ssh_pkey_password or ssh_password,
1086 logger=logger
1087 )
1088 elif logger:
1089 logger.warning('Private key file not found: {0}'
1090 .format(ssh_pkey))
1091 if isinstance(ssh_pkey, paramiko.pkey.PKey):
1092 ssh_loaded_pkeys.append(ssh_pkey)
1093
1094 if not ssh_password and not ssh_loaded_pkeys:
1095 raise ValueError('No password or public key available!')
1096 return (ssh_password, ssh_loaded_pkeys)
1097
1098 def _raise(self, exception=BaseSSHTunnelForwarderError, reason=None):
1099 if self._raise_fwd_exc:
1100 raise exception(reason)
1101 else:
1102 self.logger.error(repr(exception(reason)))
1103
1104 def _get_transport(self):
1105 """ Return the SSH transport to the remote gateway """
1106 if self.ssh_proxy:
1107 if isinstance(self.ssh_proxy, paramiko.proxy.ProxyCommand):
1108 proxy_repr = repr(self.ssh_proxy.cmd[1])
1109 else:
1110 proxy_repr = repr(self.ssh_proxy)
1111 self.logger.debug('Connecting via proxy: {0}'.format(proxy_repr))
1112 _socket = self.ssh_proxy
1113 else:
1114 _socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1115 if isinstance(_socket, socket.socket):
1116 _socket.settimeout(SSH_TIMEOUT)
1117 _socket.connect((self.ssh_host, self.ssh_port))
1118 transport = paramiko.Transport(_socket)
1119 transport.set_keepalive(self.set_keepalive)
1120 transport.use_compression(compress=self.compression)
1121 transport.daemon = self.daemon_transport
1122
1123 return transport
1124
1125 def _create_tunnels(self):
1126 """
1127 Create SSH tunnels on top of a transport to the remote gateway
1128 """
1129 if not self.is_active:
1130 try:
1131 self._connect_to_gateway()
1132 except socket.gaierror: # raised by paramiko.Transport
1133 msg = 'Could not resolve IP address for {0}, aborting!' \
1134 .format(self.ssh_host)
1135 self.logger.error(msg)
1136 return
1137 except (paramiko.SSHException, socket.error) as e:
1138 template = 'Could not connect to gateway {0}:{1} : {2}'
1139 msg = template.format(self.ssh_host, self.ssh_port, e.args[0])
1140 self.logger.error(msg)
1141 return
1142 for (rem, loc) in zip(self._remote_binds, self._local_binds):
1143 try:
1144 self._make_ssh_forward_server(rem, loc)
1145 except BaseSSHTunnelForwarderError as e:
1146 msg = 'Problem setting SSH Forwarder up: {0}'.format(e.value)
1147 self.logger.error(msg)
1148
1149 @staticmethod
1150 def _get_binds(bind_address, bind_addresses, is_remote=False):
1151 addr_kind = 'remote' if is_remote else 'local'
1152
1153 if not bind_address and not bind_addresses:
1154 if is_remote:
1155 raise ValueError("No {0} bind addresses specified. Use "
1156 "'{0}_bind_address' or '{0}_bind_addresses'"
1157 " argument".format(addr_kind))
1158 else:
1159 return []
1160 elif bind_address and bind_addresses:
1161 raise ValueError("You can't use both '{0}_bind_address' and "
1162 "'{0}_bind_addresses' arguments. Use one of "
1163 "them.".format(addr_kind))
1164 if bind_address:
1165 bind_addresses = [bind_address]
1166 if not is_remote:
1167 # Add random port if missing in local bind
1168 for (i, local_bind) in enumerate(bind_addresses):
1169 if isinstance(local_bind, tuple) and len(local_bind) == 1:
1170 bind_addresses[i] = (local_bind[0], 0)
1171 check_addresses(bind_addresses, is_remote)
1172 return bind_addresses
1173
1174 @staticmethod
1175 def _process_deprecated(attrib, deprecated_attrib, kwargs):
1176 """
1177 Processes optional deprecate arguments
1178 """
1179 if deprecated_attrib not in DEPRECATIONS:
1180 raise ValueError('{0} not included in deprecations list'
1181 .format(deprecated_attrib))
1182 if deprecated_attrib in kwargs:
1183 warnings.warn("'{0}' is DEPRECATED use '{1}' instead"
1184 .format(deprecated_attrib,
1185 DEPRECATIONS[deprecated_attrib]),
1186 DeprecationWarning)
1187 if attrib:
1188 raise ValueError("You can't use both '{0}' and '{1}'. "
1189 "Please only use one of them"
1190 .format(deprecated_attrib,
1191 DEPRECATIONS[deprecated_attrib]))
1192 else:
1193 return kwargs.pop(deprecated_attrib)
1194 return attrib
1195
1196 @staticmethod
1197 def read_private_key_file(pkey_file,
1198 pkey_password=None,
1199 key_type=None,
1200 logger=None):
1201 """
1202 Get SSH Public key from a private key file, given an optional password
1203
1204 Arguments:
1205 pkey_file (str):
1206 File containing a private key (RSA, DSS or ECDSA)
1207 Keyword Arguments:
1208 pkey_password (Optional[str]):
1209 Password to decrypt the private key
1210 logger (Optional[logging.Logger])
1211 Return:
1212 paramiko.Pkey
1213 """
1214 ssh_pkey = None
1215 for pkey_class in (key_type,) if key_type else (
1216 paramiko.RSAKey,
1217 paramiko.DSSKey,
1218 paramiko.ECDSAKey,
1219 paramiko.Ed25519Key
1220 ):
1221 try:
1222 ssh_pkey = pkey_class.from_private_key_file(
1223 pkey_file,
1224 password=pkey_password
1225 )
1226 if logger:
1227 logger.debug('Private key file ({0}, {1}) successfully '
1228 'loaded'.format(pkey_file, pkey_class))
1229 break
1230 except paramiko.PasswordRequiredException:
1231 if logger:
1232 logger.error('Password is required for key {0}'
1233 .format(pkey_file))
1234 break
1235 except paramiko.SSHException:
1236 if logger:
1237 logger.debug('Private key file ({0}) could not be loaded '
1238 'as type {1} or bad password'
1239 .format(pkey_file, pkey_class))
1240 return ssh_pkey
787 self.skip_tunnel_checkup = skip_tunnel_checkup # roll it back
1241788
1242789 def _check_tunnel(self, _srv):
1243790 """ Check if tunnel is already established """
1275822 finally:
1276823 s.close()
1277824
1278 def check_tunnels(self):
1279 """
1280 Check that if all tunnels are established and populates
1281 :attr:`.tunnel_is_up`
1282 """
1283 for _srv in self._server_list:
1284 self._check_tunnel(_srv)
825 def _make_ssh_forward_handler_class(self, remote_address_):
826 """
827 Make SSH Handler class
828 """
829 class Handler(_ForwardHandler):
830 remote_address = remote_address_
831 ssh_transport = self._transport
832 logger = self.logger
833 return Handler
834
835 def _make_ssh_forward_server_class(self, remote_address_):
836 return _ThreadingForwardServer if self._threaded else _ForwardServer
837
838 def _make_stream_ssh_forward_server_class(self, remote_address_):
839 return _ThreadingStreamForwardServer if self._threaded \
840 else _StreamForwardServer
841
842 def _make_ssh_forward_server(self, remote_address, local_bind_address):
843 """
844 Make SSH forward proxy Server class
845 """
846 _Handler = self._make_ssh_forward_handler_class(remote_address)
847 try:
848 forward_maker_class = self._make_stream_ssh_forward_server_class \
849 if isinstance(local_bind_address, string_types) \
850 else self._make_ssh_forward_server_class
851 _Server = forward_maker_class(remote_address)
852 ssh_forward_server = _Server(
853 local_bind_address,
854 _Handler,
855 logger=self.logger,
856 )
857
858 if ssh_forward_server:
859 ssh_forward_server.daemon_threads = self.daemon_forward_servers
860 self._server_list.append(ssh_forward_server)
861 self.tunnel_is_up[ssh_forward_server.server_address] = False
862 else:
863 self._raise(
864 BaseSSHTunnelForwarderError,
865 'Problem setting up ssh {0} <> {1} forwarder. You can '
866 'suppress this exception by using the `mute_exceptions`'
867 'argument'.format(address_to_str(local_bind_address),
868 address_to_str(remote_address))
869 )
870 except IOError:
871 self._raise(
872 BaseSSHTunnelForwarderError,
873 "Couldn't open tunnel {0} <> {1} might be in use or "
874 "destination not reachable".format(
875 address_to_str(local_bind_address),
876 address_to_str(remote_address)
877 )
878 )
879
880 def __init__(
881 self,
882 ssh_address_or_host=None,
883 ssh_config_file=SSH_CONFIG_FILE,
884 ssh_host_key=None,
885 ssh_password=None,
886 ssh_pkey=None,
887 ssh_private_key_password=None,
888 ssh_proxy=None,
889 ssh_proxy_enabled=True,
890 ssh_username=None,
891 local_bind_address=None,
892 local_bind_addresses=None,
893 logger=None,
894 mute_exceptions=False,
895 remote_bind_address=None,
896 remote_bind_addresses=None,
897 set_keepalive=5.0,
898 threaded=True, # old version False
899 compression=None,
900 allow_agent=True, # look for keys from an SSH agent
901 host_pkey_directories=None, # look for keys in ~/.ssh
902 *args,
903 **kwargs # for backwards compatibility
904 ):
905 self.logger = logger or create_logger()
906
907 # Ensure paramiko.transport has a console handler
908 _check_paramiko_handlers(logger=logger)
909
910 self.ssh_host_key = ssh_host_key
911 self.set_keepalive = set_keepalive
912 self._server_list = [] # reset server list
913 self.tunnel_is_up = {} # handle tunnel status
914 self._threaded = threaded
915 self.is_alive = False
916 # Check if deprecated arguments ssh_address or ssh_host were used
917 for deprecated_argument in ['ssh_address', 'ssh_host']:
918 ssh_address_or_host = self._process_deprecated(ssh_address_or_host,
919 deprecated_argument,
920 kwargs)
921 # other deprecated arguments
922 ssh_pkey = self._process_deprecated(ssh_pkey,
923 'ssh_private_key',
924 kwargs)
925
926 self._raise_fwd_exc = self._process_deprecated(
927 None,
928 'raise_exception_if_any_forwarder_have_a_problem',
929 kwargs) or not mute_exceptions
930
931 if isinstance(ssh_address_or_host, tuple):
932 check_address(ssh_address_or_host)
933 (ssh_host, ssh_port) = ssh_address_or_host
934 else:
935 ssh_host = ssh_address_or_host
936 ssh_port = kwargs.pop('ssh_port', None)
937
938 if kwargs:
939 raise ValueError('Unknown arguments: {0}'.format(kwargs))
940
941 # remote binds
942 self._remote_binds = self._get_binds(remote_bind_address,
943 remote_bind_addresses,
944 is_remote=True)
945 # local binds
946 self._local_binds = self._get_binds(local_bind_address,
947 local_bind_addresses)
948 self._local_binds = self._consolidate_binds(self._local_binds,
949 self._remote_binds)
950
951 (self.ssh_host,
952 self.ssh_username,
953 ssh_pkey, # still needs to go through _consolidate_auth
954 self.ssh_port,
955 self.ssh_proxy,
956 self.compression) = self._read_ssh_config(
957 ssh_host,
958 ssh_config_file,
959 ssh_username,
960 ssh_pkey,
961 ssh_port,
962 ssh_proxy if ssh_proxy_enabled else None,
963 compression,
964 self.logger
965 )
966
967 (self.ssh_password, self.ssh_pkeys) = self._consolidate_auth(
968 ssh_password=ssh_password,
969 ssh_pkey=ssh_pkey,
970 ssh_pkey_password=ssh_private_key_password,
971 allow_agent=allow_agent,
972 host_pkey_directories=host_pkey_directories,
973 logger=self.logger
974 )
975
976 check_host(self.ssh_host)
977 check_port(self.ssh_port)
978
979 self.logger.info("Connecting to gateway: {0}:{1} as user '{2}'"
980 .format(self.ssh_host,
981 self.ssh_port,
982 self.ssh_username))
983
984 self.logger.debug('Concurrent connections allowed: {0}'
985 .format(self._threaded))
986
987 @staticmethod
988 def _read_ssh_config(ssh_host,
989 ssh_config_file,
990 ssh_username=None,
991 ssh_pkey=None,
992 ssh_port=None,
993 ssh_proxy=None,
994 compression=None,
995 logger=None):
996 """
997 Read ssh_config_file and tries to look for user (ssh_username),
998 identityfile (ssh_pkey), port (ssh_port) and proxycommand
999 (ssh_proxy) entries for ssh_host
1000 """
1001 ssh_config = paramiko.SSHConfig()
1002 if not ssh_config_file: # handle case where it's an empty string
1003 ssh_config_file = None
1004
1005 # Try to read SSH_CONFIG_FILE
1006 try:
1007 # open the ssh config file
1008 with open(os.path.expanduser(ssh_config_file), 'r') as f:
1009 ssh_config.parse(f)
1010 # looks for information for the destination system
1011 hostname_info = ssh_config.lookup(ssh_host)
1012 # gather settings for user, port and identity file
1013 # last resort: use the 'login name' of the user
1014 ssh_username = (
1015 ssh_username or
1016 hostname_info.get('user')
1017 )
1018 ssh_pkey = (
1019 ssh_pkey or
1020 hostname_info.get('identityfile', [None])[0]
1021 )
1022 ssh_host = hostname_info.get('hostname')
1023 ssh_port = ssh_port or hostname_info.get('port')
1024
1025 proxycommand = hostname_info.get('proxycommand')
1026 ssh_proxy = ssh_proxy or (paramiko.ProxyCommand(proxycommand) if
1027 proxycommand else None)
1028 if compression is None:
1029 compression = hostname_info.get('compression', '')
1030 compression = True if compression.upper() == 'YES' else False
1031 except IOError:
1032 if logger:
1033 logger.warning(
1034 'Could not read SSH configuration file: {0}'
1035 .format(ssh_config_file)
1036 )
1037 except (AttributeError, TypeError): # ssh_config_file is None
1038 if logger:
1039 logger.info('Skipping loading of ssh configuration file')
1040 finally:
1041 return (ssh_host,
1042 ssh_username or getpass.getuser(),
1043 ssh_pkey,
1044 int(ssh_port) if ssh_port else 22, # fallback value
1045 ssh_proxy,
1046 compression)
1047
1048 @staticmethod
1049 def get_agent_keys(logger=None):
1050 """ Load public keys from any available SSH agent
1051
1052 Arguments:
1053 logger (Optional[logging.Logger])
1054
1055 Return:
1056 list
1057 """
1058 paramiko_agent = paramiko.Agent()
1059 agent_keys = paramiko_agent.get_keys()
1060 if logger:
1061 logger.info('{0} keys loaded from agent'.format(len(agent_keys)))
1062 return list(agent_keys)
1063
1064 @staticmethod
1065 def get_keys(logger=None, host_pkey_directories=None, allow_agent=False):
1066 """
1067 Load public keys from any available SSH agent or local
1068 .ssh directory.
1069
1070 Arguments:
1071 logger (Optional[logging.Logger])
1072
1073 host_pkey_directories (Optional[list[str]]):
1074 List of local directories where host SSH pkeys in the format
1075 "id_*" are searched. For example, ['~/.ssh']
1076
1077 .. versionadded:: 0.1.0
1078
1079 allow_agent (Optional[boolean]):
1080 Whether or not load keys from agent
1081
1082 Default: False
1083
1084 Return:
1085 list
1086 """
1087 keys = SSHTunnelForwarder.get_agent_keys(logger=logger) \
1088 if allow_agent else []
1089
1090 if host_pkey_directories is None:
1091 host_pkey_directories = [DEFAULT_SSH_DIRECTORY]
1092
1093 paramiko_key_types = {'rsa': paramiko.RSAKey,
1094 'dsa': paramiko.DSSKey,
1095 'ecdsa': paramiko.ECDSAKey}
1096 if hasattr(paramiko, 'Ed25519Key'):
1097 # NOQA: new in paramiko>=2.2: http://docs.paramiko.org/en/stable/api/keys.html#module-paramiko.ed25519key
1098 paramiko_key_types['ed25519'] = paramiko.Ed25519Key
1099 for directory in host_pkey_directories:
1100 for keytype in paramiko_key_types.keys():
1101 ssh_pkey_expanded = os.path.expanduser(
1102 os.path.join(directory, 'id_{}'.format(keytype))
1103 )
1104 try:
1105 if os.path.isfile(ssh_pkey_expanded):
1106 ssh_pkey = SSHTunnelForwarder.read_private_key_file(
1107 pkey_file=ssh_pkey_expanded,
1108 logger=logger,
1109 key_type=paramiko_key_types[keytype]
1110 )
1111 if ssh_pkey:
1112 keys.append(ssh_pkey)
1113 except OSError as exc:
1114 if logger:
1115 logger.warning('Private key file {0} check error: {1}'
1116 .format(ssh_pkey_expanded, exc))
1117 if logger:
1118 logger.info('{0} key(s) loaded'.format(len(keys)))
1119 return keys
1120
1121 @staticmethod
1122 def _consolidate_binds(local_binds, remote_binds):
1123 """
1124 Fill local_binds with defaults when no value/s were specified,
1125 leaving paramiko to decide in which local port the tunnel will be open
1126 """
1127 count = len(remote_binds) - len(local_binds)
1128 if count < 0:
1129 raise ValueError('Too many local bind addresses '
1130 '(local_bind_addresses > remote_bind_addresses)')
1131 local_binds.extend([('0.0.0.0', 0) for x in range(count)])
1132 return local_binds
1133
1134 @staticmethod
1135 def _consolidate_auth(ssh_password=None,
1136 ssh_pkey=None,
1137 ssh_pkey_password=None,
1138 allow_agent=True,
1139 host_pkey_directories=None,
1140 logger=None):
1141 """
1142 Get sure authentication information is in place.
1143 ``ssh_pkey`` may be of classes:
1144 - ``str`` - in this case it represents a private key file; public
1145 key will be obtained from it
1146 - ``paramiko.Pkey`` - it will be transparently added to loaded keys
1147
1148 """
1149 ssh_loaded_pkeys = SSHTunnelForwarder.get_keys(
1150 logger=logger,
1151 host_pkey_directories=host_pkey_directories,
1152 allow_agent=allow_agent
1153 )
1154
1155 if isinstance(ssh_pkey, string_types):
1156 ssh_pkey_expanded = os.path.expanduser(ssh_pkey)
1157 if os.path.exists(ssh_pkey_expanded):
1158 ssh_pkey = SSHTunnelForwarder.read_private_key_file(
1159 pkey_file=ssh_pkey_expanded,
1160 pkey_password=ssh_pkey_password or ssh_password,
1161 logger=logger
1162 )
1163 elif logger:
1164 logger.warning('Private key file not found: {0}'
1165 .format(ssh_pkey))
1166 if isinstance(ssh_pkey, paramiko.pkey.PKey):
1167 ssh_loaded_pkeys.insert(0, ssh_pkey)
1168
1169 if not ssh_password and not ssh_loaded_pkeys:
1170 raise ValueError('No password or public key available!')
1171 return (ssh_password, ssh_loaded_pkeys)
1172
1173 def _raise(self, exception=BaseSSHTunnelForwarderError, reason=None):
1174 if self._raise_fwd_exc:
1175 raise exception(reason)
1176 else:
1177 self.logger.error(repr(exception(reason)))
1178
1179 def _get_transport(self):
1180 """ Return the SSH transport to the remote gateway """
1181 if self.ssh_proxy:
1182 if isinstance(self.ssh_proxy, paramiko.proxy.ProxyCommand):
1183 proxy_repr = repr(self.ssh_proxy.cmd[1])
1184 else:
1185 proxy_repr = repr(self.ssh_proxy)
1186 self.logger.debug('Connecting via proxy: {0}'.format(proxy_repr))
1187 _socket = self.ssh_proxy
1188 else:
1189 _socket = (self.ssh_host, self.ssh_port)
1190 if isinstance(_socket, socket.socket):
1191 _socket.settimeout(SSH_TIMEOUT)
1192 _socket.connect((self.ssh_host, self.ssh_port))
1193 transport = paramiko.Transport(_socket)
1194 sock = transport.sock
1195 if isinstance(sock, socket.socket):
1196 sock.settimeout(SSH_TIMEOUT)
1197 transport.set_keepalive(self.set_keepalive)
1198 transport.use_compression(compress=self.compression)
1199 transport.daemon = self.daemon_transport
1200 # try to solve https://github.com/paramiko/paramiko/issues/1181
1201 # transport.banner_timeout = 200
1202 if isinstance(sock, socket.socket):
1203 sock_timeout = sock.gettimeout()
1204 sock_info = repr((sock.family, sock.type, sock.proto))
1205 self.logger.debug('Transport socket info: {0}, timeout={1}'
1206 .format(sock_info, sock_timeout))
1207 return transport
1208
1209 def _create_tunnels(self):
1210 """
1211 Create SSH tunnels on top of a transport to the remote gateway
1212 """
1213 if not self.is_active:
1214 try:
1215 self._connect_to_gateway()
1216 except socket.gaierror: # raised by paramiko.Transport
1217 msg = 'Could not resolve IP address for {0}, aborting!' \
1218 .format(self.ssh_host)
1219 self.logger.error(msg)
1220 return
1221 except (paramiko.SSHException, socket.error) as e:
1222 template = 'Could not connect to gateway {0}:{1} : {2}'
1223 msg = template.format(self.ssh_host, self.ssh_port, e.args[0])
1224 self.logger.error(msg)
1225 return
1226 for (rem, loc) in zip(self._remote_binds, self._local_binds):
1227 try:
1228 self._make_ssh_forward_server(rem, loc)
1229 except BaseSSHTunnelForwarderError as e:
1230 msg = 'Problem setting SSH Forwarder up: {0}'.format(e.value)
1231 self.logger.error(msg)
1232
1233 @staticmethod
1234 def _get_binds(bind_address, bind_addresses, is_remote=False):
1235 addr_kind = 'remote' if is_remote else 'local'
1236
1237 if not bind_address and not bind_addresses:
1238 if is_remote:
1239 raise ValueError("No {0} bind addresses specified. Use "
1240 "'{0}_bind_address' or '{0}_bind_addresses'"
1241 " argument".format(addr_kind))
1242 else:
1243 return []
1244 elif bind_address and bind_addresses:
1245 raise ValueError("You can't use both '{0}_bind_address' and "
1246 "'{0}_bind_addresses' arguments. Use one of "
1247 "them.".format(addr_kind))
1248 if bind_address:
1249 bind_addresses = [bind_address]
1250 if not is_remote:
1251 # Add random port if missing in local bind
1252 for (i, local_bind) in enumerate(bind_addresses):
1253 if isinstance(local_bind, tuple) and len(local_bind) == 1:
1254 bind_addresses[i] = (local_bind[0], 0)
1255 check_addresses(bind_addresses, is_remote)
1256 return bind_addresses
1257
1258 @staticmethod
1259 def _process_deprecated(attrib, deprecated_attrib, kwargs):
1260 """
1261 Processes optional deprecate arguments
1262 """
1263 if deprecated_attrib not in _DEPRECATIONS:
1264 raise ValueError('{0} not included in deprecations list'
1265 .format(deprecated_attrib))
1266 if deprecated_attrib in kwargs:
1267 warnings.warn("'{0}' is DEPRECATED use '{1}' instead"
1268 .format(deprecated_attrib,
1269 _DEPRECATIONS[deprecated_attrib]),
1270 DeprecationWarning)
1271 if attrib:
1272 raise ValueError("You can't use both '{0}' and '{1}'. "
1273 "Please only use one of them"
1274 .format(deprecated_attrib,
1275 _DEPRECATIONS[deprecated_attrib]))
1276 else:
1277 return kwargs.pop(deprecated_attrib)
1278 return attrib
1279
1280 @staticmethod
1281 def read_private_key_file(pkey_file,
1282 pkey_password=None,
1283 key_type=None,
1284 logger=None):
1285 """
1286 Get SSH Public key from a private key file, given an optional password
1287
1288 Arguments:
1289 pkey_file (str):
1290 File containing a private key (RSA, DSS or ECDSA)
1291 Keyword Arguments:
1292 pkey_password (Optional[str]):
1293 Password to decrypt the private key
1294 logger (Optional[logging.Logger])
1295 Return:
1296 paramiko.Pkey
1297 """
1298 ssh_pkey = None
1299 key_types = (paramiko.RSAKey, paramiko.DSSKey, paramiko.ECDSAKey)
1300 if hasattr(paramiko, 'Ed25519Key'):
1301 # NOQA: new in paramiko>=2.2: http://docs.paramiko.org/en/stable/api/keys.html#module-paramiko.ed25519key
1302 key_types += (paramiko.Ed25519Key, )
1303 for pkey_class in (key_type,) if key_type else key_types:
1304 try:
1305 ssh_pkey = pkey_class.from_private_key_file(
1306 pkey_file,
1307 password=pkey_password
1308 )
1309 if logger:
1310 logger.debug('Private key file ({0}, {1}) successfully '
1311 'loaded'.format(pkey_file, pkey_class))
1312 break
1313 except paramiko.PasswordRequiredException:
1314 if logger:
1315 logger.error('Password is required for key {0}'
1316 .format(pkey_file))
1317 break
1318 except paramiko.SSHException:
1319 if logger:
1320 logger.debug('Private key file ({0}) could not be loaded '
1321 'as type {1} or bad password'
1322 .format(pkey_file, pkey_class))
1323 return ssh_pkey
12851324
12861325 def start(self):
12871326 """ Start the SSH tunnels """
13061345 self._raise(HandlerSSHTunnelForwarderError,
13071346 'An error occurred while opening tunnels.')
13081347
1309 def stop(self):
1310 """
1311 Shut the tunnel down.
1348 def stop(self, force=False):
1349 """
1350 Shut the tunnel down. By default we are always waiting until closing
1351 all connections. You can use `force=True` to force close connections
1352
1353 Keyword Arguments:
1354 force (bool):
1355 Force close current connections
1356
1357 Default: False
1358
1359 .. versionadded:: 0.2.2
13121360
13131361 .. note:: This **had** to be handled with care before ``0.1.0``:
13141362
13291377 (address_to_str(k.local_address) for k in self._server_list)
13301378 ) or 'None'
13311379 self.logger.debug('Listening tunnels: ' + opened_address_text)
1332 self._stop_transport()
1380 self._stop_transport(force=force)
13331381 self._server_list = [] # reset server list
13341382 self.tunnel_is_up = {} # reset tunnel status
13351383
13941442 address_to_str(_srv.remote_address))
13951443 )
13961444
1397 def _stop_transport(self):
1445 def _stop_transport(self, force=False):
13981446 """ Close the underlying transport when nothing more is needed """
13991447 try:
14001448 self._check_is_started()
14011449 except (BaseSSHTunnelForwarderError,
14021450 HandlerSSHTunnelForwarderError) as e:
14031451 self.logger.warning(e)
1452 if force and self.is_active:
1453 # don't wait connections
1454 self.logger.info('Closing ssh transport')
1455 self._transport.close()
1456 self._transport.stop_thread()
14041457 for _srv in self._server_list:
1405 tunnel = _srv.local_address
1406 if self.tunnel_is_up[tunnel]:
1407 self.logger.info('Shutting down tunnel {0}'.format(tunnel))
1408 _srv.shutdown()
1458 status = 'up' if self.tunnel_is_up[_srv.local_address] else 'down'
1459 self.logger.info('Shutting down tunnel: {0} <> {1} ({2})'.format(
1460 address_to_str(_srv.local_address),
1461 address_to_str(_srv.remote_address),
1462 status
1463 ))
1464 _srv.shutdown()
14091465 _srv.server_close()
14101466 # clean up the UNIX domain socket if we're using one
1411 if isinstance(_srv, _UnixStreamForwardServer):
1467 if isinstance(_srv, _StreamForwardServer):
14121468 try:
14131469 os.unlink(_srv.local_address)
14141470 except Exception as e:
14151471 self.logger.error('Unable to unlink socket {0}: {1}'
1416 .format(self.local_address, repr(e)))
1472 .format(_srv.local_address, repr(e)))
14171473 self.is_alive = False
14181474 if self.is_active:
1475 self.logger.info('Closing ssh transport')
14191476 self._transport.close()
14201477 self._transport.stop_thread()
14211478 self.logger.debug('Transport is closed')
15321589 self.ssh_proxy.cmd[1] if self.ssh_proxy else 'no',
15331590 self.ssh_username,
15341591 credentials,
1535 self.ssh_host_key if self.ssh_host_key else'not checked',
1592 self.ssh_host_key if self.ssh_host_key else 'not checked',
15361593 '' if self.is_alive else 'not ',
15371594 'disabled' if not self.set_keepalive else
15381595 'every {0} sec'.format(self.set_keepalive),
15551612 self.__exit__()
15561613
15571614 def __exit__(self, *args):
1558 self._stop_transport()
1615 self.stop(force=True)
1616
1617 def __del__(self):
1618 if self.is_active or self.is_alive:
1619 self.logger.warning(
1620 "It looks like you didn't call the .stop() before "
1621 "the SSHTunnelForwarder obj was collected by "
1622 "the garbage collector! Running .stop(force=True)")
1623 self.stop(force=True)
15591624
15601625
15611626 def open_tunnel(*args, **kwargs):
16141679 kwargs
16151680 )
16161681
1617 ssh_port = kwargs.pop('ssh_port', None)
1682 ssh_port = kwargs.pop('ssh_port', 22)
16181683 skip_tunnel_checkup = kwargs.pop('skip_tunnel_checkup', True)
1684 block_on_close = kwargs.pop('block_on_close', None)
1685 if block_on_close:
1686 warnings.warn("'block_on_close' is DEPRECATED. You should use either"
1687 " .stop() or .stop(force=True), depends on what you do"
1688 " with the active connections. This option has no"
1689 " affect since 0.3.0",
1690 DeprecationWarning)
16191691 if not args:
16201692 if isinstance(ssh_address_or_host, tuple):
16211693 args = (ssh_address_or_host, )
18071879 return vars(parser.parse_args(args))
18081880
18091881
1810 def _cli_main(args=None):
1882 def _cli_main(args=None, **extras):
18111883 """ Pass input arguments to open_tunnel
18121884
18131885 Mandatory: ssh_address, -R (remote bind address list)
18391911 logging.DEBUG,
18401912 TRACE_LEVEL]
18411913 arguments.setdefault('debug_level', levels[verbosity])
1914 # do this while supporting py27/py34 instead of merging dicts
1915 for (extra, value) in extras.items():
1916 arguments.setdefault(extra, value)
18421917 with open_tunnel(**arguments) as tunnel:
18431918 if tunnel.is_alive:
18441919 input_('''
tests/__init__.pyc less more
Binary diff not shown
0 bashtest
1 check-manifest
2 docutils
3 flake8
4 mccabe
5 pygments
6 readme
7 twine
0 coveralls
1 mock
2 pytest
3 pytest-cov
4 pytest-xdist
5 twine
6 # readme-renderer (required by twine) 25.0 has removed support for Python 3.4
7 readme-renderer>21.0,<25.0; python_version == '3.4'
8 # try to solve CI problem
9 importlib-metadata==1.7.0; python_version == '3.5'
10 importlib-metadata==1.1.3; python_version == '3.4'
7373 ssh_config_file=None,
7474 allow_agent=False,
7575 skip_tunnel_checkup=True,
76 host_pkey_directories=[],
7677 )
7778
7879 # CONSTANTS
168169 return paramiko.AUTH_SUCCESSFUL if _ok else paramiko.AUTH_FAILED
169170
170171 def check_channel_request(self, kind, chanid):
171 self.log.debug('NullServer.check_channel_request()'
172 .format(kind, chanid))
172 self.log.debug('NullServer.check_channel_request()')
173173 return paramiko.OPEN_SUCCEEDED
174174
175175 def check_channel_exec_request(self, channel, command):
176 self.log.debug('NullServer.check_channel_exec_request()'
177 .format(channel, command))
176 self.log.debug('NullServer.check_channel_exec_request()')
178177 return True
179178
180179 def check_port_forward_request(self, address, port):
181 self.log.debug('NullServer.check_port_forward_request()'
182 .format(address, port))
180 self.log.debug('NullServer.check_port_forward_request()')
183181 return True
184182
185183 def check_global_request(self, kind, msg):
186 self.log.debug('NullServer.check_port_forward_request()'
187 .format(kind, msg))
184 self.log.debug('NullServer.check_port_forward_request()')
188185 return True
189186
190187 def check_channel_direct_tcpip_request(self, chanid, origin, destination):
412409 self.log.info('<<< forward-server received STOP signal')
413410 except socket.error:
414411 self.log.critical('{0} sending RST'.format(info))
415 except Exception as e:
416 # we reach this point usually when schan is None (paramiko bug?)
417 self.log.critical(repr(e))
412 # except Exception as e:
413 # # we reach this point usually when schan is None (paramiko bug?)
414 # self.log.critical(repr(e))
418415 finally:
419416 if schan:
420417 self.log.debug('{0} closing connection...'.format(info))
423420 self.log.debug('{0} connection closed.'.format(info))
424421
425422 def randomize_eport(self):
426 return self.eport + random.randint(1, 999)
423 return random.randint(49152, 65535)
427424
428425 def test_echo_server(self):
429426 with self._test_server(
490487 remote_bind_address=(self.eaddr, self.eport),
491488 logger=self.log,
492489 ssh_config_file=None,
493 allow_agent=False
490 allow_agent=False,
491 host_pkey_directories=[],
494492 )
495493 self.assertEqual(server.ssh_host, self.saddr)
496494 self.assertEqual(server.ssh_port, self.sport)
692690 open_tunnel(**_kwargs)
693691 logged_message = "'{0}' is DEPRECATED use '{1}' instead"\
694692 .format(deprecated_arg,
695 sshtunnel.DEPRECATIONS[deprecated_arg])
693 sshtunnel._DEPRECATIONS[deprecated_arg])
696694 self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
697695 self.assertEqual(logged_message, str(w[-1].message))
698696
712710 open_tunnel(**_kwargs)
713711 logged_message = "'{0}' is DEPRECATED use '{1}' instead"\
714712 .format(deprecated_arg,
715 sshtunnel.DEPRECATIONS[deprecated_arg])
713 sshtunnel._DEPRECATIONS[deprecated_arg])
716714 self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
717715 self.assertEqual(logged_message, str(w[-1].message))
718716
960958 ) as server:
961959 self.assertIsInstance(server.local_bind_addresses, list)
962960 self.assertListEqual(server.local_bind_addresses,
963 [l for l in zip([self.saddr] * 2,
964 server.local_bind_ports)])
961 list(zip([self.saddr] * 2,
962 server.local_bind_ports)))
965963 with self.assertRaises(sshtunnel.BaseSSHTunnelForwarderError):
966964 self.log.info(server.local_bind_address)
967965
10081006 '-R', '{0}:{1}'.format(self.eaddr,
10091007 self.eport),
10101008 '-c', '',
1011 '-n'])
1009 '-n'],
1010 host_pkey_directories=[])
10121011 self.stop_echo_and_ssh_server()
10131012
10141013 @unittest.skipIf(sys.version_info < (2, 7),
10831082 s.close
10841083 log = 'send to {0}'.format((self.eaddr, self.eport))
10851084
1086 self.assertTrue(any(log in l for l in
1085 self.assertTrue(any(log in msg for msg in
10871086 self.sshtunnel_log_messages['trace']))
10881087 # set loglevel back to the original value
10891088 logger = sshtunnel.create_logger(logger=self.log,
11241123 remote_bind_address=(self.eaddr, self.eport),
11251124 logger=self.log,
11261125 ssh_config_file=None,
1127 allow_agent=False
1126 allow_agent=False,
1127 host_pkey_directories=[],
11281128 )
11291129 try:
11301130 tunnel.daemon_forward_servers = case
11561156 local_bind_address=('', self.randomize_eport()),
11571157 logger=self.log
11581158 ) as server:
1159
1160 keys = server.get_keys(allow_agent=True)
1159 keys = server.get_keys(logger=self.log)
11611160 self.assertIsInstance(keys, list)
1162 self.assertTrue(any('keys loaded from agent' in l) for l in
1163 self.sshtunnel_log_messages['info'])
1161 self.assertFalse(any('keys loaded from agent' in msg for msg in
1162 self.sshtunnel_log_messages['info']))
11641163
11651164 with self._test_server(
11661165 (self.saddr, self.sport),
11701169 local_bind_address=('', self.randomize_eport()),
11711170 logger=self.log
11721171 ) as server:
1173 keys = server.get_keys()
1172 keys = server.get_keys(logger=self.log, allow_agent=True)
11741173 self.assertIsInstance(keys, list)
1175 self.assertFalse(any('keys loaded from agent' in l for l in
1176 self.sshtunnel_log_messages['info']))
1174 self.assertTrue(any('keys loaded from agent' in msg for msg in
1175 self.sshtunnel_log_messages['info']))
11771176
11781177 tmp_dir = tempfile.mkdtemp()
11791178 shutil.copy(get_test_data_path(PKEY_FILE),
11851184 )
11861185 self.assertIsInstance(keys, list)
11871186 self.assertTrue(
1188 any('1 keys loaded from host directory' in l
1189 for l in self.sshtunnel_log_messages['info'])
1187 any('1 key(s) loaded' in msg
1188 for msg in self.sshtunnel_log_messages['info'])
11901189 )
11911190 shutil.rmtree(tmp_dir)
11921191
13861385 'item',
13871386 kwargs.copy())
13881387
1389 def check_address(self):
1388 def test_check_address(self):
13901389 """ Test that an exception is raised with incorrect bind addresses """
13911390 address_list = [('10.0.0.1', 10000),
13921391 ('10.0.0.1', 10001)]
13931392 if os.name == 'posix': # UNIX sockets supported by the platform
13941393 address_list.append('/tmp/unix-socket')
1394 # UNIX sockets not supported on remote addresses
1395 with self.assertRaises(AssertionError):
1396 sshtunnel.check_addresses(address_list, is_remote=True)
13951397 self.assertIsNone(sshtunnel.check_addresses(address_list))
1396 # UNIX sockets not supported on remote addresses
1397 with self.assertRaises(AssertionError):
1398 sshtunnel.check_addresses(address_list, is_remote=True)
13991398 with self.assertRaises(ValueError):
14001399 sshtunnel.check_address('this is not valid')
14011400 with self.assertRaises(ValueError):