如何执行 sudo + exec?

如何执行 sudo + exec?

我需要用来sudo以不同的用户身份运行进程。但如何使用sudo类似exec

sudo -u www-data exec php -r 'sleep(2); echo 5;'

替换 sudo 进程?

为什么和要求?

  • 最小化总进程数
  • 命令是连续启动的,不可能一次性启动它们sudo
  • 该解决方案需要表现得像没有exec,即没有背景等。

答案1

从 sudo(8) 的手册页:

作为一种特殊情况,如果策略插件没有定义 close 函数并且不需要 pty,则 sudo 将直接执行命令,而不是先调用 fork(2)。 sudoers 策略插件仅在启用 I/O 日志记录、需要 pty 或启用 pam_session 或 pam_setcred 选项时定义关闭函数。

在正常情况下,这意味着如果您确保进程与 TTY 断开连接,它将不会使用 fork。例如:

sudo -u www-data php -r 'sleep(2); echo 5;' < /dev/null > /dev/null 2>&1

将避免额外的过程。如果您需要某些输出,请将它们定向到一个非 TTY 的文件或类似文件,并将其余部分保留/dev/null如上所示。

我注意到在进程列表中看到原始 sudo 命令仍然存在副作用,但也许这对您来说没问题。或者,可能有一些 php 函数或类似的调用来更改进程在进程列表中的显示方式,以弥补这一需求。

更新:

许多系统似乎默认启用了 pam 选项。可以禁用它们,但请注意,存在一些副作用,例如“正在运行的命令可能不会更新资源限制”。有关详细信息,请参阅 sudoers(5)。要全局禁用它们,您可以在中添加以下行/etc/sudoers

Defaults        !pam_setcred
Defaults        !pam_session

检查 sudoers(5) 手册页以了解如何将设置限制为特定用户或类似用户;在手册页中查找“Default_Type”。

答案2

编辑:这是我原来的答案(引用是因为 SO 不支持删除线):

原来的:

正确的答案是你根本做不到。至少不可靠且容易。

已修复:正确的答案是可以。非常非常仔细。请参阅底部的附录。

fork()尽管手册页在其他评论者引用的部分中说了些什么,但大多数时候(99.9%)的系统上默认的 sudo 进程模型都是如此。

htop您可以使用或 之类的过程监视器轻松地自行验证真相ps --forest

甚至建议的答案:

<?php
exec("exec sudo -u www-data php -r 'sleep(2); echo 5;' < /dev/null > /dev/null 2>&1");

将留下挥之不去的 sudo 进程:

php spawn-test.php
 └─ sudo -u www-data php -r 'sleep(2); echo 5;'
     └─ php -r 'sleep(2); echo 5;'

只要 php 调用 sudo 驱动脚本以某种方式绑定到某个控制终端(编辑:或将子进程编写到 pty)。

其原因可能是“现代”sudo设置。sudo使用 tty 识别进行票务和pam插件,这些插件也可以在您的会话中执行任何他们想要的操作。在某些情况下,大多数软件通常只是简单地编译 pam 即可禁用execve()路径。然后你就有了像 systemd-logind 等交互的“现代”东西。

sudo是一个非常复杂的软件,让它做你想做的事情并不容易,

原来的:

而在你的情况下,这基本上是不可能的。

编辑:根据您的情况,也许仍然有可能。

然而,让多个sudo实例等待它们的孩子通常不是问题。在 *nix 系统上,进程是非常便宜的实体,即使在今天,当它们与数以百万计的 .so 库动态链接时也是如此。

您也可以放心,sudo流连忘返“适当地”,这意味着它消耗最少的计算资源(几乎为零),并且操作系统足够智能,可以在所有正在运行的实例之间共享尽可能多的内存sudo。所以你根本不应该将其视为问题。

现在的问题是,为什么要摆脱sudo中间的流程?

如果原因是您不“喜欢”那里的 sudo 进程,而没有适当的理由,那么这是错误的原因。

当然,当然可以通过分别通过 SUID root 和删除权限的方式来完成,但这种方法对初学者不友好,很难正确执行,所有这些都使其极其危险。

如此之多,即使是专家也普遍不赞成,所以我们在这里不讨论如何实现这一点。

不过,如果你有足够的毅力,即使在这里,在 stackoverflow 网络上,也有如何root通过 SUID 切换回的答案。

请记住,您不能信任该进程状态中的任何内容(甚至是环境变量),并且必须尽快将权限正确地授予目标用户(并正确执行此操作)。

此外,根据您的部署平台,您可能还需要考虑 selinux 或其他安全框架等内容。

最后,一旦您的孩子的用户与其驱动父进程的用户不同,就不可能向其发送信号来更改其状态,而无需跳过更多的环节。

因为它非常困难且容易出错,所以这是sudo存在的唯一原因,但我同意你的观点sudo并不总是适合每个用例。

编辑:

请参阅 user11658273 关于如何禁用所有高级功能的答案:pam 集成、pty 创建和日志服务器 - 似乎所有这些都必须禁用才能工作。即使您在本地配置了 PAM,它也应该使 sudo 进入无分支模式。

要足够小心。

禁用pam_session可能会破坏某些东西,具体取决于正在设置的 PAM 会话,而我(仅想象)pam_setcred可能会破坏 IPA、Kerberos 或其他依赖于通过 PAM 发出的凭据票证的东西。log_servers不经常使用,并且!use_pty您的目标脚本将继承其父级设置的前 3 个文件描述符。我无法在这个环境中测试这是否真的阻止 sudo 验证“远程”帐户。

更好的是,为了最大限度地减少机器所有其他用户的潜在损坏,您应该仅向相关用户授予这些特殊权限: Defaults: yourscriptuser !pam_session,!pam_setcred,!use_pty,!log_servers

了解您在做什么以及从父脚本发送信号(例如终止子脚本)可能不起作用:

$ sudo cat /etc/sudoers | grep \!use_pty
Defaults: testuser !pam_session,!pam_setcred,!use_pty,!log_servers

# in one terminal:
$ sudo sh -c 'echo $$ && exec sleep 88888' 
27749

# in other terminal:
$ kill -TERM 27749                 
kill: kill 27749 failed: operation not permitted

确保您的子脚本在完成工作后或从标准输入句柄读取零字节时始终退出(即不时读取并在空读取时退出) - 这是标准的unix指示文件已关闭(在本例中为管道来自父级),这应该使所有徘徊的子级在父级终止时仍然保持运行状态时正确退出。否则,它们最终可能会无限期地徘徊,具体取决于它们的代码(为您分叉 sudo 句柄)。

答案3

在运行您指定的命令之前,Sudo 会分叉(编辑:并非总是如此,请参阅我的其他答案)。您不能使用 exec 撤消分叉。当在 bash 中使用 exec 时,它完全避免了 fork,这就是它在 bash 中工作的原因 - sudo otoh 没有此功能。但是,您可以像这样解决问题,将程序分叉到后台,然后退出中间进程:

sudo -u www-data bash -c "php -r 'sleep(2); echo 5;' &"

这还有一些其他副作用,可能对您来说可能是问题,也可能不是问题,例如丢失原始父进程信息并且不等待进程完成。

答案4

这可以通过将以下行添加到您的sudoers文件中来实现(使用sudo visudo或进行编辑sudo -e /etc/sudoers):

# Run commands for all users with the exec system call directly.
# This may break some authentication systems or scripts.                              
Defaults !pam_session,!pam_setcred,!use_pty,!log_servers

# Run commands by "user" with the exec system call directly.
# This may also break authentication, but only for one user.
Defaults:user !pam_session,!pam_setcred,!use_pty,!log_servers

以下是sudo进行上述更改之前运行的程序的进程列表:

$ sudo sleep 500 &
[1] 4453
$ ps -f T
UID        PID  PPID  C STIME TTY      STAT   TIME CMD
root      4453  3478  0 00:16 pts/3    S      0:00 sudo sleep 500
root      4454  4453  0 00:16 pts/3    S      0:00 sleep 500

进行上述更改后:

$ sudo sleep 500 &
[1] 4479
$ ps -f T
UID        PID  PPID  C STIME TTY      STAT   TIME CMD
root      4479  3478  0 00:18 pts/3    S      0:00 sleep 500

如果需要,可以通过使用不同的“默认”类型来“影响任何主机上的所有用户、特定主机上的所有用户、特定用户、特定命令或作为特定用户运行的命令”。请参阅sudoers(5)手册了解更多信息。

正如 @etosan 所提到的,禁用 PAM 将破坏 PAM 的身份验证,并可能将用户和管理员锁定在外,特别是在多用户和远程系统上。 @etosan 的另一个好处是,不再可能以启动它的用户的身份终止使用 sudo 启动的程序,因为原始 sudo 进程没有运行并且无法代表用户转发信号。在某些情况下,这可能被认为是一种安全改进,但它也可能会破坏依赖此功能的程序。

sudo 文档并不总是全面且通常难以理解,因此如果重要的话我建议尝试阅读源代码。

在我的系统(Gentoo Linux 配置文件default/linux/amd64/17.1/no-multilib/hardened/selinux)上,发行版默认配置默认禁用use_ptylog_servers,因此不需要后两个选项。

此配置也可能会降低 的安全性sudo,再次参考sudoers(5)

pam_session  On systems that use PAM for authentication, sudo will
             create a new PAM session for the command to be run in.
             Unless sudo is given the -i or -s options, PAM session
             modules are run with the “silent” flag enabled.  This
             prevents last login information from being displayed
             for every command on some systems.  Disabling
             pam_session may be needed on older PAM implementations
             or on operating systems where opening a PAM session
             changes the utmp or wtmp files.  If PAM session support
             is disabled, resource limits may not be updated for the
             command being run.  If pam_session, pam_setcred, and
             use_pty are disabled, log_servers has not been set and
             I/O logging has not been configured, sudo will execute
             the command directly instead of running it as a child
             process.  This flag is on by default.

             This setting is only supported by version 1.8.7 or
             higher.

pam_setcred  On systems that use PAM for authentication, sudo will
             attempt to establish credentials for the target user by
             default, if supported by the underlying authentication
             system.  One example of a credential is a Kerberos
             ticket.  If pam_session, pam_setcred, and use_pty are
             disabled, log_servers has not been set and I/O logging
             has not been configured, sudo will execute the command
             directly instead of running it as a child process.
             This flag is on by default.

             This setting is only supported by version 1.8.8 or
             higher.

对于use_pty(默认情况下可能被禁用):

use_pty      If set, and sudo is running in a terminal, the command
             will be run in a pseudo-terminal (even if no I/O log‐
             ging is being done).  If the sudo process is not at‐
             tached to a terminal, use_pty has no effect.

             A malicious program run under sudo may be capable of
             injecting commands into the user's terminal or running
             a background process that retains access to the user's
             terminal device even after the main program has fin‐
             ished executing.  By running the command in a separate
             pseudo-terminal, this attack is no longer possible.
             This flag is off by default.

相关内容