subprocess stdout and stderr while doing ssh - python

I want to know the disk usage of remote servers and i thought of doing it using ssh
here's what i have done so far:-
def disk_usage(server):
msg=""
ps = subprocess.Popen(["ssh", "-o", "BatchMode=yes", "-l", "mygroup", server, "df -k /some/directory"], stdout=subprocess.PIPE)
out, err = ps.communicate()
if err != None:
msg += "\n"+err
else:
msg = out
return msg
Final_msg = ""
server_list= ['server A','server B','server C']
for server in server_list:
Final_msg+="For Server :"+server+"\n"+disk_usage(server)
print Final_msg
The script works fine, but problem is when the ssh for any server is not configured it just displays a blank output for that server
Output:-
For Server A :
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/cfd/ace 8064048 3581524 4072892 47% /app
For Server B :
For server C :
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/wsa/ace 306423 244524 23243434 90% /app
Here ssh for server B is not configured so i'm getting a blank output because the batchmode is on (BatchMode=yes) for all the ssh connections, but i want the user to know why there was no output.
when i run the same command on the shell for the sever where ssh is not configured i get the below error:
Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).
I want the same error in my output of the script for that particular server where ssh is not configured.
any ideas?

To detect that an error happened, you should check the returncode attribute of the Popen object (ps).
To get the output from stderr, you have to pass stderr=subprocess.PIPE to Popen, just as you do for stdout.

if your local machine has static ip i would recommend using sockets so your data usage script will connect to your local machine and deliver data.
or if you have domain to post your server info to your web app via urllib.

Related

How to send data from a file in a Python docker container to remote SFTP server?

I have a Python script I am trying to run in a Docker container to send a file that is on this container to an SFTP server.
I tried the following :
import paramiko
ssh=paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
session = ssh.connect(hostname="X", port=X, username='X', password="X")
stdin,stdout,stderr = ssh.exec_command('sftp -P X ldzdl#hostname', get_pty=True)
I also tried with paramiko transport method but didn't work from remote (docker container) to remote SFTP.
But I have the following error : paramiko.ssh_exception.AuthenticationException: Authentication failed.
How can I do this ? I don't know if my method is okay or if there is other better way to solve it (send data from container to an SFTP server).
The argument given to the exec_command function is not the command you would normally run on the local (client) host's shell, but rather attempted to be ran on the remote (server) host. While it is not likely to get an AuthenticationException by attempting to run remote commands, as you did not post a full traceback in the question - it is hard to tell for sure.
I suggest checking the following code:
import paramiko
ssh=paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
session = ssh.connect(hostname="X", port=X, username='X', password="X")
### so far, same code as in the question ###
print("Auth OK")
sftp_connection = ssh.open_sftp()
sftp_connection.put("local/file/path", "remote/file/path")
If you see the "Auth OK" print - then you should be good to go, just replace the file path arguments of the sftp_connection.put() method with actual local and remote file paths.
Otherwise - there is an actual authentication issue which should be resolved.

SSH tunnel with terminal

I'm a beginner at ssh so be kind with my limited knowedge ;)
What I want to do is as follow:
SSH to a PC and then from this PC SSH to another one, see picture below:
SSH Tunnel
Here are the commands I run when I do it manually:
ssh user#155.254.0.1
After this command I will be prompt to enter the password.
From here I ssh again to the next "PC" with the following command:
ssh root#190.22.0.1 -y
and then I get prompt to enter the password.
I tried to use a python script to do it automatically by I was not able to come to the next seconds step.
Here is how the python code looks like:
import subprocess
cmd_1 = ["ls"]
cmd_3 = ['ls', '-l']
def send_top_cmd():
cmd_2 = ['top', "-b", "-n", "5"]
com2 = subprocess.Popen(cmd_2, stdout=out)
com2.wait()
def send_ssh_pc_1():
cmd = ["sshpass", "-p", "'user'", "ssh", "swupdate#155.254.0.1"]
ssh_sga = subprocess.Popen(cmd, stdout=out)
ssh_sga.wait()
def send_ssh_pc_2():
cmd = ["sshpass", "-p", "'root'", "ssh", "root#190.22.0.1"]
ssh_hpa = subprocess.Popen(cmd, stdout=out)
ssh_hpa.wait()
def send_exit():
cmd = ["exit"]
process = subprocess.Popen(cmd, stdout=out)
cmd = ["exit"]
process = subprocess.Popen(cmd, stdout=out)
print("done")
with open('output.txt', 'w') as out:
send_ssh_pc_1() # ssh PC 1
send_ssh_pc_2() # ssh PC 2
send_top_cmd() # Send a simply command
send_exit()
The script fails at the "send_ssh_pc_2()" since I dont have sshpass installed and there's no possibility to install it there :(
Is there a easier way to do it automatically?
So much easier to write as an answer instead of comment.
First, enable RSA authentication for both of your SSH boxes. Then you don't need to worry about passing password. https://www.ssh.com/academy/ssh/public-key-authentication
Then open SSH tunnel from your computer with following command:
ssh -L 2222:190.22.0.1:22 user#155.254.0.1
That will enable tunnel from your local computer port 2222 to host in address 190.22.0.1 port 22. So next you can open SSH connection to the target computer like this.
ssh -p 2222 root#localhost
If your RSA private key is authorized to both user#155.254.0.1 and root#190.22.0.1 no passwords should be asked and you have SSH connection to 192.22.0.1 from your workstation.
Of course you can tunnel any TCP traffic, not just SSH.
*** ADDED ***
Here is example of content of authorized_keys -file (some content removed).
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA3fauf5H3kN92Gxx8xerCF***********************************************************************************************************************PPIrUMdf1948pqLspom8SIyeqJeKX8wVqcJch35O0Q4UVlbw== user#host
ssh-rsa AAAAB3Nzaasdfrgaa4634w4gfdewrtfauf5H3kN92Gxx8xerCF***********************************************************************************************************************PPIrUMdf1948pqLspossdfgqrbbsrdtwetdsfgsfdgsd== admin#anotherhost

CyberArk ITATS004E Authentication failure for User in python script

I'm trying to implement a python script that executes local bash scripts or simple commands on remote CyberArk machines. Here is my code:
if __name__ == '__main__':
for ip in IP_LIST:
bash_cmd = f"ssh -o stricthostkeychecking=no {USER}%{LOCAL_USER}%{ip}#{PROXY} 'bash -s' < {BASH_SCRIPT}"
exit_code = subprocess.call(bash_cmd, shell=True)
print(exit_code)
bash_cmd = f"scp {USER}%{LOCAL_USER}%{ip}#{PROXY}:server_info_PY.txt ."
exit_code = subprocess.call(bash_cmd, shell=True)
print(exit_code)
The main problem is that i get this CyberArk authentication error most of the times, but not always, so it's kind of random and i don't know why:
PSPSD072E Perform session error occurred. Reason: PSPSD033E Error receiving PSM For SSH server
response (Extra information: [289E [4426b00e-cc44-11ec-bca1-005056b74f99] Failed to impersonate as
user <user>. Error: [ITATS004E Authentication failure for User <user>.
In this case the ssh exit code is 255, but if i check sshd service logs on the remote machine, there are no errors. I even tried with the os library to execute bash commands, but I got same result.
I was thinking of multiple ssh sessions hanging after executing this script a lot of times, but on the remote machine i only find the one i'm using.
Could someone explain what is happening or do you have any ideas?
Notes: I don't have any access to the PSM server, that is stored in the variable PROXY
Edit 1: I tried to use Paramiko library to create the ssh connection, but i get an authentication error related to Paramiko and not related to CyberArk. I also tried Fabric library which is based on Paramiko, so it didn't work.
If i try to run the same ssh command manually from my terminal it works and i can see that it first connects to the PROXY and then to the ip of the remote machine. From the script side it looks like he can't even connect to the PROXY because of the CyberArk authentication error.
Edit 2: I logged some informations about all commands running when executing the python script and i found out that the first command which is launched is /bin/sh/ -c plus the ssh string:
/bin/sh -c ssh <user>#<domain>
Could be this the main problem? The prepending of /bin/sh -c? Or it's a normal behaviour when using subprocess library? There is a way to just execute the ssh command without this prepend?
Edit 3: I removed shell=True but got same auhtentication error. So, if i execute manually the ssh command i get no error, but if it is executed from the python script i get the error, but i can't find any contradiction at proccess level using ps aux in both cases.
Since the authentication error is kind of random, I just added a while loop that resets known_hosts file and runs the ssh command for n retries.
succeeded_cmd_exec = False
retries = 5
while not succeeded_cmd_exec:
if retries == 0:
break
bash_cmd = f'ssh-keygen -f "{Configs.KNOWN_HOSTS}" -R "{Configs.PROXY}"'
_, _, exit_code = exec_cmd(bash_cmd)
if exit_code == 0:
radius_password = generate_password(Configs.URI, Configs.PASSWORD)
bash_cmd = f"sshpass -p \"{radius_password}\" ssh -o stricthostkeychecking=no {Configs.USER}%{Configs.LOCAL_USER}%{ip}#{Configs.PROXY} 'ls'"
stdout, stderr, exit_code = exec_cmd(bash_cmd)
if exit_code == 0:
print('Output from SSH command:\n')
print(stdout)
succeeded_cmd_exec = True
else:
retries = retries - 1
print(stdout)
print('SSH command failed, retrying ... ')
print('Sleeping 15 seconds')
time.sleep(15)
else:
print('Reset known hosts files failed, retrying ...')
if retries == 0 and not succeeded_cmd_exec:
print(f'Failed processing IP {ip}')
The exec_cmd function is defined like this:
def exec_cmd(bash_cmd: str):
process = subprocess.Popen(bash_cmd, shell=True, executable='/bin/bash', stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
process.wait()
return stdout.decode('utf-8'), stderr.decode('utf-8'), process.returncode

Get command result from SSH/Telnet via another SSH jump server in Python

I am writing a Python code to SSH or Telnet remote hosts, execute some commands and get the result output. Here we have a jump server, so the code must be "connected" with this server and from it, Telnet or SSH the remote hosts.
All my approaches work fine within the jump server, for example I can get the output of commands inside it. The problem is when I try to remote connect to hosts from it.
import paramiko
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect('IP', 22, username="user", password="pass")
stdin, stdout, stderr = client.exec_command("command")
for line in stdout:
print('... ' + line.strip('\n'))
client.close()
Using the library jumpssh get same results, and I am not will able to Telnet hosts. I tried the follow approach, but get the error
Administratively prohibited.
from jumpssh import SSHSession
gateway_session = SSHSession('jumpserver','user', password='pass').open()
remote_session = gateway_session.get_remote_session('IP',password='pass')
print(gateway_session.get_cmd_output('command'))
In the last company i worked we had a license from an SSH client that supports Python scripts, and worked fine in a more "textual" treatment.
There is any way of accomplish same task natively in Python?
SSHSession is trying to open direct-tcpip port forwarding channel over the gateway_session.
"administratively prohibited" is OpenSSH sshd server message indicating that direct-tcpip port forwarding is disabled.
To enable port forwarding, set AllowTcpForwarding and DisableForwarding directives in sshd_config appropriately.
If you cannot enable the port forwarding on the server, you cannot use jumpssh library.
If you have a shell access to the server, you can use ProxyCommand-like approach instead.
See Paramiko: nest ssh session to another machine while preserving paramiko functionality (ProxyCommand).

Paramiko SSH failing with "Server '...' not found in known_hosts" when run on web server

I am trying to use Paramiko to make an SSH communication between 2 servers on a private network. The client server is a web server and the host server is going to be a "worker" server. The idea was to not open up the worker server to HTTP connections. The only communication that needs to happen, is the web server needs to pass strings to a script on the worker server. For this I was hoping to use Paramiko and pass the information to the script via SSH.
I set up a new user and created a test script in Python 3, which works when I run it from the command line from my own user's SSH session. I put the same code into my Django web app, thinking that it should work, since it tests OK from the command line, and I get the following error:
Server 'worker-server' not found in known_hosts
Now, I think I understand this error. When performing the test script, I was using a certain user to access the server, and the known hosts information is saved to ~/.ssh/known_hosts even though the user is actually a 3rd party user created just for this one job. So the Django app is running under a different user who doesn't find the saved known hosts info because it doesn't have access to that folder. As far as I can tell the user which Apache uses to execute the Django scripts doesn't have a home directory.
Is there a way I can add this known host in a way that the Django process can see it?
Script:
import paramiko
client = paramiko.SSHClient()
client.load_system_host_keys()
client.connect('worker-server', 22, 'workeruser', 'workerpass')
code = "123wfdv"
survey_id = 111
stdin, stdout, stderr =
client.exec_command('python3 /path/to/test_script/test.py %s %s' % ( code, survey_id ))
print( "ssh successful. Closing connection" )
stdout = stdout.readlines()
client.close()
print ( "Connection closed" )
output = ""
for line in stdout:
output = output + line
if output!="":
print ( output )
else:
print ( "There was no output for this command" )
You can hard-code the host key in your Python code, using HostKeys.add:
import paramiko
from base64 import decodebytes
keydata = b"""AAAAB3NzaC1yc2EAAAABIwAAAQEA0hV..."""
key = paramiko.RSAKey(data=decodebytes(keydata))
client = paramiko.SSHClient()
client.get_host_keys().add('example.com', 'ssh-rsa', key)
client.connect(...)
This is based on my answer to:
Paramiko "Unknown Server".
To see how to obtain the fingerprint for use in the code, see my answer to:
Verify host key with pysftp.
If using pysftp, instead of Paramiko directly, see:
PySFTP failing with "No hostkey for host X found" when deploying Django/Heroku
Or, as you are connecting within a private network, you can give up on verifying host key altogether, using AutoAddPolicy:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(...)
(This can be done only if you really do not need the connection to be secure)

Categories