Sudo in Fabric2 - python

I would like to reproduce the behavior of my old Fabric1 script running
sudo('useradd -m -u --groups mygroup myuser')
In that case the remote machine (an Ubuntu AWS instance) would prompt me for my sudo password and I was able to type it in. Even the repeated prompt after a wrong input would work. After that Fabric1 would hold on to that password by keeping the connection open.
In my new Fabric 2.x script I am using within a #task
c.sudo('useradd -m -u --groups mygroup myuser')
I am still getting a user prompt but it does not wait for my response, I am not able to type the password, and it fails as expected with
invoke.exceptions.AuthFailure: The password submitted to prompt '[sudo] password: ' was rejected
Using the --prompt-for-sudo-password argument has the same (or no effect).
I also tried
run('sudo useradd -m -u --groups mygroup myuser')
and received
sudo: no tty present and no askpass program specified
I would preferable not set the askpass program on a machine that worked perfectly without on Fabric 1.x (unless I could do that from my script which would require sudo, I guess).
I have been banging my head against the wall trying to use the complicated and not well documented configuration system but with no success.
What am I missing? If there is extra configuration required, I would like to know the syntax of the config file as well as where to place that file so that it get picked up by the fab CLI command.

It seems to matter that the arguments are passed in before the tasks, so make sure you're not just tacking args on the end of the command. For instance, this fails (doesn't prompt for password, then fails sudo authentication):
$ fab -H myhost mysudotask --prompt-for-sudo-password
[sudo] password: Sorry, try again.
[sudo] password: Traceback (most recent call last):
... <snip> ...
File "<string>", line 2, in raise_from
invoke.exceptions.AuthFailure: The password submitted to prompt '[sudo] password: ' was rejected.
While the following works as expected (prompting for password, then executing the sudo task):
$ fab -H myhost --prompt-for-sudo-password mysudotask
Desired 'sudo.password' config value:
[sudo] password: hi 1

Related

Fabric 3 ask sudo password once

I am using fabric to automate some deployment stuff. Below is a sample of the code I used:
run(f"sudo -H -u www-data bash -c 'rm -r project_name' ")
run(f"sudo -H -u www-data bash -c '/opt/www-data/project-name/bin/pip install -r requirements.txt' ")
run("sudo systemctl stop gunicorn")
run("sudo systemctl start gunicorn")
Everytime each line of code was ran, the terminal ask for my user password, is there a way I can enter the password just once?
Edit:
I am using python3 and the essence of the script was to run the commands on a different user, rather than my own.
Update:
I achieved this by running fabric with "-I" param.
fabric -I deploy
Using run is not the ideal way to achieve this.
fabric.operations.sudo(*args, **kwargs) is something that can be used to achieve what you are attempting.
Please be careful with sudo :)
Every run() invocation is a separate shell, as would be a sudo() invocation. The sudo credentials are per shell, so they are gone every time.
A quick and dirty way would be to lump all commands into one sudo invocation.
A nicer way would be to have a sudoers file on the target host(s) and give each user the required privileges to run particular commands without entering a password.
You can create a fab script like below and then iterate over the host list you want to run the commands because you can passwd username and password in the script itself so to avoid password invocation:
# testCheck.py
#!/usr/bin/python2.7
import sys
from fabric.api import *
env.skip_bad_hosts=True
env.command_timeout=160
env.user = 'user_name'
env.shell = "/bin/sh -c"
env.warn_only = True
env.password = 'user_password'
def readhost():
env.hosts = [line.strip() for line in sys.stdin.readlines()]
def hosts():
with settings(warn_only=True):
output=sudo("ls -l /myfolder",shell=False)
# cat hostfile.txt| | /usr/local/bin/fab readhost -f testCheck.py hosts -P -z 5
OR supplying password at command line
# cat hostfile.txt | /usr/local/bin/fab readhost -f testCheck.py --password=your_pass hosts -P -z 5
--> argument "-P" refers to parallel execution method
--> argument "-z" refres to the number of concurrent processes to use in parallel mode
exapmle hostfile.txt:
server1
server2
server3
server4
Hope this will help.
If you are using ssh keys, then set the fabric environment variable key_filename:
env.key_filename='/path/to/key.pem'
# set the following as well
env.user='username'
env.host='hostaddr'
It will ask you for the password only one time.
Have a look at this question regarding avoid to enter any sudo password when using fabric.

Python Change Permission of Dir and all Subdirs

I'm trying to execute the following command in Python:
shutil.rmtree('/var/www')
but that gives me the following error:
PermissionError: [Errno 13] Permission denied: '/var/www'
I've tried executing this command before that command to change permissions on the directory
subprocess.call(['sudo', 'chmod', '-R', '777', '/var/www'])
Which I guess is working because it asks me for my sudo password and doesn't throw an error, however when I then reach the rmtree command it still throws a
Permission denied: '/var/www'
What am I doing wrong? Also I need this to work without asking for my sudo password during execution. Is there a way to put my password into the Python script?
Thanks
You need to check the group ownership of that directory, make sure that your username is added to this group which has write permissions to all files in the path:
chmod -R g+w /var/www/
As for the second pat of your question, sudo can read the password from the standard input using the -S flag which can be included in your script. Here's an example:
$ echo "password" | sudo -S <command>
A quick look at the man sudo page:
-S, --stdin
Write the prompt to the standard error and read the password from the
standard input instead of using the terminal device. The password must
be followed by a newline character.
Another way of doing that (which needs to be added to your script as well):
sudo -S <<< "password" command
Another approach is to disable password prompt by editing the sudoers file using the visudo command and add the following line:
username ALL=(ALL) NOPASSWD: ALL
The above line will allow username to run all commands (the last ALL) under any users without prompting for a password.
A secure and more restricted way of doing that is to use:
username ALL=(ALL) NOPASSWD: /usr/bin/python myscript.py
This time, we have limited the username for that type of execution but for a single command /usr/bin/python myscript.py.
I'd also prefer checking the permissions carefully and testing these commands directly from the shell before using it in your python script. It could save you a lot of time of troubleshooting.

Fabric sudo() not respecting env.password

I'm trying to prefill env.password using --initial-password-prompt, but remote is throwing back some strangeness. Let's say that I'm trying to cat a root-owned file as testuser, with 600 permissions on the file. I'm calling sudo('cat /home/testuser/test.txt'), and getting this back:
[testuser#testserver] sudo: cat /home/testuser/test.txt
[testuser#testserver] out: cat: /home/testuser/test.txt: Permission denied
[testuser#testserver] out:
Fatal error: sudo() received nonzero return code 1 while executing!
Requested: cat /home/testuser/test.txt
Executed: sudo -S -p 'sudo password:' -u "testuser" /bin/bash -l -c "cat /home/testuser/test.txt"
Is that piping the prompt right back into the input? I tried using sudo() with pty=False to see if it was an issue with the pseudoterminal, but to no avail.
Here's the weird part: calling run('sudo cat /home/testuser/test.txt') and invoking fab without --initial-password-prompt passes back a password prompt from remote, and on entering the password, everything works fine.
Naturally, running ssh -t testuser#testserver 'sudo cat /home/user/test.txt' prompts for a password and returns the contents of the file correctly. Do I have an issue with my server's shell config, or is the issue with how I'm using sudo()?
Down the line, I'm likely to set up a deploy user with no-password sudo and restricted commands. That'll probably moot the issue, but I'd like to figure this one out if possible. I'm running an Ubuntu 14.10 VPS, in case that's relevant.
Oh, my mistake. I had foolishly set env.sudo_user to my deploy user testuser, thinking that it was specifying the invoking user on remote. In fact, it was specifying the target user, and I was attempting to sudo into myself. Whoops.

Running fabric scripts as root

I am trying to use fabric to automate some administrative work that I am doing on a couple of servers. The general flow is the following:
SSH with local user
run: sudo su - to become root (providing local user password again)
Do the work as root :)
Unfortunately using run('sudo su -') blocks execution of the scripts and allows user input. When I type exit or Ctrl+D the scipt resumes, but without root privileges.
I have seen a similar problem in Switching user in Fabric but I am restricted to sudo su - because I am not allowed to change the /etc/sudoers file which contains the following line:
localuser ALL = /usr/bin/su -
I browsed the source of fabric trying to find a workaround but with no success.
Having faced the same problem as yours, (only sudo su - user allowed by admin, sudo -u user -c cmd not allowed), here's my working solution with fabric:
from ilogue.fexpect import expect, expecting, run
def sudosu(user, cmd):
cmd += ' ;exit'
prompts = []
prompts += expect('bash', cmd)
prompts += expect('assword:', env.password)
with expecting(prompts):
run('sudo su - ' + user)
def host_type():
sudosu('root', 'uname -s')
There are several solutions for your issue. First, you want to run commands using sudo. You can use the fabric method sudo instead of run that runs a shell command on a remote host, with superuser privileges(sudo ref).
For example, these commands are executed using sudo :
sudo("~/install_script.py")
sudo("mkdir /var/www/new_docroot", user="www-data")
sudo("ls /home/jdoe", user=1001)
result = sudo("ls /tmp/")
Another idea is that you want to wrap a set of commands (that need to be sudoed).
You can use Fabric context managers (ref) to do that. Particularly, you can use prefix or settings.
For example:
with settings(user='root'):
run('do something')
run('do another thing')
will ask you once the root password then execute commands as root.
You can tweek settings to store the password.
There is one solution for the following problem Sorry, user localuser is not allowed to execute '/usr/bin/su - -c /bin/bash -l -c pwd' as root on hostname.
You can try sudo('mkdir ~/test_fabric',shell=False). Using param shell to avoid the bash param -l.

Can I prevent fabric from prompting me for a sudo password?

I am using Fabric to run commands on a remote server. The user with which I connect on that server has some sudo privileges, and does not require a password to use these privileges. When SSH'ing into the server, I can run sudo blah and the command executes without prompting for a password. When I try to run the same command via Fabric's sudo function, I get prompted for a password. This is because Fabric builds a command in the following manner when using sudo:
sudo -S -p <sudo_prompt> /bin/bash -l -c "<command>"
Obviously, my user does not have permission to execute /bin/bash without a password.
I've worked around the problem by using run("sudo blah") instead of sudo("blah"), but I wondered if there is a better solution. Is there a workaround for this issue?
Try passing shell=False to sudo. That way /bin/bash won't be added to the sudo command. sudo('some_command', shell=False)
From line 503 of fabric/operations.py:
if (not env.use_shell) or (not shell):
real_command = "%s %s" % (sudo_prefix, _shell_escape(command))
the else block looks like this:
# V-- here's where /bin/bash is added
real_command = '%s %s "%s"' % (sudo_prefix, env.shell,
_shell_escape(cwd + command))
You can use:
from fabric.api import env
# [...]
env.password = 'yourpassword'
In your /etc/sudoers file add
user ALL=NOPASSWD: some_command
where user is your sudo user and some_command the command you want to run with fabric, then on the fabric script run sudo it with shell=False:
sudo('some_command', shell=False)
this works for me
In your /etc/sudoers file, you could add
user ALL=NOPASSWD: /bin/bash
...where user is your Fabric username.
Obviously, you can only do this if you have root access, as /etc/sudoers is only writable by root.
Also obviously, this isn't terribly secure, as being able to execute /bin/bash leaves you open to essentially anything, so if you don't have root access and have to ask a sysadmin to do this for you, they probably won't.
Linux noob here but I found this question while trying to install graphite-fabric onto an EC2 AMI. Fabric kept prompting for a root password.
The evntual trick was to pass in the ssh private key file to fabric.
fab -i key.pem graphite_install -H root#servername
You can also use passwords for multiple machines:
from fabric import env
env.hosts = ['user1#host1:port1', 'user2#host2.port2']
env.passwords = {'user1#host1:port1': 'password1', 'user2#host2.port2': 'password2'}
See this answer: https://stackoverflow.com/a/5568219/552671
I recently faced this same issue, and found Crossfit_and_Beer's answer confusing.
A supported way to achieve this is via using env.sudo_prefix, as documented by this github commit (from this PR)
My example of use:
env.sudo_prefix = 'sudo '

Categories