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.
Related
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.
[root#hostname ~]# python script.py # allow this
[user#hostname ~]$ sudo python script.py # deny this
[user#hostname ~]$ sudo -E python script.py # deny this
[user#hostname ~]$ sudo PATH=$PATH python script.py # deny this
[user#hostname ~]$ python script.py # kindly refuse this
I'm trying to achieve the behavior above. Read further if you care why or if the example isn't sufficient enough. Sorry for the sharp tongue, but most of my Stack Exchange questions get hostile questions back instead of answers.
This question arises from requiring an admin to run my script, but the nature of the script requires root's environment variables (and not sudo's).
I've given this some thorough research... below is from this answer
if os.geteuid() == 0:
pass # sufficient to determine if elevated privileges
But then I started needing to access PATH inside of my script. I noticed that
sudo -E env | grep PATH; env | grep PATH
prints different PATH values. I found it was because of the security policy on PATH. I also found the workaround to PATH is sudo PATH=$PATH ...
However, it's not the only policy protected environment variable, and at that point, why push this enumeration of environment variables on the script user? It seems that requiring root explicitly is the best approach, and just warn the admin to use root explicitly from within the script otherwise.
Is there such a way to distinguish between root and sudo with Python?
Despite the reasons discussed to not pursue this solution, I actually did find it for others wondering if it's possible.
[user#hostname ~]$ sudo python
>>> import os
>>> os.environ["SUDO_UID"] # UID of user running sudo
'uid'
And when logged in as root...
[root#hostname ~]# python
>>> import os
>>> try:
... uid = os.environ["SUDO_UID"]
raise AssertionError("Ran with sudo")
... except KeyError, e:
... ... # SUDO_UID, SUDO_USER, etc. not set without sudo
I also found a way to access root's PATH just running with sudo.
path = os.popen("su - -c env | grep ^PATH= | cut -d'=' -f2-").read().strip()
I think I like this solution better than relying on how my script is ran.
You're going to get "hostile questions" because the premise of your issue doesn't make much sense. In general if a command can be run as the root user via sudo then it should not matter whether it was run via sudo (or runas, etc.) or by some other mechanism that has the UID set to root such as an interactive login as the root user. You should not require running a program to be predicated on an interactive login as the root user account rather than via a setuid program like sudo or your program if it were setuid root.
A cheap and dirty solution is to ensure the interactive root login sets a unique env var that is unlikely to be set when your program is run via sudo. That is, however, obviously easy to spoof so if you're doing this for security then that approach is not acceptable.
Use the subprocess module to run commands and check the output:
from subprocess import check_output
uid = check_output(['bash', '-c', 'echo $UID']).decode().strip()
if uid != '0':
sys.exit() # or return
or
user = check_output(['whoami']).decode().strip()
if user != 'root':
sys.exit() # or return
It appears that aside from checking $PATH, root and sudo are indistinguishable.
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.
I am using Python Fabric. Here is my fabfile.
env.user='XXX'
env.password='XXX'
env.key_filename='/key/path'
env.hosts=[<ip>]
def install():
run('ifconfig')
run('su - hduser')
run('ls')
run('sudo mkdir -p /usr/lib/jvm')
When I run the above fabfile it login to my node and runs 'ifconfig' then i am login into the user hduser and i want to execute all my future command in /home/hduser. But it is executing the commands in root. How can i fix this ? Also if it is asking the password. How can i pass the password in script?
Thanks
Navaz
First Off, every run command is a separate connection to the computer. This means that after you run the
su - hduser
the next command will run as if that was never issued.
To run nested commands you need to use
with settings(user='hduser',password='Passw0rd1'):
run('ls')
will run the 'ls' command as the hduser and in the hduser home directory and with supply the password.
To run commands in a directory you can also use
with cd('/path/to/dir'):
run ('command in path')
http://docs.fabfile.org/en/latest/usage/env.html
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 '