Running script from remote server using ssh in python - python

I want to run my python script from my local machine. But the python script named script.py is in remote server and it has some arguments and parameters.
I have tried:
#!/usr/bin/python
import paramiko
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname='x.x.x.x', port=22, username='root', password='passwd')
stdin, stdout, stderr=ssh.exec_command('python /root/file/script.py') #It has some argument I want to use them from my local machine
for i in stdout.readlines():
print i.strip('\r\n')
ssh.close()
script.py has some arguments. how should I change this script in order to use arguments of script.py from my local machine?

It is more tricky than one would think, thanks to ssh not supporting argument lists:
import sys
try:
from pipes import quote # python 2
except ImportError:
from shlex import quote # python 3
ssh.exec_command('python /root/file/script.py ' +
' '.join([quote(i) for i in sys.argv[1:]])
)
When running python myprogram.py argument\ with" quotes\" and spaces", this should pass 'argument with quotes" and spaces' as 1 argument to the other program. Though I wouldn't vouch for paramiko always doing the right thing.

Related

Command output is corrupted when executed using Python Paramiko exec_command

I'm a software tester, trying to verify that the log on a remote QNX (a BSD variant) machine will contain the correct entries after specific actions are taken. I am able to list the contents of the directory in which the log resides, and use that information in the command to read (really want to use tail -n XX <file>) the file. So far, I always get a "(No such file or directory)" when trying to read the file.
We are using Froglogic Squish for automated testing, because the Windows UI (that interacts with the server piece on QNX) is built using Qt extensions for standard Windows elements. Squish uses Python 2.7, so I am using Python 2.7.
I am using paramiko for the SSH connection to the QNX server. This has worked great for sending commands to the simulator piece that also runs on the QNX server.
So, here's the code. Some descriptive names have been changed to avoid upsetting my employer.
import sys
import time
import select
sys.path.append(r"C:\Python27\Lib\site-packages")
sys.path.append(r"C:\Python27\Lib\site-packages\pip\_vendor")
import paramiko
# Import SSH configuration variables
ssh_host = 'vvv.xxx.yyy.zzz'
thelog_dir = "/logs/the/"
ssh_user = 'un'
ssh_pw = 'pw'
def execute_Command(fullCmd):
outptLines = []
#
# Try to connect to the host.
# Retry a few times if it fails.
#
i = 1
while True:
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(ssh_host, 22, ssh_user, ssh_pw)
break
except paramiko.AuthenticationException:
log ("Authentication failed when connecting to %s" % ssh_host)
return 1
except:
log ("Could not SSH to %s, waiting for it to start" % ssh_host)
i += 1
time.sleep(2)
# If we could not connect within time limit
if i == 30:
log ("Could not connect to %s. Giving up" % ssh_host)
return 1
# Send the command (non-blocking?)
stdin, stdout, stderr = ssh.exec_command(fullCmd, get_pty=True)
for line in iter(stdout.readline, ""):
outptLines.append(line)
#
# Disconnect from the host
#
ssh.close()
return outptLines
def get_Latest_Log():
fullCmd = "ls -1 %s | grep the_2" %thelog_dir
files = execute_Command(fullCmd)
theFile = files[-1]
return theFile
def main():
numLines = 20
theLog = get_Latest_Log()
print("\n\nThe latest log is %s\n\n" %theLog)
fullCmd = "cd /logs/the; tail -n 20 /logs/the/%s" %theLog
#fullCmd = "tail -n 20 /logs/the/%s" %theLog
print fullCmd
logLines = execute_Command(fullCmd)
for line in logLines:
print line
if __name__ == "__main__":
# execute only if run as a script
main()
I have tried to read the file using both tail and cat. I have also tried to get and open the file using Paramiko's SFTP client.
In all cases, the response of trying to read the file fails -- despite the fact that listing the contents of the directory works fine. (?!) And BTW, the log file is supposed to be readable by 'world'. Permissions are -rw-rw-r--.
The output I get is:
"C:\Users\xsat086\Documents\paramikoTest>python SSH_THE_MsgChk.py
The latest log is the_20210628_115455_205.log
cd /logs/the; tail -n 20 /logs/the/the_20210628_115455_205.log
(No such file or directory)the/the_20210628_115455_205.log"
The file name is correct. If I copy and paste the tail command into an interactive SSH session with the QNX server, it works fine.
Is it something to do with the 'non-interactive' nature of this method of sending commands? I read that some implementations of SSH are built upon a command that offers a very limited environment. I don't see how that would impact this tail command.
Or am I doing something stupid in this code?
I cannot really explain completely, why you get the results you get.
But in general a corrupted output is a result of enabling and not handling terminal emulation. You enable the terminal emulation using get_pty=True. Remove it. You should not use the terminal emulation, when automating command execution.
Related question:
Is there a simple way to get rid of junk values that come when you SSH using Python's Paramiko library and fetch output from CLI of a remote machine?

Paramiko: calling "cd" command with exec_command does nothing

I have the following program using Paramiko:
#!/usr/bin/env python
import paramiko
hostname = '192.168.1.12'
port = 22
username = 'root'
password = 'whatl0ol'
if __name__ == "__main__":
paramiko.util.log_to_file('paramiko.log')
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.connect(hostname, port, username, password)
while True:
pick = raw_input("sshpy: ")
stdin, stdout, stderr = ssh.exec_command(pick)
print stdout.readlines()
But when I connect and try to use cd, it doesn't work. How can I fix this?
It looks like you are implementing some kind of interactive program that allows executing a sequence of commands on the server.
The SSHClient.exec_command executes each command in a separate "exec" channel. The individual commands run in their own environment. So if you execute cd command, it has no effect at all on subsequent commands. They will again start in user's home directory.
If you want to implement an interactive shell session, use SSHClient.invoke_shell.
For an example, see how to interact with Paramiko's interactive shell session?
See also Execute multiple commands in Paramiko so that commands are affected by their predecessors.
Paramiko SSH_Client opens a new session and executes the command in that session and once command execution gets completed, the session channel is closed.
Executing 'cd' command would have been done in the first session and later on, for the next command the session would start again from home directory.
If you want to hold the session, use invoke_shell for an interactive session.
I needed to change directories and run an executable. I have to do this all in one command. The client unit was a windows 10 machine.
The cmd shell in windows is soo problematic! Commands are different. ';' between commands doesn't work. You need to use '&'. cd d:/someDirectory doesn't work. You need '/d'. 'pwd' doesn't work. Also, echo%cd% to pwd doesn't work reliably. 'cd' with no parameters for pwd does work reliably. I was hoping the not working list would save you time. This is where it landed.
cmd = 'cd /d D:\someDirectory & SomeExecutable.exe
someParameter'
ssh_stdin, ssh_stdout, ssh_stderr =
ssh.exec_command(cmd_1_to_execute)
To check directory change use the following:
cmd = 'cd /d D:\someDirectory & cd'
ssh_stdin, ssh_stdout, ssh_stderr =
ssh.exec_command(cmd_1_to_execute)
output = ssh_stdout.readline()
error = ssh_stderr.readline()
print("output: " + output)
print("error: " + error)

SSHLibrary prompts for password

I have a problem with a test suite. I use robot framework and python.I created a function in python which executes a console command in a remote Linux client.
def purge_default_dns(device_ip):
ssh_val = "usr1#" + device_ip
command = "ctool dns | grep -v \"grep\" | awk \'{print $2}\'"
test = check_output(["ssh", ssh_val, "-p", "6022", command])
The check_output() function connects with device_ip and executes command. If I try to connect with a fully qualified domain name (ex. my.domain.io), then I get a prompt for password (which is empty). I press enter and command executes regular. Is there any parameter that passes for example Enter when password prompt comes up?
I tried ssh -e switch , I don't want to change ssh client , I just need a generic solution.
For example using paramiko library in the code below , I can create an paramiko SSHClient , which has a parameter for password and doesn't prompt anything. While I can't use paramiko right now , I need something else with SSHLirary to go around the problem.
def send_ssh_command(device_ip , command):
hostname = device_ip
password = ""
username = "usr1"
port = 6022
try:
client = paramiko.SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.WarningPolicy())
client.connect(hostname, port=port, username=username, password=password)
stdin , stdout , stderr = client.exec_command(command)
command_return_val = stdout.read()
finally:
client.close()
return command_return_val
Thank you.
To get this straight, the only solution you look for is to pass the password on the command line to the default OS ssh client, and do not/cannot install any libraries (paramiko, etc) that can help you achieve the same result through other means?
I'm asking this, because the robot framework's SSHLibrary provides this out of the box; you already have the python's solution with paramiko; and the general linux solution is to install the sshpass package, and use it to pass the value:
sshpass -p "YOUR_PASS" ssh -usr1#my.domain.io:6022
So if all of these are not an option, you are left with two alternatives - either hack something around SSH_ASKPASS - here's an article with a sample, or use expect to pass it - this one is what I'd prefer out of the two.
Here's a very good SO answer with an expect script wrapper around ssh. In your method, you will have to first create a file with its content, set an executable flag on it, and then call that file in check_output(), passing as arguments the password, 'ssh' and all its arguments.
Why You need to go with python , I am using below code in robotframework for the same:
[Arguments] ${host}=${APP_SERVER} ${username}=${APP_USERNAME} ${password}=${APP_PASSWORD}
Open Connection ${host} timeout=2m
Login ${username} ${password}
${out} ${err} ${rc}= Execute Command cd ${PATH};ctool dns | grep -v \"grep\" | awk \'{print $2}\' * return_stdout=True return_stderr=True return_rc=True
Should Be Equal ${rc} ${0}

Subprocess on remote server

I am using this code for executing command on remote server.
import subprocess
import sys
COMMAND="ls"
ssh = subprocess.Popen(["ssh", "%s" % HOST, COMMAND],
shell=False,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
result = ssh.stdout.readlines()
if result == []:
error = ssh.stderr.readlines()
print >>sys.stderr, "ERROR: %s" % error
else:
print result
When I try to execute this script, I get prompt for password. Is there any way I could avoid it, for example, can I enter password in script somehow? Also, password should be encrypted somehow so that people who have access to the script cannot see it.
Why make it so complicated? Here's what I suggest:
1) Create a ssh config section in your ~/.ssh/config file:
Host myserver
HostName 50.50.50.12 (fill in with your server's ip)
Port xxxx (optional)
User me (your username for server)
2) If you have generated your ssh keypair do it now (with ssh-keygen). Then upload with:
$ ssh-copy-id myserver
3) Now you can use subprocess with ssh. For example, to capture output, I call:
result = subprocess.check_output(['ssh', 'myserver', 'cat', 'somefile'])
Simple, robust, and the only time a password is needed is when you copy the public key to the server.
BTW, you code will probably work just fine as well using these steps.
One way is to create a public key, put it on the server, and do ssh -i /path/to/pub/key user#host or use paramiko like this:
import paramiko
import getpass
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
p = getpass.getpass()
ssh.connect('hostname', username='user', password=p)
stdin, stdout, stderr = ssh.exec_command('ls')
print stdout.readlines()
ssh.close()
You should use pexpect or paramiko to connect to remote machine,then spawn a child ,and then run subprocess to achieve what you want.
Here's what I did when encountering this issue before:
Set up your ssh keys for access to the server.
Set up an alias for the server you're accessing. Below I'll call it remote_server.
Put the following two lines at the end of ~/.bash_profile.
eval $(ssh-agent -s)
ssh-add
Now every time you start your shell, you will be prompted for a passphrase. By entering it, you will authenticate your ssh keys and put them 'in hand' at the start of your bash session. For the remainder of your session you will be able to run commands like
ssh remote_server ls
without being prompted for a passphrase. Here ls will run on the remote server and return the results to you. Likewise your python script should run without password prompt interruption if you execute it from the shell.
You'll also be able to ssh to the server just by typing ssh remote_server without having to enter your username or password every time.
The upside to doing it this way is that you should be doing this anyway to avoid password annoyances and remembering funky server names :) Also you don't have to worry about having passwords saved anywhere in your script. The only potential downside is that if you want to share the python script with others, they'll have to do this configuring as well (which they should anyway).
You don't really need something like pexpect to handle this. SSH keys already provide a very good and secure solution to this sort of issue.
The simplest way to get the results you want would probably be to generate an ssh key and place it in the .ssh folder of your device. I believe github has a pretty good guide to doing that, if you look into it. Once you set up the keys correctly on both systems, you won't actually have to add a single line to your code. When you don't specify a password it will automatically use the key to authenticate you.
While subprocess.Popen might work for wrapping ssh access, this is not the preferred way to do so.
I recommend using paramiko.
import paramiko
ssh_client = paramiko.SSHClient()
ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_client.connect(server, username=user,password=password)
...
ssh_client.close()
And If you want to simulate a terminal, as if a user was typing:
chan=ssh_client.invoke_shell()
def exec_cmd(cmd):
"""Gets ssh command(s), execute them, and returns the output"""
prompt='bash $' # the command line prompt in the ssh terminal
buff=''
chan.send(str(cmd)+'\n')
while not chan.recv_ready():
time.sleep(1)
while not buff.endswith(prompt):
buff+=ssh_client.chan.recv(1024)
return buff[:len(prompt)]
Example usage: exec_cmd('pwd')
If you don't know the prompt in advance, you can set it with:
chan.send('PS1="python-ssh:"\n')
You could use following.
import subprocess
import sys
COMMAND="ls"
ssh = subprocess.Popen("powershell putty.exe user#HOST -pw "password", stdout=PIPE, stdin=PIPE, stderr=STDOUT)
result = ssh.stdout.readlines()
if result == []:
error = ssh.stderr.readlines()
print >>sys.stderr, "ERROR: %s" % error
else:
print result

Paramiko and Pseudo-tty Allocation

I'm trying to use Paramiko to connect to a remote host and execute a number of text file substitutions.
i, o, e = client.exec_command("perl -p -i -e 's/" + initial + "/"
+ replaced + "/g'" + conf);
Some of these commands need to be run as sudo, which results in:
sudo: sorry, you must have a tty to
run sudo
I can force pseudo-tty allocation with the -t switch and ssh.
Is it possible to do the same thing using paramiko?
Actually it's quite simple. Just:
stdin, stdout, stderr = client.exec_command(command, get_pty=True)
The following code works for me:
#!/usr/bin/env python
import paramiko
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('localhost',username='root',password='secret')
chan = ssh.get_transport().open_session()
chan.get_pty()
chan.exec_command('tty')
print(chan.recv(1024))
This was just assembled from looking at a few examples online... not sure if its the "right" way.
I think you want the invoke_shell method of the SSHClient object (I'd love to give a URL but the paramiko docs at lag.net are frame-heavy and just won't show me a specific URL for a given spot in the docs) -- it gives you a Channel, on which you can do exec_command and the like, but does that through a pseudo-terminal (complete with terminal type and numbers of rows and columns!-) which seems to be what you're asking for.
According to the sudo manpage:
The -S (stdin) option causes sudo to read the password from the standard input instead of the terminal device. The
password must be followed by a newline character.
You can write to the stdin because it is a file object with write():
import paramiko
client = paramiko.client.SSHClient()
client.set_missing_host_key_policy(paramiko.client.AutoAddPolicy())
client.connect(hostname='localhost', port=22, username='user', password='password')
stdin, stdout, stderr = client.exec_command('sudo -S aptitude update')
stdin.write('password\n')
stdin.flush()
# print the results
print stdout.read()
client.close()

Categories

Resources