在linux系统下,对于用户的权限的管理,是通过uid来识别和鉴权的。你登录时用的用户名,在系统里有一个uid与之相对应,不同的用户名可以对应到同一个uid,反之则不可以,gid也是如此,他们都是一串数字。
系统并不认我们的用户名和组名,它只认uid和gid,你可以通过修改password文件实现两个不同的用户拥有相同的uid,不过这样做除了会造成一些混乱外,没有什么实际意义。
本文想要讨论的是,如何修改python进程的uid和gid。
在研究如何修改进程的uid之前,先考虑一下修改进程uid是否有实践意义。如果你是root用户,执行的程序是某个普通用户的程序,那么这个程序在执行期间就拥有了root用户的权限,这是一个危险的事情,如果能够将进程的uid,gid修改为普通用户,那么就可以消除这种危险。
我现在root用户下新建一个1.txt文件,这个文件,hadoop用户是无权修改的,接下来,我要编写一个脚本,由root用户启动,但是在启动后,将进程的uid修改为hadoop的uid,脚本里尝试修改1.txt文件,如果所有操作正常,文件将无法正常打开。
import os
import grp
import pwd
def change_user(username):
user = pwd.getpwnam(username)
uid = user.pw_uid # uid
gid = user.pw_gid # gid
home = user.pw_dir # 用户home目录
gids = [g.gr_gid for g in grp.getgrall() if username in g.gr_mem] # 用户组
os.setgid(gid)
try:
os.setgroups(gids)
except Exception as e:
print('Failed to set groups %s' % e)
os.setuid(uid)
#os.chdir(home)
change_user('hadoop')
with open('1.txt', 'w')as f:
f.write('bbb')
程序果然报错
PermissionError: [Errno 13] Permission denied: '1.txt'
上面的例子,需要普通用户的程序配合才行,似乎实用性不强,那么,咱们来看jupyterhub这个实用性比较强的例子。
jupyterhub实现了多租户管理, 可以通过进程的方式启动notebook,每个hub的用户在机器上有一个同名的系统用户。
hub采用的是PAM认证方法来管理用户登录,
notebook是hub启动的,而hub是root账户启动的,在notebook启动时,必须修改notebook进程的uid,否则notebook进程将拥有同root一样的权限。
这意味着,当一个用户,在notebook上编写代码时,自然的拥有了root账户的权限,这是十分危险的。
为了控制notebook进程的权限,jupyterhub所采用的就是修改进程uid的方法,不同于我上面所举的例子,notebook进程是hub的子进程,那么hub是如何设置子进程的uid的呢?
self.proc = Popen(cmd, **popen_kwargs)
hub使用subprocess模块的Popen方法来启动notebook子进程,秘密就藏在参数popen_kwargs里
popen_kwargs = dict(
preexec_fn=self.make_preexec_fn(self.user.name),
start_new_session=True, # don't forward signals
)
preexec_fn参数的值是一个函数,在子进程启动后执行,hub就是在这里修改了子进程的uid
def make_preexec_fn(self, name):
return set_user_setuid(name)
def _try_setcwd(path):
while path != '/':
try:
os.chdir(path)
except OSError as e:
exc = e # break exception instance out of except scope
print("Couldn't set CWD to %s (%s)" % (path, e), file=sys.stderr)
path, _ = os.path.split(path)
else:
return
print("Couldn't set CWD at all (%s), using temp dir" % exc, file=sys.stderr)
td = mkdtemp()
os.chdir(td)
def set_user_setuid(username, chdir=True):
import grp
import pwd
user = pwd.getpwnam(username)
uid = user.pw_uid
gid = user.pw_gid
home = user.pw_dir
gids = [g.gr_gid for g in grp.getgrall() if username in g.gr_mem]
def preexec():
"""Set uid/gid of current process
Executed after fork but before exec by python.
Also try to chdir to the user's home directory.
"""
os.setgid(gid)
try:
os.setgroups(gids)
except Exception as e:
print('Failed to set groups %s' % e, file=sys.stderr)
os.setuid(uid)
# start in the user's home dir
if chdir:
_try_setcwd(home)
return preexec
功能的核心实现在set_user_setuid里,这个函数返回preexec,作为preexec_fn的实参,在子进程创建以后执行,这样就改变了notebook子进程的权限,从root用户变为普通用户。
QQ交流群: 211426309