Drop root privileges for certain operations in Python - python

In my Python script, I perform a few operations that need root privileges. I also create and write to files that I don't want to be owned exclusively by root but by the user who is running my script.
Usually, I run my script using sudo. Is there a way to do the above?

You can switch between uid's using os.seteuid(). This differs from os.setuid() in that you can go back to getting root privileges when you need them.
For example, run the following as root:
import os
open('file1', 'wc')
# switch to userid 501
os.seteuid(501)
open('file2', 'wc')
# switch back to root
os.seteuid(0)
open('file3', 'wc')
This creates file1 and file3 as root, but file2 as the user with uid 501.
If you want to determine which user is calling your script, sudo sets two environment variables:
SUDO_USER
SUDO_UID
Respectively the username and the uid of the user who called sudo. So you could use int(os.environ['SUDO_UID']) to use with os.seteuid().

http://linux.die.net/man/8/sudo quote:
The real and effective uid and gid are set to match those of the target user
So, your only option of knowing which user to use is to read the target user from either a config file or a cmdline option, or someway of heuristical guessing.
A good idea is the so called rights shedding: Start with root privilegs, then do what you nedd them for. Then become a less privileged user.
You would use the os module for that:
http://docs.python.org/2/library/os.html#os.setuid

I found that using os.seteuid and os.setegid didn't actually drop the root privileges. After calling them I was still able to do things that required root privileges. The solution I found that worked was to use os.setresuid and os.setresgid instead:
sudo_uid = int(os.getenv("SUDO_UID"))
sudo_gid = int(os.getenv("SUDO_GID"))
# drop root privileges
os.setresgid(sudo_gid, sudo_gid, -1)
os.setresuid(sudo_uid, sudo_uid, -1)
subprocess.call("mkdir /foo1", shell = True) # should fail
# regain root privileges
os.setresgid(0, 0, -1)
os.setresuid(0, 0, -1)
subprocess.call("mkdir /foo2", shell = True) # should succeed

Related

"/Library" directory permission denied on Mac - Python3

I'm trying to create a program that copies a directory in the library directory on mac (path : "/Library"). I use shutil which works very well in other directories but not in the Library directory...
I want to be able to compile my program, so I can't run it as root.
Here is my code :
import shutil
def copy(src_path, dir_path):
try:
shutil.copytree(src_path, dir_path)
print("Success!")
except:
print("Impossible to copy the folder...")
print("Failed!")
copy("/Users/marinnagy/Desktop/Test", "Library/Test")
I think it's because the library directory is protected and requires authentication to make changes.
Do I have to make an authentication request to the user ? Or do I need to use another method than shutil ?
Thanks for your help !
After a good deal of research and many attempts, I finally managed to copy a folder into my Library directory.
On macOS, the process of writing to a protected directory like the Library directory is blocked for python program. Once compiled (I use pyinstaller), it seems to be impossible for a python application to access this kind of folder, even if you give the app Full Disk Access in the System Preferences.
So I used some AppleScript to manage this specific copy/paste task :
on run {scr_path, dir_path} # Run with arguments
# Translate standard paths to their quoted form
set formated_scr_path to quoted form of scr_path
set formated_dir_path to quoted form of dir_path
# Run a simple shell script to copy the repertory in the other
do shell script "cp -R " & formated_scr_path & space & formated_dir_path ¬
with administrator privileges # Ask for administrator privileges
end run
Then, in my python program, I call the AppleScript program when I want to copy/past to a protected repertory like the Library repertory :
import subprocess
def copy(scr_path, dir_path):
# Use the osascript process to call the AppleScript
# Give the paths in arguments
process = subprocess.call(['osascript', "path/to/applescript",
scr_path, dir_path])
return process
copy("path/to/folder 1", "path/to/folder 2")
This method worked for me on protected repertories. The AppleScript run in the background and an authentication window pop in, asking the user to identify himself as an admin :
result screenshot

How to get pathlib’s unix path handling goodies without the system specific ones, eg Path.resolve() changes /tmp to /private/tmp

Hit an interesting case with pathlib where I am using it to represent paths on a android device, not the machine the python is actively running on. Is it still possible to make use of pathlib’s sugary syntax and ability to resolve unix-wide truisms such as relative paths “../../“ without resolving device specific symlink things like “/tmp/path” -> “/private/tmp/path”?
For the most part, pathlib makes handling paths on the device super easy, but i run into problems when wanting to resolve a path on the device without using the host python machine’s symlink resolving mechanisms.
I love using pathlib instead of strings. Is it possible to do path manipulations and actions with the Path object but then send off the final file command to the device ssh’d into?
The only piece from resolve i’d like is the ability to turn (Path(“/tmp/analysis/ptool”) / “../../“).resolve take care of the .. but not change /tmp to /private/tmp, which is not a symlink on the device i’m about to use this path command with.
The example is a little trivial “just do it with strings”, but the organization and automation i want to apply this on is significantly cleaner and easier to read as Path objects instead of strings. If possible, it would be great to make it work.
In [1]: import pathlib
In [2]: from pathlib import Path
In [3]: Path('/tmp/hello').resolve()
Out[3]: PosixPath('/private/tmp/hello')
MacOS, Python3.7.3
/private/tmp is where /tmp contents are actually stored on Mac OS. On this platform, /tmp is a symlink. You can see this from the operating system, without using Python at all:
$ ls -l /tmp
lrwxr-xr-x 1 root wheel 11 Oct 22 2018 /tmp -> private/tmp
$ (cd /tmp && pwd -P)
/private/tmp
pathlib.Path.resolve() replaces symlinks with the absolute paths they point to, so it's doing exactly what you're asking for.
See Why is /tmp a symlink to /private/tmp? on our sister site Ask Different.
using a whole Path in this case seems wrong as it exposes methods like stat which will try and operate on your local system, rather than the remote device. using a PurePosixPath looks better as it doesn't expose as much, and it's easy to add in a resolve method in a derived class:
from pathlib import PurePosixPath
class ResolvingPosixPath(PurePosixPath):
def resolve(self):
if not self.is_absolute():
raise ValueError('only able to resolve absolute paths')
sep = self._flavour.sep
path = ''
for name in str(self).split(sep):
if not name or name == '.':
# current dir
continue
if name == '..':
# parent dir
path, _, _ = path.rpartition(sep)
continue
path = path + sep + name
return path or sep
I've ripped the relevant code from pathlib._PosixFlavour.resolve, and
you're obviously free to use shorter class names if that's convenient!
this can be used as you suggest you want:
hello = ResolvingPosixPath('/tmp') / 'hello'
print(hello.resolve())
giving me '/tmp/hello'

Adding a single python executable to windows system PATH for multiple computers?

I've created a command line program that I would like to distribute to some folks at work. Getting them all to install the python interpreter is simply unrealistic. Hence, I've created a single .exe file using PyInstaller. I am coming to realize however, that most people don't even know how to navigate to the directory where the .exe is, in order to invoke it. (And as of now, I haven't yet figured out how to get the program to run when clicked.) Is there a way to make the program add it's self to the users sys PATH when it is run the first time or would this require an installer? Thanks!
The common trap would be to read the PATH env. variable by using os.environ('PATH'). That would be a big mistake because this variable contains user & system paths mixed together. That's a special case for the PATH variable.
What you need to do is to fetch PATH env variable from the registry (user part), update it if needed, and write it back.
You can achieve that using winreg module, modifying the user PATH environment variable (or create if doesn't exist for this particular user)
read user PATH variable
if exists, tokenize the paths (else, path list defaults to empty)
compute the path of the current module (using os.path.dirname(__file__))
check if already in the path, if so, exit (I print the path list in that case so you can test)
create/update PATH user env. variable with the updated path list if necessary
Code:
import winreg,os
script_directory = os.path.dirname(__file__)
paths = []
key_type = winreg.REG_EXPAND_SZ # default if PATH doesn't exist
try:
keyQ = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Environment', 0, winreg.KEY_QUERY_VALUE)
path_old, key_type = winreg.QueryValueEx(keyQ, "PATH")
winreg.CloseKey(keyQ)
paths = path_old.split(os.pathsep)
except WindowsError:
pass
if script_directory in paths:
# already set, do nothing
print(paths)
else:
# add the new path
paths.append(script_directory)
# change registry
keyQ = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Environment', 0, winreg.KEY_WRITE)
winreg.SetValueEx(keyQ, 'PATH', 0, key_type, os.pathsep.join(paths))
winreg.CloseKey(keyQ)
Note that the user will have to logoff/logon for changes to take effect. Another solution would be to call setx on the PATH variable. System call, ugly, but effective immediately.
# change registry with immediate effect
import subprocess
subprocess.call(["setx","PATH",os.pathsep.join(paths)])
Or, courtesy to eryksun, some python code to propagate the registry changes to new processes. No need to logoff, no need for ugly setx, just call broadcast_change('Environment') using the code below:
import ctypes
user32 = ctypes.WinDLL('user32', use_last_error=True)
HWND_BROADCAST = 0xFFFF
WM_SETTINGCHANGE = 0x001A
SMTO_ABORTIFHUNG = 0x0002
ERROR_TIMEOUT = 0x05B4
def broadcast_change(lparam):
result = user32.SendMessageTimeoutW(HWND_BROADCAST, WM_SETTINGCHANGE,
0, ctypes.c_wchar_p(lparam), SMTO_ABORTIFHUNG, 1000, None)
if not result:
err = ctypes.get_last_error()
if err != ERROR_TIMEOUT:
raise ctypes.WinError(err)
(seems that I have to refactor some code of my own with that last bit :))
env. variable read code took from here: How to return only user Path in environment variables without access to Registry?

Python - Relative Paths in os.system calls

I am building a python utility which automates sysadmin type tasks. Part of the tool involves writing scripts and then calling them with powershell from the python interface. An example of such code is this:
def remote_ps_session():
target = raw_input("Enter your target hostname: ")
print "Creating target.ps1 file to establish connection"
pstarget = open("pstarget.ps1", "w")
pstarget.write("$target = New-Pssession " + target + "\n")
pstarget.write("Enter-PSSession $target" + "\n")
pstarget.close()
print "File created. Initiating Connection to remote host..."
os.system("powershell -noexit -ExecutionPolicy Unrestricted " + "C:\path\to\my\file\pstarget.ps1")
I would like to do two things which I think can be answered with the same method, I've just yet to find out what is best (importing vs variables vs initial setup definitions and so on)
For simplicity we'll say the utility is in C:\utility and the powershell functions are in a functions folder one level deeper: C:\utility\functions
I want to be able to specify a location for 1) where the script (the file that is written) is saved to and then 2) refer to that location when the os.system call is made. I want this to be able to run on most/any modern Windows system.
My thoughts on possibilities are:
When the script launches get the current directory and save that as a variable, if I need to go back a directory take that variable and remove everything after the last \ and so on. Doesn't seem ideal.
On the first launch of the file prompt for system locations to put in variables. For instance have it prompt 'where do you want your log files?' 'where do you want your output files?' 'where do you want your generated scripts?' These could then be referred to as variables but would break if they ever moved folders and may not be easy to 'fix' for a user.
I imagine there is some way to refer to current directories and navigate to ..\parallel folder to where I am executing from. ....\2 folders up, but that also seems like it might be messy. I've yet to see what a standard/best practice for managing this is.
Edit: based on some comments I think __file__ might be the place to start looking. I'm going to dig in to this some but any examples (for example: __file__/subfoldernameor whatever the usage would be would be cool.
Python has a lib dedicated to path manipulation os.path, so anytime you need filesystem paths manipulation take a look at it.
As for your particular questions, run the following example, to see how you can use the functions from this lib:
test.py
import os
# These two should basicly be the same,
# but `realpath` resolves symlinks
this_file_absolute_path = os.path.abspath(__file__)
this_file_absolute_path1 = os.path.realpath(__file__)
print(this_file_absolute_path)
print(this_file_absolute_path1)
this_files_directory_absolute_path = os.path.dirname(this_file_absolute_path)
print(this_files_directory_absolute_path)
other_script_file_relative_path = "functions/some.ps"
print(other_script_file_relative_path)
other_script_file_absolute_path = os.path.join(this_files_directory_absolute_path,
other_script_file_relative_path)
print(other_script_file_absolute_path)
print("powershell -noexit -ExecutionPolicy Unrestricted %s" %
other_script_file_absolute_path)
You should get output similar to this:
/proj/test_folder/test.py
/home/user/projects/test_folder/test.py
/proj/test_folder
functions/some.ps
/proj/test_folder/functions/some.ps
powershell -noexit -ExecutionPolicy Unrestricted /proj/test_folder/functions/some.ps

uwsgi is running my app as root, but shouldn't be

I have a Flask app run via uwsgi being served by nginx, all being controlled by supervisord
I have set my user parameter in /etc/supervisor.conf to user=webdev
and in both ../myapp/uwsgi_app.ini and /etc/uwsgi/emperor.ini, I have set uid=webdev and gid=www-data
Problem is, I am having a permissions issue within my app. with the following print statements in one of my views, I discover that the application is being run as root. This is causing issues in a function call that requires creation of a directory.
All of the following print statements are located inside the Flask view.
print 'group!! {}'.format(os.getegid())
print 'user id!! {}'.format(os.getuid())
print 'user!! {}'.format(os.path.expanduser('~'))
results in...
group!! 1000
user id!! 1000
user!! /root
EDIT: I added the following print statements:
from subprocess import call
print 'here is user',
call('echo $USER', shell=True)
print 'here is home',
call('echo $HOME', shell=True)
This prints
here is user root
here is home /root
in a terminal on the server, I type $ id, I get uid=1000(webdev) gid=1000(webdev) groups=1000(webdev)
Here is the output from $ getent group
root:x:0:
...
webdev:x:1000:
...
www-data:x:1001:nginx
nginx:x:996:nginx
...
Here are some lines from /etc/passwd
webdev:x:1000:1000::/home/webdev:/bin/bash
That's strange, because normally you wouldn't have any permissions issues when running as root (the opposite actually, you'd have more permissions than necessary in this case).
I have the feeling that you might be running the process as webdev and not root after all. Can you try calling os.getuid() instead of os.expanduser()?
The /rootdirectory is often used as a default when there is no home directory set for a user. You can also check your /etc/passwd/ for webdev's entry to see what the home directory is set to.
If you're not running as root, your permissions issue are probably related to something else (maybe webdev isn't the owner of the directory you're writing in?).
EDIT: If you want user webdev to have a proper home directory, run the following as root:
mkdir -p /home/webdev
usermod -m -d /home/webdev webdev
After that, os.expanduser() should display the correct home directory.
EDIT 2: I was wrongly assuming that webdev was not a normal user but just a minimally configured service username like www that you were using. My mistake.
In any case, as I mentioned in the comment, what matters is your uid value. You're not running as root because your uid is not 0. Nothing else matters in UNIX terms.
I think that I figured it out though. The way uWSGI works when you specify the uid & gid options is that it still runs as root originally but immediately calls setuid() to drop its privileges and switch to the uid and gid you provided. This would explain the behavior you're seeing: the environment is still configured for root and even though uWSGI is now running as webdev, $USER and $HOME must be still pointing to root.
You can try to test this by adding this line inside the Flask view:
open('/home/webdev/testfile', 'a').close()
This will create an empty file in webdev's home directory. Now log in afterwards as webdev, go to /home/webdev and do an ls -l. If the owner of testfile is webdev, you're running as webdev.
If you can establish that, then what you'll have to do is write all your code assuming that $HOME and $USER are wrongly set. I'm not sure how it will affect your code, but try, for instance, to avoid relative paths (it is possible assumed that the default destination is your wrong home directory).

Categories