Skip to content

Commit

Permalink
Add support for connecting through SSH tunnels
Browse files Browse the repository at this point in the history
  • Loading branch information
cnelson committed Oct 22, 2015
2 parents 4bb5051 + 686ce98 commit a11dcd8
Show file tree
Hide file tree
Showing 15 changed files with 1,138 additions and 422 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,5 @@ docs/_build/

# PyBuilder
target/

.DS_Store
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

A python client for [fleet](https://github.com/coreos/fleet)

Full documentation available at [python-fleet.readthedocs.org](http://python-fleet.readthedocs.org/en/latest/). Source for the documentation is in ``fleet/v1/docs/``
Full documentation available at [python-fleet.readthedocs.org](http://python-fleet.readthedocs.org/en/latest/). Source for the documentation is in [fleet/v1/docs/](fleet/v1/docs)

## Install

Expand Down Expand Up @@ -43,6 +43,8 @@ The [fleet API documentation](https://github.com/coreos/fleet/blob/master/Docume

python-fleet will attempt to retrieve and parse this document when it is instantiated. Should any error occur during this process ``ValueError`` will be raised.

python-fleet supports connecting through SSH tunnels. See the [full Client documentation](fleet/v1/docs/client.md) for additional information on configuring SSH tunnels.

from __future__ import print_function

# connect to fleet over tcp
Expand All @@ -52,6 +54,13 @@ python-fleet will attempt to retrieve and parse this document when it is instant
print('Unable to discover fleet: {0}'.format(exc))
raise SystemExit

# or via an ssh tunnel
try:
fleet_client = fleet.Client('http://127.0.0.1:49153', ssh_tunnel='198.51.100.23')
except ValueError as exc:
print('Unable to discover fleet: {0}'.format(exc))
raise SystemExit

# or over a unix domain socket
try:
fleet_client = fleet.Client('http+unix://%2Fvar%2Frun%2Ffleet.sock')
Expand Down
3 changes: 2 additions & 1 deletion fleet/http/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from .unix_socket import * # NOQA
from .unix_socket import * # NOQA
from .ssh_tunnel import * # NOQA
82 changes: 82 additions & 0 deletions fleet/http/ssh_tunnel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
try: # pragma: no cover
# python 2
import httplib
except ImportError: # pragma: no cover
# python 3
import http.client as httplib

import httplib2 # NOQA
import sys

try: # pragma: no cover
# python 2
import urllib
unquote = urllib.unquote
except AttributeError: # pragma: no cover
# python 3
import urllib.parse
unquote = urllib.parse.unquote


class SSHTunnelProxyInfo(httplib2.ProxyInfo):
def __init__(self, sock):
"""A data structure for passing a socket to an httplib.HTTPConnection
Args:
sock (socket-like): A connected socket or socket-like object.
"""

self.sock = sock


class HTTPOverSSHTunnel(httplib.HTTPConnection):
"""
A hack for httplib2 that expects proxy_info to be a socket already connected
to our target, rather than having to call connect() ourselves. This is used
to provide basic SSH Tunnelling support.
"""

def __init__(self, host, port=None, strict=None, timeout=None, proxy_info=None):
"""
Setup an HTTP connection over an already connected socket.
Args:
host: ignored (exists for compatibility with parent)
post: ignored (exists for compatibility with parent)
strict: ignored (exists for compatibility with parent)
timeout: ignored (exists for compatibility with parent)
proxy_info (SSHTunnelProxyInfo): A SSHTunnelProxyInfo instance.
"""

# do the needful
httplib.HTTPConnection.__init__(self, host, port)

# looks like the python2 and python3 versions of httplib differ
# python2, executables any callables and returns the result as proxy_info
# python3 passes the callable directly to this function :(
if hasattr(proxy_info, '__call__'):
proxy_info = proxy_info(None)

# make sure we have a validate socket before we stash it
if not proxy_info or not isinstance(proxy_info, SSHTunnelProxyInfo) or not proxy_info.sock:
raise ValueError('This Connection must be suppplied an SSHTunnelProxyInfo via the proxy_info arg')

# keep it
self.sock = proxy_info.sock

def connect(self): # pragma: no cover
"""Do nothing"""
# we don't need to connect, this functions job is to make sure
# self.sock exists and is connected. We did that in __init__
# This is just here to keep other code in the parent from fucking
# with our already connected socket :)
pass

# Add our module to httplib2 via sorta monkey patching
# When a request is made, the class responsible for the scheme is looked up in this dict
# So we inject our schemes and capture the SSH tunnel requests
sys.modules['httplib2'].SCHEME_TO_CONNECTION['ssh+http'] = HTTPOverSSHTunnel
sys.modules['httplib2'].SCHEME_TO_CONNECTION['ssh+http+unix'] = HTTPOverSSHTunnel
2 changes: 2 additions & 0 deletions fleet/http/unix_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,6 @@ def connect(self):
raise socket.error(msg)

# Add our module to httplib2 via sorta monkey patching
# When a request is made, the class responsible for the scheme is looked up in this dict
# So we inject our schemes and capture the Unix domain requests
sys.modules['httplib2'].SCHEME_TO_CONNECTION['http+unix'] = UnixConnectionWithTimeout
Loading

0 comments on commit a11dcd8

Please sign in to comment.