# -*- 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.
.. important:
Local (i.e., per Proxy) option override the global configuration or CLI
options.
``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.
``ignore_host_keys``: ``False``
By default ssh host keys are honored and connections will ask for approval.
Use this option to disable ``StrictHostKeyChecking``.
``no_host_keys``: ``False``
Fully ignores ssh host keys which by default are honored and connections
would ask for approval. Useful if the host key of a remote server has
changed and would still error with ``ignore_host_keys``.
``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 six
import salt.client.ssh
import salt.fileclient
import salt.exceptions
import salt.utils.path
__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.
argv = [fun]
argv.extend([salt.utils.json.dumps(arg) for arg in args])
argv.extend(
[
"{0}={1}".format(
salt.utils.stringutils.to_str(key), salt.utils.json.dumps(val)
)
for key, val in six.iteritems(kwargs)
]
)
if not opts["proxy"].get("ssh_options"):
opts["proxy"]["ssh_options"] = []
if opts["proxy"].get("ignore_host_keys", False):
opts["proxy"]["ssh_options"].append("StrictHostKeyChecking=no")
if opts["proxy"].get("no_host_keys", False):
opts["proxy"]["ssh_options"].extend(
["StrictHostKeyChecking=no", "UserKnownHostsFile=/dev/null"]
)
for cli_opt in ("identities_only", "priv", "priv_passwd"):
if opts.get(cli_opt) and not opts["proxy"].get(cli_opt):
opts["proxy"][cli_opt] = opts[cli_opt]
ext_mods = salt.client.ssh.mod_data(fsclient)
conn = salt.client.ssh.Single(
opts, argv, opts["id"], fsclient=fsclient, mods=ext_mods, **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