counsel to the library to work with SSH. The main requirement is normal operation with the utility sudo.
I have already tried and what I am suffering:
paramiko - can not sudo at all, trying after a call to serve in STDIN password, but sudo wrote that then type: "No ttys present"
pxssh - mmmmmm, very slow, very very slow, awkward
fabric - can sudo only in what is an ideal world, as there is to work with different users and where i need send password ?
Have normal libraries that work with sudo, or not?
Rather than force sudo to work without a tty, why not get Paramiko to allocate you a TTY?
Paramiko and Pseudo-tty Allocation
I think you are looking for fabric.
You can configure sudo to work without a real terminal with 'requiretty' setting. From sudoers manual:
If set, sudo will only run when the user is logged in to a real tty. This will
disallow things like "rsh somehost sudo ls" since rsh(1) does not
allocate a tty.
Because it is not possible to turn off echo when there is no tty present,
some site may wish to set this flag to prevent a user from entering a visible
password. This flag is off by default.
This works for me with paramiko. Depending o what are you doing, you can also look at something like pexpect.
I had the same problem with pxssh at first: it was extremely slow!
Here is a way I found to make it run quicker:
#!/usr/bin/python
import pxssh
import getpass
try:
s = pxssh.pxssh()
s.PROMPT = "#"
hostname = raw_input('hostname: ')
username = raw_input('username: ')
password = getpass.getpass('password: ')
s.login(hostname, username, password, auto_prompt_reset=False)
s.sendline('ls') # run a command
s.prompt() # match the prompt
print(s.before) # print everything before the prompt.
s.sendline('ls -l /tmp') # run a command
s.prompt() # match the prompt
print(s.before) # print everything before the prompt.
s.logout()
except pxssh.ExceptionPxssh as e:
print("pxssh failed on login.")
print(e)
The key part is s.PROMPT = "#" and auto_prompt_reset=False in s.login().
This method requires that you know the pattern for the prompt (in my case it is "#", I think the PROMPT attribute can be set to a regular expression).
I also had some problems with login speed on pxssh. I tried using the code referenced above, but still was seeing 10+ seconds just to login. Using the original_prompt argument fixed the issue for me. You need to make sure to set the original_prompt to what you see when you first ssh into the machine, which in my case ended in '>'.
#!/usr/bin/env python
from pexpect import pxssh
host = 'hostname.domain'
user = 'username'
password = 'password'
terminal = pxssh.pxssh()
terminal.login(host, user, original_prompt='[>$]')
Related
I use a friends server that allows only one user to be logged from SSH, so normally I just log in as that user and then do su -l myuser to change accounts. I wanted to automate some boring stuff using Python, but I ran into problems with that. Apparently Paramiko module that I tried first invokes a single shell for every command, so that was out of the question. Later I tried using invoke_shell() to overcome that, but it still failed (I assume it's because changing user changes shell as well).
After that I found about Fabric module, but best I could do is open SSH shell with a proper user logged in, but without option to run any commands from code.
Is there any way to accomplish that? Final goal would probably look something like this:
ssh.login(temp_user, pass)
ssh.command("su -l myuser")
expect("Password: ", ssh.send("mypass\n")
ssh.command("somescript.sh > datadump.txt")
Using sudo is impossible, as well as adding passwordless login.
As suggested here is the code that I tried with Paramiko:
import paramiko
host = "hostip"
user = "user"
user_to_log = "myuser"
password = "pass"
password_to_log = "mypass"
login_command = "su -l " + user_to_log
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostip, username=user,
password=password)
transport = ssh.get_transport()
session = transport.open_session()
session.set_combine_stderr(True)
session.get_pty()
session.exec_command("su -l " + user_to_log)
stdin = session.makefile('wb', -1)
stdin.write(password_to_log +'\n')
stdin.flush()
session.exec_command("whoami")
stdout = session.makefile('rb', -1)
for line in stdout.read().splitlines():
print('host: %s: %s' % (host, line))
su -c command won't work either, since server system doesn't support that option.
General disclaimers first (to others who stumble upon this question):
Using su is not the right solution. su is a tool intended for an interactive use, not for an automation. The correct solution is to login with the correct account directly.
Or at at least use a password-less sudo.
Or you can create a root-owned script with setuid right.
See also Allowing automatic command execution as root on Linux using SSH.
If you are stuck with su, on most systems you can use -c switch to su to specify a command:
su -c "whoami" user
See also How to run sudo with Paramiko? (Python)
If none of the above is feasible (and you really tried hard to make the admin enable some of the options above):
As the last resort option, you can write the command to a standard input of the su, the same way you already write a password (another thing not to do):
stdin, stdout, stderr = session.exec_command("su -l " + user_to_log)
stdin.write(password_to_log + '\n')
stdin.flush()
command = 'whoami'
stdin.write(command + '\n')
stdin.flush()
(also note that it's redundant to call makefile, as exec_command already returns that)
See Execute (sub)commands in secondary shell/command on SSH server in Python Paramiko.
Note that your question is not about which SSH client library to use. It does not matter if you use Paramiko or other. This all is actually a generic SSH/Linux/shell question.
I use a friends server that allows only one user to be logged from SSH, so normally I just log in as that user and then do su -l myuser to change accounts. I wanted to automate some boring stuff using Python, but I ran into problems with that. Apparently Paramiko module that I tried first invokes a single shell for every command, so that was out of the question. Later I tried using invoke_shell() to overcome that, but it still failed (I assume it's because changing user changes shell as well).
After that I found about Fabric module, but best I could do is open SSH shell with a proper user logged in, but without option to run any commands from code.
Is there any way to accomplish that? Final goal would probably look something like this:
ssh.login(temp_user, pass)
ssh.command("su -l myuser")
expect("Password: ", ssh.send("mypass\n")
ssh.command("somescript.sh > datadump.txt")
Using sudo is impossible, as well as adding passwordless login.
As suggested here is the code that I tried with Paramiko:
import paramiko
host = "hostip"
user = "user"
user_to_log = "myuser"
password = "pass"
password_to_log = "mypass"
login_command = "su -l " + user_to_log
ssh = paramiko.SSHClient()
ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostip, username=user,
password=password)
transport = ssh.get_transport()
session = transport.open_session()
session.set_combine_stderr(True)
session.get_pty()
session.exec_command("su -l " + user_to_log)
stdin = session.makefile('wb', -1)
stdin.write(password_to_log +'\n')
stdin.flush()
session.exec_command("whoami")
stdout = session.makefile('rb', -1)
for line in stdout.read().splitlines():
print('host: %s: %s' % (host, line))
su -c command won't work either, since server system doesn't support that option.
General disclaimers first (to others who stumble upon this question):
Using su is not the right solution. su is a tool intended for an interactive use, not for an automation. The correct solution is to login with the correct account directly.
Or at at least use a password-less sudo.
Or you can create a root-owned script with setuid right.
See also Allowing automatic command execution as root on Linux using SSH.
If you are stuck with su, on most systems you can use -c switch to su to specify a command:
su -c "whoami" user
See also How to run sudo with Paramiko? (Python)
If none of the above is feasible (and you really tried hard to make the admin enable some of the options above):
As the last resort option, you can write the command to a standard input of the su, the same way you already write a password (another thing not to do):
stdin, stdout, stderr = session.exec_command("su -l " + user_to_log)
stdin.write(password_to_log + '\n')
stdin.flush()
command = 'whoami'
stdin.write(command + '\n')
stdin.flush()
(also note that it's redundant to call makefile, as exec_command already returns that)
See Execute (sub)commands in secondary shell/command on SSH server in Python Paramiko.
Note that your question is not about which SSH client library to use. It does not matter if you use Paramiko or other. This all is actually a generic SSH/Linux/shell question.
The script provided by TechJS: (https://stackoverflow.com/users/5252192/techjs) in their answer on (How to run sudo with paramiko? (Python)) works perfectly for me.
However, it echos the password in the command line after my additions and i know that's not a good idea. I imagine its from the stdin.write() but i have no idea how to do it differently.
Can anyone suggest a more secure way of storing and inputting the server password? I'm still pretty new and would love a good lesson on proper password security protocol in these situations :)
Thanks so much to any and all help!
import paramiko
import re
import <passwords file> #did chmod 400 for this file
ssh_client= None
server_address='<removed for security>'
server_username='<removed for security>'
server_pass = <password file>.<this server password from passwords file>
command = "<removed for security>"
def main(command, server_address, server_username, server_pass):
try:
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname=server_address,
username=server_username,
password=server_pass)
session = ssh.get_transport().open_session()
session.set_combine_stderr(True)
session.get_pty()
session.exec_command("sudo bash -c \"" + command + "\"")
stdin = session.makefile('wb', -1)
stdout = session.makefile('rb', -1)
stdin.write(server_pass + '\n')
stdin.flush()
print(stdout.read().decode("utf-8"))
except Exception as e:
print("The following error has occurred during your requested process")
print(e.message)
finally:
if ssh:
session.close()
ssh.close()
if __name__ == '__main__':
main(command, server_address, server_username, server_pass)
After a lot of research I believe I have an acceptable answer, however please take this with skepticism as I am NOT an expert in this field. You have been advised.
This also did NOT fix the printing of the stdin problem, but i have just removed the print() function all together to remove the issue. This answer is for the password security part ONLY.
tl:dr here is the answer https://alexwlchan.net/2016/11/you-should-use-keyring/
but i will explain in more detail and provide examples below of my code used to store and use passwords while never plain texting them.
LONG ANSWER:
Python has a package that is build for this purpose specifically called keyring(). It allows you to store and call on passwords with relative security. It works off of your login credentials so unfortunately if someone gains access to your account they will have access to this information, but without that you should theoretically be secure (or as secure as one can be i guess)
Keyring() plus a package called getpass() allow for a user to input a password into their system without committing it to plain text and thus preventing accidental leaking through file sharing or the like.
Here is a very simple script I wrote to automatically prompt you through your choices and store the password without ever needing to store it in plain text
import keyring
import getpass
def main():
system = input('System:')
username = input('Please input username:')
keyring.set_password(system,username,getpass.getpass())
print('The password for ' +username+' in '+system+' has been set.\nPlease do not misplace, you will not be able to recover at this point.\nFor misplaced passwords, please resubmit new entry with the same details, it will overwrite the previous entry.')
if __name__=='__main__':
print('Please input the system in which the password will be used,\nand corresponding username.')
main()
(if you're using Python 2 then it needs to be raw_input() )
This is done in an entirely different script so you DO NOT NEED TO HAVE THEM TOGETHER, run one script to set the password, then to call on the corresponding password is very simple in your main script from that point forward.
passwd = keyring.get_password('<system you inputed>','<username you inputed>')
And you're done!
p.s. I personally have placed a bash file on my PATH that runs this script so that if i ever need to create a password it can be done from any directory within the machine, and thus reinforcing good security procedures.
I've managed to get the cmd being opened by python. However, using runas administrator comes with a password check before cmd.exe is executed.
I'm using this to open cmd...
import subprocess
subprocess.call(["runas", "/user:Administrator", "cmd.exe"])
I'm looking for a way to automatically enter the password into the runas.exe prompt which opens when i run the code. Say if i were to create var = "test" and add it after import subprocess how would i make it so that this variable is passed to and seen as an input to the runas.exe?
The solution would require only python modules which are in version 3.4 or higher.
Update
I have found some code which appears to input straight into runas.exe. However, the apparent input is \x00\r\n when in the code the input is supposed to be test I am fairly certain that if i can get the input to be test then the code will be successful.
The code is as follows :
import subprocess
args = ['runas', '/user:Administrator', 'cmd.exe']
proc = subprocess.Popen(args,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
proc.stdin.write(b'test\n')
proc.stdin.flush()
stdout, stderr = proc.communicate()
print (stdout)
print (stderr)
Although not an answer to your question, this can be a solution to your problem. Use psexec instead of runas. You can run it like this:
psexec -u user -p password cmd
(or run it from Python using subprocess.Popen or something else)
This piece of code actually works (tested on a Windows 2008 server). I've used it to call runas for a different user and pass his password. A new command prompt opened with new user context, without needing to enter password.
Note that you have to install pywin32 to have access to the win32 API.
The idea is:
to Popen the runas command, without any input redirection, redirecting output
read char by char until we encounter ":" (last char of the password prompt).
send key events to the console using win32 packages, with the final \r to end the password input.
(adapted from this code):
import win32console, win32con, time
import subprocess
username = "me"
domain = "my_domain"
password ="xxx"
free_console=True
try:
win32console.AllocConsole()
except win32console.error as exc:
if exc.winerror!=5:
raise
## only free console if one was created successfully
free_console=False
stdin=win32console.GetStdHandle(win32console.STD_INPUT_HANDLE)
p = subprocess.Popen(["runas",r"/user:{}\{}".format(domain,username),"cmd.exe"],stdout=subprocess.PIPE)
while True:
if p.stdout.read(1)==b":":
for c in "{}\r".format(password): # end by CR to send "RETURN"
## write some records to the input queue
x=win32console.PyINPUT_RECORDType(win32console.KEY_EVENT)
x.Char=unicode(c) # remove unicode for python 3
x.KeyDown=True
x.RepeatCount=1
x.VirtualKeyCode=0x0
x.ControlKeyState=win32con.SHIFT_PRESSED
stdin.WriteConsoleInput([x])
p.wait()
break
I'm trying to do a login script using python that will attempt to login with the shell command login -q MyUsername and try multiple passwords. I can already generate the passwords needed but when I try to login using the code below, the login command responds that I entered the wrong username although I know I'm writing it correctly. To clarify: I'm creating a script to login using the shell command login when I already know the username but not the password. The code below shows what I'm doing (iterating over the passwords).
for password in passwordList:
p = Popen(["login","-q","MyUsername"], stdin=PIPE, stdout=PIPE) #The username MyUsername is correct, 100% sure
print repr(p)
stdout_value = p.communicate(password)[0] #
print(str(stdout_value))
if repr(stdout_value).startswith('Login incorrect\nlogin: '):
print "ERROR"
else:
print "GOOD"
break
If I type in the command login -q MyUsername directly into the terminal, I get prompted to write my password whereas using the script returns 'Login Incorrect'. I'm also confused as how Popen works and how to write to stdout.
Thanks in advance!
(Other question: Is there an easier way to do this? (Attempt to login using multiple passwords) I'm using login because it has no lockdown and the user data can't be accessed if it is not by the superuser).
login might read/write directly from/to terminal (tty) outside of process' stdin/stdout. You could use pexpect instead, read the first reason in its docs Q: Why not just use a pipe (popen())?:
import pexpect
output, rc = pexpect.run("login -q MyUsername",
events={"(?i)password: ": "password"},
withexitstatus=True)
Is there an easier way to do this?
Read the hashes from /etc/passwd, /etc/shadow and check those using crypt.crypt(). Or use a specialized tool to test for weak passwords such as "John the Reaper".