如何在Python中使用setuid?

7gyucuyw  于 2022-12-10  发布在  Python
关注(0)|答案(1)|浏览(215)

我读取了this,但无法解决问题。我不知道如何使用setuid
我有一个小的python应用程序,在linux机器上运行python命令和bash命令。我只希望下面这部分以普通用户身份运行。应用程序以sudo python3 app.py运行,因为做一些需要sudo权限的命令。我只上传我想以普通用户身份运行的部分。
我怎么能那样做呢?

import alsaaudio

m = alsaaudio.Mixer('Capture')
m.setvolume(10) # set volume
vol = m.getvolume() # get volume float value
moiiocjp

moiiocjp1#

一个人不能简单地随意来回改变一个进程的所有者(好吧,一个人可以用适当的称呼:os.seteuid,我在开始回答这个问题后才知道。无论哪种方式,这个特定的问题需要做的事情比设置UID更多-答案的底部有一个子进程方法):只有root用户可以执行此操作,并且一旦完成此操作,就不再是root用户可以切换回来。因此,技巧是在一个独占运行有限任务的进程中调用它。
您可以派生进程并在子进程上调用os.setuid,然后终止它:

import alsaaudio
import os
import sys
...

if not os.fork():
    # this block just runs on the child process: fork returns a non-0 PID on the parent
    os.setuid(<usernumber>)
    m = alsaaudio.Mixer('Capture')
    m.setvolume(10) # set volume
    vol = m.getvolume() # get volume float value
    sys.exit(0)  # terminates the child process. 

# normal code that will just run on the parent process (as root)
# continues:
...

当然,这不会使vol变量在父进程上可用--您必须设置一种方法来沿着该值。
在这种情况下,可以使用Python多处理,而不是fork,通过多处理队列发送vol值,并添加所需的暂停来破坏其他用户进程:如果你正在写“生产质量”的代码,而这些代码需要在第三方计算机上处理大量的极端情况,那么这是很好的。2如果你的对象是一个简单的脚本,用于在你的机器中设置一些东西,那么将值写入一个文件,并在父文件中阅读它将更容易:

import alsaaudio
import os
import sys
import tempfile
import time
import pickle

...

_, file_ = tempfile.mkstemp()

if not os.fork():
    # this block just runs on the child process: fork returns a non-0 PID on the parent
    os.setuid(<usernumber>)
    m = alsaaudio.Mixer('Capture')
    m.setvolume(10) # set volume
    vol = m.getvolume() # get volume float value
    with open(file_, "wb") as file:
         pickle.dump(vol, file)
    sys.exit(0)  # terminates the child process. 

# normal code that will just run on the parent process (as root)
# continues:

time.sleep(0.1)  # may need calibration
vol = pickle.load(open(file_, "rb"))
os.unlink(file_)
...

根据OP注解,这不仅仅是更改有效UID的问题,还包括环境变量,以及重新导入alsaaudio模块-“fork”不会将其删除(因为env变量没有更改,并且在Python端更改os.environment条目可能不会在alsalib初始化时反映在alsalib的本地代码端)。
在这种情况下,使用subprocess.Popen运行进程,并确保在它之前的环境正确,可以达到预期的效果。进程间通信可以通过捕获子进程stdout来解决--这样我们就可以避免pickle。另外,默认的子进程调用是同步的,不需要为目标子进程设置任意的暂停来初始化alsa并选择它的值:在默认调用中,主进程将在子进程完成后继续。
我并不是在这里尝试alsaaudio,这可能是因为在子进程上需要比我设置的2个环境变量更多的环境变量,如果是这样的话,就继续根据需要将then添加到代码的env={ ...}部分。
这个示例只是按照您的要求执行操作,并将“vol”的值传递回来--如果您需要更多的数据,请将“encoding”参数放到子进程中,并将任意数据pickle到子进程上的sys.stdout中--然后您可以使用pickle检索它。

import alsaaudio
import os
import sys

import tempfile
import time
import pickle

vol = None

def child_code():
    import alsaaudio 
    # no need to reload: when running as a subprocess, the top-level import (if any)
    # will also run as the new user
    m = alsaaudio.Mixer('Capture')
    m.setvolume(10) # set volume
    vol = m.getvolume() # get volume float value
    print(vol)
    
    ...

def parent_code():

    import subprocess
    import pwd
    target_user = 1000
    user_data = os.getpwuid(target_user).pw_dir()
    # if alsalib fails, you may need to setup other variables
    env = {"HOME": user_data, "PWD": user_data, }
    # __file__ bellow may have to be changed if this code
    # is in a submodule in a larger project.
    proc = subprocess.run(
        ["/usr/bin/env", "python3", __file__  ], 
        env=env,
        user=target_user,
        capture_output=True,
        encoding="utf-8"
    )
    vol = literal_eval(proc.stdout)
    return vol
    ...
    
if os.getuid() == 0:
    # runs  on parent, UID==1, process
    vol = parent_code()
else:
    # runs on subprocess.
    child_code()
    sys.exit(0)

...

相关问题