Context
Imagine I have a application with a Python server running as user 'python-app' on Linux and a Python client also running as user 'python-app'. The user 'python-app' does not have root permissions. Thus, the Python application cannot open files it does not have access to.
The purpose of the application is to allow users to perform some task on a file they own.
Another user 'user' (who exists on the Linux host running the application) accesses the application and authenticates themselves somehow (e.g., OAuth2, username/password, whatever, not relevant for this question). That user successfully authenticates.
Now, I need the application (running as user 'python-app') to perform actions on behalf of the user 'user' using the 'user' permission set.
Question
How do I access files as the application on behalf of the user without doing sudo -u 'user' <command>
and potentially having to re-enter credentials every time?
What I would love is:
sudo -u 'user' --token <token> <command>
Is this not possible? I feel like I am missing something and I just don't know what. I've reviewed other enterprise applications that do this, such as RStudio Server (relevant source), but I don't conceptually understand what is happening.
I figured out a solution. It requires that some aspect of the application run as root with sudo
. The solution here is to:
sudo service
uid
)gid
) for that userA minimally working example.
import os
import subprocess
import pam
import pwd
import grp
from getpass import getpass
def authenticate(user, pwd):
return pam.authenticate(user, pwd, service='common-auth')
def report_ids(msg):
print(msg)
print('uid', os.getuid())
print('gids', os.getgroups())
def spawn_process(user):
pw_record = pwd.getpwnam(user)
def demote():
def result():
report_ids('starting demotion')
# set multiple group ids instead of just one
# os.setgid(pw_record.pw_uid)
groups = [g.gr_gid for g in grp.getgrall() if user in g.gr_mem]
gid = pwd.getpwnam(user).pw_gid
groups.append(grp.getgrgid(gid).gr_gid)
try:
groups.remove(27) # sudo group
except ValueError:
pass # Silently ignore if element not found
os.setgroups(groups)
os.setuid(pw_record.pw_gid)
report_ids('finished demotion')
return result
# Some generic bash script with a different owner and group id
# $ ls -l process.sh
# -rwxrwx--- 1 user test 28 Jan 8 15:17 process.sh
# $ cat process.sh
# #!/bin/bash
# ls /home/other-user
subprocess.Popen(["./process.sh"], preexec_fn=demote())
if __name__ == "__main__":
user = input("Username: ")
passwd = getpass("Password: ")
if authenticate(user, passwd) is False:
print("Failed to authenticate user")
exit(1)
# Launch process as another user
spawn_process(user)
exit(0)
Resources:
I would be curious to know if others find any problems with this approach