以不同用户身份运行Linux服务的最佳实践

142

在我的 RHEL 服务器上,服务默认会在启动时以 root 身份运行。如果我没记错的话,其他使用 /etc/init.d 初始化脚本的 Linux 发行版也是这样。

你认为最好的方法是什么,可以让进程以我选择的(静态)用户身份运行?

我想到的唯一方法就是使用类似以下的内容:

 su my_user -c 'daemon my_cmd &>/dev/null &'

但这看起来有些杂乱无序...

是否有一些神奇的方法,可以提供一个简单的机制,以其他非root用户的身份自动启动服务?

编辑:我应该说,在这种情况下我要启动的进程是Python脚本或Java程序。我不想编写本地包装器,所以很遗憾,我不能像Black建议的那样调用setuid()


Python没有提供对setuid()系统调用族的访问吗?与Perl相比,这似乎是一个严重的缺陷。 - Jonathan Leffler
13
哇,没错:os.setuid(uid)。每一天都是一个学校日! - James Brady
8个回答

68

在 Debian 上,我们使用 start-stop-daemon 工具,它可以处理 pid 文件、更改用户、将守护进程置于后台等等。

我不熟悉 RedHat,但是你已经在使用的 daemon 工具(它在 /etc/init.d/functions 中定义)被广泛认为是等同于 start-stop-daemon 的工具,因此它也可以更改程序的 uid,或者您现在正在使用的方法已经是正确的。

如果你在网上查找,有几个现成的包装器可供使用。有些甚至可能已经在 RedHat 中打包了。例如,看一下 daemonize


x-ref很有趣。我有自己的daemonize程序,非常相似;不会做pidfile或lockfile,但会设置umask。我有一个单独的SUID root程序用于设置UID、GID、EUID、EGID和辅助组(称为asroot)。我使用'asroot [opts] -- env -i [env] daemonize [opts] -- command [opts]'。 - Jonathan Leffler
继续上文:POSIX标准的env程序不接受在环境设置和执行命令之间加入“--”(这很烦人,但是就是这样)。 - Jonathan Leffler
4
在upstart脚本中如何使用/etc/init.d/functions中的守护进程函数?请给出一个示例。 - Meglio
10
在Debian中,请查看/etc/init.d/skeleton。在do_start()函数中添加UID和GID变量,并使用以下命令:start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --chuid $UID:$GID -- $DAEMON_ARGS - Jonathan Ben-Avraham
我注意到daemon()在我的RHEL和CentOS盒子的/etc/rc.d/init.d/function中都有定义。 - quickshiftin

53

在查看了这里所有的建议后,我发现了一些东西,希望对其他和我处境相似的人有所帮助:

  1. hop is right to point me back at /etc/init.d/functions: the daemon function already allows you to set an alternate user:

    daemon --user=my_user my_cmd &>/dev/null &
    

    This is implemented by wrapping the process invocation with runuser - more on this later.

  2. Jonathan Leffler is right: there is setuid in Python:

    import os
    os.setuid(501) # UID of my_user is 501
    

    I still don't think you can setuid from inside a JVM, however.

  3. Neither su nor runuser gracefully handle the case where you ask to run a command as the user you already are. E.g.:

    [my_user@my_host]$ id
    uid=500(my_user) gid=500(my_user) groups=500(my_user)
    [my_user@my_host]$ su my_user -c "id"
    Password: # don't want to be prompted!
    uid=500(my_user) gid=500(my_user) groups=500(my_user)
    
为了解决surunuser的这种行为,我已经将我的init脚本更改为以下内容:
if [[ "$USER" == "my_user" ]]
then
    daemon my_cmd &>/dev/null &
else
    daemon --user=my_user my_cmd &>/dev/null &
fi

感谢大家的帮助!

5
  • 一些守护进程(例如apache)通过调用setuid()来自行完成此操作。
  • 您可以使用setuid-file flag将进程作为不同的用户运行。
  • 当然,您提到的解决方案也是可行的。

如果您打算编写自己的守护进程,则建议调用setuid()。 这样,您的进程就可以

  1. 利用其根权限(例如打开日志文件,创建pid文件)。
  2. 在启动过程中的某个时刻放弃其根权限。

3

还有一些需要注意的事项:

  • 在init.d脚本中使用sudo是不可行的,因为它需要tty(“sudo: sorry, you must have a tty to run sudo”)
  • 如果您正在将Java应用程序作为守护程序运行,您可能需要考虑使用Java Service Wrapper(它提供了设置用户ID的机制)
  • 另一种选择可能是su --session-command=[cmd] [user]

3
在CENTOS(Red Hat)虚拟机上作为svn服务器: 编辑/etc/init.d/svnserver, 将pid更改为svn可以写入的内容:
pidfile=${PIDFILE-/home/svn/run/svnserve.pid}

并添加了选项--user=svn

daemon --pidfile=${pidfile} --user=svn $exec $args

原始的pid文件为/var/run/svnserve.pid。由于只有root用户才能在此处写入,因此守护程序未能启动。

 These all work:
/etc/init.d/svnserve start
/etc/init.d/svnserve stop
/etc/init.d/svnserve restart

3
这会产生特权升级漏洞。svn用户现在可以在/home/svn/run/svnserve.pid文件中放置任意进程ID,每当停止或重新启动svn服务时,这些进程将被杀死,而不是svn进程。 - rbu

2

需要注意以下几点:

  • 正如您所提到的,如果您已经是目标用户,则su会提示输入密码
  • 同样地,setuid(2)在某些操作系统上将无法成功,如果您已经是目标用户
  • setuid(2)不会安装/etc/limits.conf(Linux)或/etc/user_attr(Solaris)中定义的特权或资源控制
  • 如果您选择使用setgid(2)/setuid(2),请不要忘记调用initgroups(3) -- 更多信息请参见这里

通常我会使用/sbin/su在启动守护进程之前切换到适当的用户。


2

为什么不在初始化脚本中尝试以下操作:

setuid $USER application_name

对我来说它起作用了。


3
并非所有的发行版都有这个功能。我在 RHEL 7 上尝试了一下,出现了“setuid: command not found”的错误信息。 - Cocowalla

0

我需要将一个Spring .jar应用程序作为服务运行,并找到了一种简单的方法来指定特定用户运行:

我将我的jar文件的所有者和组更改为我想要运行的用户。然后在init.d中创建符号链接并启动服务。

因此:

#chown myuser:myuser /var/lib/jenkins/workspace/springApp/target/springApp-1.0.jar

#ln -s /var/lib/jenkins/workspace/springApp/target/springApp-1.0.jar /etc/init.d/springApp

#service springApp start

#ps aux | grep java
myuser    9970  5.0  9.9 4071348 386132 ?      Sl   09:38   0:21 /bin/java -Dsun.misc.URLClassPath.disableJarChecking=true -jar /var/lib/jenkins/workspace/springApp/target/springApp-1.0.jar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接