"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "salt/client/ssh/shell.py" between
salt-3002.1.tar.gz and salt-3002.2.tar.gz

About: SaltStack is a systems management software for data center automation, cloud orchestration, server provisioning, configuration management and more. Community version.

shell.py  (salt-3002.1):shell.py  (salt-3002.2)
# -*- coding: utf-8 -*-
""" """
Manage transport commands via ssh Manage transport commands via ssh
""" """
from __future__ import absolute_import, print_function, unicode_literals
import logging import logging
import os import os
# Import python libs
import re import re
import shlex import shlex
import subprocess import subprocess
import sys import sys
import time import time
# Import salt libs
import salt.defaults.exitcodes import salt.defaults.exitcodes
import salt.utils.json import salt.utils.json
import salt.utils.nb_popen import salt.utils.nb_popen
import salt.utils.vt import salt.utils.vt
from salt.ext import six
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
SSH_PASSWORD_PROMPT_RE = re.compile(r"(?:.*)[Pp]assword(?: for .*)?:", re.M) SSH_PASSWORD_PROMPT_RE = re.compile(r"(?:.*)[Pp]assword(?: for .*)?:", re.M)
KEY_VALID_RE = re.compile(r".*\(yes\/no\).*") KEY_VALID_RE = re.compile(r".*\(yes\/no\).*")
SSH_PRIVATE_KEY_PASSWORD_PROMPT_RE = re.compile(r"Enter passphrase for key", re. M) SSH_PRIVATE_KEY_PASSWORD_PROMPT_RE = re.compile(r"Enter passphrase for key", re. M)
# Keep these in sync with ./__init__.py # Keep these in sync with ./__init__.py
RSTR = "_edbc7885e4f9aac9b83b35999b68d015148caf467b78fa39c05f669c0ff89878" RSTR = "_edbc7885e4f9aac9b83b35999b68d015148caf467b78fa39c05f669c0ff89878"
RSTR_RE = re.compile(r"(?:^|\r?\n)" + RSTR + r"(?:\r?\n|$)") RSTR_RE = re.compile(r"(?:^|\r?\n)" + RSTR + r"(?:\r?\n|$)")
def gen_key(path): def gen_key(path):
""" """
Generate a key for use with salt-ssh Generate a key for use with salt-ssh
""" """
cmd = ["ssh-keygen", "-P", '""', "-f", path, "-t", "rsa", "-q"] cmd = ["ssh-keygen", "-P", "", "-f", path, "-t", "rsa", "-q"]
dirname = os.path.dirname(path) dirname = os.path.dirname(path)
if dirname and not os.path.isdir(dirname): if dirname and not os.path.isdir(dirname):
os.makedirs(os.path.dirname(path)) os.makedirs(os.path.dirname(path))
subprocess.call(cmd) subprocess.call(cmd)
def gen_shell(opts, **kwargs): def gen_shell(opts, **kwargs):
""" """
Return the correct shell interface for the target system Return the correct shell interface for the target system
""" """
if kwargs["winrm"]: if kwargs["winrm"]:
skipping to change at line 60 skipping to change at line 54
import saltwinshell import saltwinshell
shell = saltwinshell.Shell(opts, **kwargs) shell = saltwinshell.Shell(opts, **kwargs)
except ImportError: except ImportError:
log.error("The saltwinshell library is not available") log.error("The saltwinshell library is not available")
sys.exit(salt.defaults.exitcodes.EX_GENERIC) sys.exit(salt.defaults.exitcodes.EX_GENERIC)
else: else:
shell = Shell(opts, **kwargs) shell = Shell(opts, **kwargs)
return shell return shell
class Shell(object): class Shell:
""" """
Create a shell connection object to encapsulate ssh executions Create a shell connection object to encapsulate ssh executions
""" """
def __init__( def __init__(
self, self,
opts, opts,
host, host,
user=None, user=None,
port=None, port=None,
skipping to change at line 89 skipping to change at line 83
sudo_user=None, sudo_user=None,
remote_port_forwards=None, remote_port_forwards=None,
winrm=False, winrm=False,
ssh_options=None, ssh_options=None,
): ):
self.opts = opts self.opts = opts
# ssh <ipv6>, but scp [<ipv6]:/path # ssh <ipv6>, but scp [<ipv6]:/path
self.host = host.strip("[]") self.host = host.strip("[]")
self.user = user self.user = user
self.port = port self.port = port
self.passwd = six.text_type(passwd) if passwd else passwd self.passwd = str(passwd) if passwd else passwd
self.priv = priv self.priv = priv
self.priv_passwd = priv_passwd self.priv_passwd = priv_passwd
self.timeout = timeout self.timeout = timeout
self.sudo = sudo self.sudo = sudo
self.tty = tty self.tty = tty
self.mods = mods self.mods = mods
self.identities_only = identities_only self.identities_only = identities_only
self.remote_port_forwards = remote_port_forwards self.remote_port_forwards = remote_port_forwards
self.ssh_options = "" if ssh_options is None else ssh_options self.ssh_options = "" if ssh_options is None else ssh_options
skipping to change at line 127 skipping to change at line 121
""" """
options = [ options = [
"KbdInteractiveAuthentication=no", "KbdInteractiveAuthentication=no",
] ]
if self.passwd: if self.passwd:
options.append("PasswordAuthentication=yes") options.append("PasswordAuthentication=yes")
else: else:
options.append("PasswordAuthentication=no") options.append("PasswordAuthentication=no")
if self.opts.get("_ssh_version", (0,)) > (4, 9): if self.opts.get("_ssh_version", (0,)) > (4, 9):
options.append("GSSAPIAuthentication=no") options.append("GSSAPIAuthentication=no")
options.append("ConnectTimeout={0}".format(self.timeout)) options.append("ConnectTimeout={}".format(self.timeout))
if self.opts.get("ignore_host_keys"): if self.opts.get("ignore_host_keys"):
options.append("StrictHostKeyChecking=no") options.append("StrictHostKeyChecking=no")
if self.opts.get("no_host_keys"): if self.opts.get("no_host_keys"):
options.extend(["StrictHostKeyChecking=no", "UserKnownHostsFile=/dev /null"]) options.extend(["StrictHostKeyChecking=no", "UserKnownHostsFile=/dev /null"])
known_hosts = self.opts.get("known_hosts_file") known_hosts = self.opts.get("known_hosts_file")
if known_hosts and os.path.isfile(known_hosts): if known_hosts and os.path.isfile(known_hosts):
options.append("UserKnownHostsFile={0}".format(known_hosts)) options.append("UserKnownHostsFile={}".format(known_hosts))
if self.port: if self.port:
options.append("Port={0}".format(self.port)) options.append("Port={}".format(self.port))
if self.priv and self.priv != "agent-forwarding": if self.priv and self.priv != "agent-forwarding":
options.append("IdentityFile={0}".format(self.priv)) options.append("IdentityFile={}".format(self.priv))
if self.user: if self.user:
options.append("User={0}".format(self.user)) options.append("User={}".format(self.user))
if self.identities_only: if self.identities_only:
options.append("IdentitiesOnly=yes") options.append("IdentitiesOnly=yes")
ret = [] ret = []
for option in options: for option in options:
ret.append("-o {0} ".format(option)) ret.append("-o {} ".format(option))
return "".join(ret) return "".join(ret)
def _passwd_opts(self): def _passwd_opts(self):
""" """
Return options to pass to ssh Return options to pass to ssh
""" """
# TODO ControlMaster does not work without ControlPath # TODO ControlMaster does not work without ControlPath
# user could take advantage of it if they set ControlPath in their # user could take advantage of it if they set ControlPath in their
# ssh config. Also, ControlPersist not widely available. # ssh config. Also, ControlPersist not widely available.
options = [ options = [
"ControlMaster=auto", "ControlMaster=auto",
"StrictHostKeyChecking=no", "StrictHostKeyChecking=no",
] ]
if self.opts["_ssh_version"] > (4, 9): if self.opts["_ssh_version"] > (4, 9):
options.append("GSSAPIAuthentication=no") options.append("GSSAPIAuthentication=no")
options.append("ConnectTimeout={0}".format(self.timeout)) options.append("ConnectTimeout={}".format(self.timeout))
if self.opts.get("ignore_host_keys"): if self.opts.get("ignore_host_keys"):
options.append("StrictHostKeyChecking=no") options.append("StrictHostKeyChecking=no")
if self.opts.get("no_host_keys"): if self.opts.get("no_host_keys"):
options.extend(["StrictHostKeyChecking=no", "UserKnownHostsFile=/dev /null"]) options.extend(["StrictHostKeyChecking=no", "UserKnownHostsFile=/dev /null"])
if self.passwd: if self.passwd:
options.extend(["PasswordAuthentication=yes", "PubkeyAuthentication= yes"]) options.extend(["PasswordAuthentication=yes", "PubkeyAuthentication= yes"])
else: else:
options.extend( options.extend(
[ [
"PasswordAuthentication=no", "PasswordAuthentication=no",
"PubkeyAuthentication=yes", "PubkeyAuthentication=yes",
"KbdInteractiveAuthentication=no", "KbdInteractiveAuthentication=no",
"ChallengeResponseAuthentication=no", "ChallengeResponseAuthentication=no",
"BatchMode=yes", "BatchMode=yes",
] ]
) )
if self.port: if self.port:
options.append("Port={0}".format(self.port)) options.append("Port={}".format(self.port))
if self.user: if self.user:
options.append("User={0}".format(self.user)) options.append("User={}".format(self.user))
if self.identities_only: if self.identities_only:
options.append("IdentitiesOnly=yes") options.append("IdentitiesOnly=yes")
ret = [] ret = []
for option in options: for option in options:
ret.append("-o {0} ".format(option)) ret.append("-o {} ".format(option))
return "".join(ret) return "".join(ret)
def _ssh_opts(self): def _ssh_opts(self):
return " ".join(["-o {0}".format(opt) for opt in self.ssh_options]) return " ".join(["-o {}".format(opt) for opt in self.ssh_options])
def _copy_id_str_old(self): def _copy_id_str_old(self):
""" """
Return the string to execute ssh-copy-id Return the string to execute ssh-copy-id
""" """
if self.passwd: if self.passwd:
# Using single quotes prevents shell expansion and # Using single quotes prevents shell expansion and
# passwords containing '$' # passwords containing '$'
return "{0} {1} '{2} -p {3} {4} {5}@{6}'".format( return "{} {} '{} -p {} {} {}@{}'".format(
"ssh-copy-id", "ssh-copy-id",
"-i {0}.pub".format(self.priv), "-i {}.pub".format(self.priv),
self._passwd_opts(), self._passwd_opts(),
self.port, self.port,
self._ssh_opts(), self._ssh_opts(),
self.user, self.user,
self.host, self.host,
) )
return None return None
def _copy_id_str_new(self): def _copy_id_str_new(self):
""" """
Since newer ssh-copy-id commands ingest option differently we need to Since newer ssh-copy-id commands ingest option differently we need to
have two commands have two commands
""" """
if self.passwd: if self.passwd:
# Using single quotes prevents shell expansion and # Using single quotes prevents shell expansion and
# passwords containing '$' # passwords containing '$'
return "{0} {1} {2} -p {3} {4} {5}@{6}".format( return "{} {} {} -p {} {} {}@{}".format(
"ssh-copy-id", "ssh-copy-id",
"-i {0}.pub".format(self.priv), "-i {}.pub".format(self.priv),
self._passwd_opts(), self._passwd_opts(),
self.port, self.port,
self._ssh_opts(), self._ssh_opts(),
self.user, self.user,
self.host, self.host,
) )
return None return None
def copy_id(self): def copy_id(self):
""" """
skipping to change at line 260 skipping to change at line 254
if ssh != "scp": if ssh != "scp":
command.append(self.host) command.append(self.host)
if self.tty and ssh == "ssh": if self.tty and ssh == "ssh":
command.append("-t -t") command.append("-t -t")
if self.passwd or self.priv: if self.passwd or self.priv:
command.append(self.priv and self._key_opts() or self._passwd_opts() ) command.append(self.priv and self._key_opts() or self._passwd_opts() )
if ssh != "scp" and self.remote_port_forwards: if ssh != "scp" and self.remote_port_forwards:
command.append( command.append(
" ".join( " ".join(
[ [
"-R {0}".format(item) "-R {}".format(item)
for item in self.remote_port_forwards.split(",") for item in self.remote_port_forwards.split(",")
] ]
) )
) )
if self.ssh_options: if self.ssh_options:
command.append(self._ssh_opts()) command.append(self._ssh_opts())
command.append(cmd) command.append(cmd)
return " ".join(command) return " ".join(command)
skipping to change at line 302 skipping to change at line 296
def exec_nb_cmd(self, cmd): def exec_nb_cmd(self, cmd):
""" """
Yield None until cmd finished Yield None until cmd finished
""" """
r_out = [] r_out = []
r_err = [] r_err = []
rcode = None rcode = None
cmd = self._cmd_str(cmd) cmd = self._cmd_str(cmd)
logmsg = "Executing non-blocking command: {0}".format(cmd) logmsg = "Executing non-blocking command: {}".format(cmd)
if self.passwd: if self.passwd:
logmsg = logmsg.replace(self.passwd, ("*" * 6)) logmsg = logmsg.replace(self.passwd, ("*" * 6))
log.debug(logmsg) log.debug(logmsg)
for out, err, rcode in self._run_nb_cmd(cmd): for out, err, rcode in self._run_nb_cmd(cmd):
if out is not None: if out is not None:
r_out.append(out) r_out.append(out)
if err is not None: if err is not None:
r_err.append(err) r_err.append(err)
yield None, None, None yield None, None, None
yield "".join(r_out), "".join(r_err), rcode yield "".join(r_out), "".join(r_err), rcode
def exec_cmd(self, cmd): def exec_cmd(self, cmd):
""" """
Execute a remote command Execute a remote command
""" """
cmd = self._cmd_str(cmd) cmd = self._cmd_str(cmd)
logmsg = "Executing command: {0}".format(cmd) logmsg = "Executing command: {}".format(cmd)
if self.passwd: if self.passwd:
logmsg = logmsg.replace(self.passwd, ("*" * 6)) logmsg = logmsg.replace(self.passwd, ("*" * 6))
if 'decode("base64")' in logmsg or "base64.b64decode(" in logmsg: if 'decode("base64")' in logmsg or "base64.b64decode(" in logmsg:
log.debug("Executed SHIM command. Command logged to TRACE") log.debug("Executed SHIM command. Command logged to TRACE")
log.trace(logmsg) log.trace(logmsg)
else: else:
log.debug(logmsg) log.debug(logmsg)
ret = self._run_cmd(cmd) ret = self._run_cmd(cmd)
return ret return ret
def send(self, local, remote, makedirs=False): def send(self, local, remote, makedirs=False):
""" """
scp a file or files to a remote system scp a file or files to a remote system
""" """
if makedirs: if makedirs:
self.exec_cmd("mkdir -p {0}".format(os.path.dirname(remote))) self.exec_cmd("mkdir -p {}".format(os.path.dirname(remote)))
# scp needs [<ipv6} # scp needs [<ipv6}
host = self.host host = self.host
if ":" in host: if ":" in host:
host = "[{0}]".format(host) host = "[{}]".format(host)
cmd = "{0} {1}:{2}".format(local, host, remote) cmd = "{} {}:{}".format(local, host, remote)
cmd = self._cmd_str(cmd, ssh="scp") cmd = self._cmd_str(cmd, ssh="scp")
logmsg = "Executing command: {0}".format(cmd) logmsg = "Executing command: {}".format(cmd)
if self.passwd: if self.passwd:
logmsg = logmsg.replace(self.passwd, ("*" * 6)) logmsg = logmsg.replace(self.passwd, ("*" * 6))
log.debug(logmsg) log.debug(logmsg)
return self._run_cmd(cmd) return self._run_cmd(cmd)
def _split_cmd(self, cmd): def _split_cmd(self, cmd):
""" """
Split a command string so that it is suitable to pass to Popen without Split a command string so that it is suitable to pass to Popen without
shell=True. This prevents shell injection attacks in the options passed shell=True. This prevents shell injection attacks in the options passed
skipping to change at line 434 skipping to change at line 428
return "", "Password authentication failed", 254 return "", "Password authentication failed", 254
elif buff and KEY_VALID_RE.search(buff): elif buff and KEY_VALID_RE.search(buff):
if key_accept: if key_accept:
term.sendline("yes") term.sendline("yes")
continue continue
else: else:
term.sendline("no") term.sendline("no")
ret_stdout = ( ret_stdout = (
"The host key needs to be accepted, to " "The host key needs to be accepted, to "
"auto accept run salt-ssh with the -i " "auto accept run salt-ssh with the -i "
"flag:\n{0}" "flag:\n{}"
).format(stdout) ).format(stdout)
return ret_stdout, "", 254 return ret_stdout, "", 254
elif buff and buff.endswith("_||ext_mods||_"): elif buff and buff.endswith("_||ext_mods||_"):
mods_raw = ( mods_raw = (
salt.utils.json.dumps(self.mods, separators=(",", ":")) salt.utils.json.dumps(self.mods, separators=(",", ":"))
+ "|_E|0|" + "|_E|0|"
) )
term.sendline(mods_raw) term.sendline(mods_raw)
if stdout: if stdout:
old_stdout = stdout old_stdout = stdout
 End of changes. 31 change blocks. 
32 lines changed or deleted 26 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)