Source code for _proxy.ssh

# -*- coding: utf-8 -*-
'''
SSH Proxy
=========

Manage a remote host via SSH, using a Proxy Minion. This module doesn't have any
external dependencies, as it makes use of the native Salt internals used for
salt-ssh, therefore managing the remote machine by uploading a lightweight Salt
version on the target host, then invokes Salt functions over SSH (using the
``ssh`` binary installed on your computer or wherever this Proxy Minion runs).

.. note::

    To manage machines running Windows, you will need to install the
    ``saltwinshell`` library.

Pillar
------

The configuration is aligned to the general Proxy Minion standards: put the
connection details and credentials under the ``proxy`` key in the Proxy config
or Pillar.

``host``
    The IP address or the hostname of the remove machine to manage.

``port``
    Integer, the port number to use when establishing he connection
    (defaults to 22).

``user``
    The username required for authentication.

``passwd``
    The password used for authentication.

``priv``
    Absolute path to the private SSH key used for authentication.

``priv_passwd``
    The SSH private key password.

``timeout``: 30
    The SSH timeout. Defaults to 30 seconds.

``sudo``: ``False``
    Execute commands as sudo.

``tty``: ``False``
    Connect over tty.

``sudo_user``
    The username that should execute the commands as sudo.

``remote_port_forwards``
    Enable remote port forwarding. Example: ``8888:my.company.server:443``.
    Multiple remote port forwardings are supported, using comma-separated
    values, e.g., ``8888:my.company.server:443,9999:my.company.server:80``.

``identities_only``: ``False``
    Execute SSH with ``-o IdentitiesOnly=yes``. This option is intended for
    situations where ssh-agent offers many different identities and allow ssh
    to ignore those identities and use the only one specified in options.

``winrm``: ``False``
    Flag that tells Salt to connect to a Windows machine. This option requires
    the ``saltwinshell`` to be installed.

Example Pillar:

.. code-block:: yaml

  proxy:
    proxytype: ssh
    host: srv.example.com
    user: test
    passwd: test
    port: 2022
'''
from __future__ import absolute_import, print_function, unicode_literals

import json
import logging

import salt.client.ssh
import salt.fileclient
import salt.exceptions
import salt.utils.path
from salt.ext import six

__proxyenabled__ = ['ssh']

log = logging.getLogger(__name__)

CONN = None
INITIALIZED = False
GRAINS_CACHE = {}


def _prep_conn(opts, fun, *args, **kwargs):
    '''
    Prepare the connection.
    '''
    opts['_ssh_version'] = salt.client.ssh.ssh_version()
    fsclient = salt.fileclient.FSClient(opts)
    # TODO: Have here more options to simplify the usage, through features like
    # auto-expand the path to the priv key, auto-discovery, etc.
    conn = salt.client.ssh.Single(
        opts, [fun], opts['id'], fsclient=fsclient, **opts['proxy']
    )
    conn.args = args
    conn.kwargs = kwargs
    thin_dir = conn.opts['thin_dir']
    thin_dir = thin_dir.replace('proxy', '')
    conn.opts['thin_dir'] = thin_dir
    conn.thin_dir = thin_dir
    return conn


[docs]def init(opts): ''' Init the SSH connection, and execute a simple call to ensure that the remote device is reachable, otherwise throw an error. ''' global CONN, INITIALIZED if not salt.utils.path.which('ssh'): raise salt.exceptions.SaltSystemExit( code=-1, msg='No ssh binary found in path -- ssh must be installed for this Proxy module. Exiting.', ) CONN = _prep_conn(opts, 'cmd.run', 'echo') INITIALIZED = True
[docs]def initialized(): ''' Proxy initialized properly? ''' return INITIALIZED
[docs]def module_executors(): ''' Return the list of executors that should invoke the Salt functions. ''' return ['ssh']
[docs]def call(fun, *args, **kwargs): ''' Call an arbitrary Salt function and return the output. ''' global CONN, INITIALIZED if not CONN or not INITIALIZED: return opts = CONN.opts opts['output'] = 'json' ssh_conn = _prep_conn(opts, fun, *args, **kwargs) ret = ssh_conn.run() if ret[2] != 0: log.error('[%s] %s', opts['id'], ret[1]) return ret[0] thin_ret = json.loads(ret[0]) if '_error' in thin_ret['local']: log.error(thin_ret['local']['_error']) if 'stdout' in thin_ret['local']: log.error(thin_ret['local']['stdout']) return thin_ret['local']['return']
[docs]def ping(): ''' Execute "echo" on the remote host to ensure it's still accessible. ''' global CONN, INITIALIZED if not CONN or not INITIALIZED: log.debug('Not connected, or not initialized') return False ret = CONN.run() log.debug(ret) return ret[2] == 0
[docs]def grains(): ''' Invoke grains.items from the thin Salt on the remote machine, in order to return here the Grains. ''' global GRAINS_CACHE if not GRAINS_CACHE: GRAINS_CACHE = call('grains.items') return GRAINS_CACHE
[docs]def shutdown(opts): ''' Buh-bye... ''' global CONN, INITIALIZED if CONN and INITIALIZED: del CONN INITIALIZED = False