Mini Shell
"""
salt.utils.vt_helper
~~~~~~~~~~~~~~~~~~~~
VT Helper
This module provides the SSHConnection to expose an SSH connection object
allowing users to programmatically execute commands on a remote server using
Salt VT.
"""
import logging
import os
import re
from salt.utils.vt import Terminal, TerminalException
SSH_PASSWORD_PROMPT_RE = re.compile(r"(?:.*)[Pp]assword(?: for .*)?:", re.M)
KEY_VALID_RE = re.compile(r".*\(yes\/no\).*")
log = logging.getLogger(__name__)
class SSHConnection:
"""
SSH Connection to a remote server.
"""
def __init__(
self,
username="salt",
password="password",
host="localhost",
key_accept=False,
prompt=r"(Cmd)",
passwd_retries=3,
linesep=os.linesep,
ssh_args="",
):
"""
Establishes a connection to the remote server.
The format for parameters is:
username (string): The username to use for this
ssh connection. Defaults to root.
password (string): The password to use for this
ssh connection. Defaults to password.
host (string): The host to connect to.
Defaults to localhost.
key_accept (boolean): Should we accept this host's key
and add it to the known_hosts file? Defaults to False.
prompt (string): The shell prompt (regex) on the server.
Prompt is compiled into a regular expression.
Defaults to (Cmd)
passwd_retries (int): How many times should I try to send the password?
Defaults to 3.
linesep (string): The line separator to use when sending
commands to the server. Defaults to os.linesep.
ssh_args (string): Extra ssh args to use with ssh.
Example: '-o PubkeyAuthentication=no'
"""
self.conn = Terminal(
f"ssh {ssh_args} -l {username} {host}",
shell=True,
log_stdout=True,
log_stdout_level="trace",
log_stderr=True,
log_stderr_level="trace",
stream_stdout=False,
stream_stderr=False,
)
sent_passwd = 0
self.prompt_re = re.compile(prompt)
self.linesep = linesep
while self.conn.has_unread_data:
stdout, stderr = self.conn.recv()
if stdout and SSH_PASSWORD_PROMPT_RE.search(stdout):
if not password:
log.error("Failure while authentication.")
raise TerminalException(
"Permission denied, no authentication information"
)
if sent_passwd < passwd_retries:
self.conn.sendline(password, self.linesep)
sent_passwd += 1
continue
else:
# asking for a password, and we can't seem to send it
raise TerminalException("Password authentication failed")
elif stdout and KEY_VALID_RE.search(stdout):
# Connecting to this server for the first time
# and need to accept key
if key_accept:
log.info("Adding %s to known_hosts", host)
self.conn.sendline("yes")
continue
else:
self.conn.sendline("no")
elif stdout and self.prompt_re.search(stdout):
# Auth success!
# We now have a prompt
break
def sendline(self, cmd):
"""
Send this command to the server and
return a tuple of the output and the stderr.
The format for parameters is:
cmd (string): The command to send to the sever.
"""
self.conn.sendline(cmd, self.linesep)
# saw_prompt = False
ret_stdout = []
ret_stderr = []
while self.conn.has_unread_data:
stdout, stderr = self.conn.recv()
if stdout:
ret_stdout.append(stdout)
if stderr:
log.debug("Error while executing command.")
ret_stderr.append(stderr)
if stdout and self.prompt_re.search(stdout):
break
return "".join(ret_stdout), "".join(ret_stderr)
def close_connection(self):
"""
Close the server connection
"""
self.conn.close(terminate=True, kill=True)
Zerion Mini Shell 1.0