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