satori/satori/tunnel.py
Samuel Stavinoha 6cabc1773d Adds Windows Support to satori discovery
This change adds the capability to satori to do data plane
discovery on Windows devices. For that, a class has been
added that mirrors the functionality of satori/ssh.py and
utilizes a "3rd party" script (satori/contrib/psexec.py)
which is called via subprocess.Popen(). Further to that, an
SSH tunneling class has been put in place that uses paramiko
to establish a tunnel (similar to running ssh -L from a shell).
requirements.txt has been extended to include impacket which
satori/contrib/psexec.py imports.

Move support for PoSH-Ohai into its own provider module.
Raise UnsupportedPlatform exceptions in ohai_solo.py
when the client is non-linux, and raise the same
exception in posh_ohai.py when the client is non-windows.

Co-Authored-By: Nico Engelen <engelen.nico@googlemail.com>
Co-Authored-By: Samuel Stavinoha <samuel.stavinoha@rackspace.com>
Change-Id: I7a94eea9446bc7f57843407fb98880222f7af6af
Implements: blueprint windows-support
2014-07-29 03:14:22 -05:00

168 lines
5.2 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""SSH tunneling module.
Set up a forward tunnel across an SSH server, using paramiko. A local port
(given with -p) is forwarded across an SSH session to an address:port from
the SSH server. This is similar to the openssh -L option.
"""
try:
import eventlet
eventlet.monkey_patch()
from eventlet.green import threading
from eventlet.green import time
except ImportError:
import threading
import time
pass
import logging
import select
import socket
try:
import SocketServer
except ImportError:
import socketserver as SocketServer
import paramiko
LOG = logging.getLogger(__name__)
class TunnelServer(SocketServer.ThreadingTCPServer):
"""Serve on a local ephemeral port.
Clients will connect to that port/server.
"""
daemon_threads = True
allow_reuse_address = True
class TunnelHandler(SocketServer.BaseRequestHandler):
"""Handle forwarding of packets."""
def handle(self):
"""Do all the work required to service a request.
The request is available as self.request, the client address as
self.client_address, and the server instance as self.server, in
case it needs to access per-server information.
This implementation will forward packets.
"""
try:
chan = self.ssh_transport.open_channel('direct-tcpip',
self.target_address,
self.request.getpeername())
except Exception as exc:
LOG.error('Incoming request to %s:%s failed',
self.target_address[0],
self.target_address[1],
exc_info=exc)
return
if chan is None:
LOG.error('Incoming request to %s:%s was rejected '
'by the SSH server.',
self.target_address[0],
self.target_address[1])
return
while True:
r, w, x = select.select([self.request, chan], [], [])
if self.request in r:
data = self.request.recv(1024)
if len(data) == 0:
break
chan.send(data)
if chan in r:
data = chan.recv(1024)
if len(data) == 0:
break
self.request.send(data)
try:
peername = None
peername = str(self.request.getpeername())
except socket.error as exc:
LOG.warning("Couldn't fetch peername.", exc_info=exc)
chan.close()
self.request.close()
LOG.info("Tunnel closed from '%s'", peername or 'unnamed peer')
class Tunnel(object): # pylint: disable=R0902
"""Create a TCP server which will use TunnelHandler."""
def __init__(self, target_host, target_port,
sshclient, tunnel_host='localhost',
tunnel_port=0):
"""Constructor."""
if not isinstance(sshclient, paramiko.SSHClient):
raise TypeError("'sshclient' must be an instance of "
"paramiko.SSHClient.")
self.target_host = target_host
self.target_port = target_port
self.target_address = (target_host, target_port)
self.address = (tunnel_host, tunnel_port)
self._tunnel = None
self._tunnel_thread = None
self.sshclient = sshclient
self._ssh_transport = self.get_sshclient_transport(
self.sshclient)
TunnelHandler.target_address = self.target_address
TunnelHandler.ssh_transport = self._ssh_transport
self._tunnel = TunnelServer(self.address, TunnelHandler)
# reset attribute to the port it has actually been set to
self.address = self._tunnel.server_address
tunnel_host, self.tunnel_port = self.address
def get_sshclient_transport(self, sshclient):
"""Get the sshclient's transport.
Connect the sshclient, that has been passed in and return its
transport.
"""
sshclient.connect()
return sshclient.get_transport()
def serve_forever(self, async=True):
"""Serve the tunnel forever.
if async is True, this will be done in a background thread
"""
if not async:
self._tunnel.serve_forever()
else:
self._tunnel_thread = threading.Thread(
target=self._tunnel.serve_forever)
self.start()
# cooperative yield
time.sleep(0)
def shutdown(self):
"""Stop serving the tunnel.
Also close the socket.
"""
self._tunnel.shutdown()
self._tunnel.socket.close()