如何在需要sudo的bash脚本中只输入一次密码

数据

  • 我希望在这台机器上的操作员用户能够挂载自己的cifs共享。
  • sudoers文件已经包含了所有操作员的/bin/mount -t cifs //*/* /media/* -o username=*命令。
  • 我希望用户通过一个脚本只需输入一次密码就能挂载共享,而不是两次。
  • sudo密码和cifs密码是相同的。

我已经拥有的内容

这个脚本已经可以工作:

#!/bin/bash
sudo 'mount -t cifs //192.168.1.1/home /media/$USER/home -o username=$USER'

...但这要求用户需要两次输入相同的密码!

  • 第一次用于 sudo
  • 第二次用于挂载本身

这也可以起作用:

#!/bin/bash
echo -n Password: 
read -s szPassword
echo $szPassword | sudo -S sh -c 'echo $szPassword | mount -t cifs //192.168.1.1/home /media/$USER/home -o username=$USER'

...但这将要求我允许所有操作员用户能够sudo sh(严重的安全问题)

问题

如何在bash中挂载cifs共享¹,而不需要将sh放入sudoers文件中也不创建永久/临时文件???

注1:请不要使用python、perl、C、Go等语言?
注2:我知道可以通过sudoers文件来删除密码,但我正在尝试加强安全性,而不是放松它,同时又不放弃便利性...


2printf "%s\n" "$szPassword" "$szPassword" | sudo -S mount -t cifs / ... 这个命令怎么样? - muru
现在试试看!@Muru - Fabby
4个回答

你应该让用户使用sudo来调用脚本,而不是直接使用sudo。只需检查脚本是否以root权限运行,如果没有,则要求用户提供权限。
if [[ $EUID -ne 0 ]]; then
   echo "This script must be run as root, use sudo "$0" instead" 1>&2
   exit 1
fi

不要试图获取用户的密码。


1也许我漏掉了什么,但你能解释一下这是如何将密码提示次数从两次减少到一次的吗?否则我不明白这个回答怎样解决问题。 - Oliphaunt
@Oliphaunt,我没有看到两个密码提示,你能解释一下吗?此外,这个答案不是OP想要的,而是它所需要的。当你需要以root身份运行命令时,这是一个干净且合适的解决方案,就像其他实用工具一样(检查是否为root,否则退出)。 - Braiam
1根据我的理解,OP的问题是用户在使用sudo时被要求输入密码,然后(成功验证后)又被mount再次要求输入密码。在他们的使用情况下,这两个密码是相同的,所以我能理解为什么OP希望用户只需输入一次密码。我不认为你的解决方案对此有帮助。我同意不应该捕获密码。 - Oliphaunt
谢谢,但我可能把问题简化了一点:脚本已经存在,并且已经包含了那个测试(除了 1>&2),它在自动启动中,并且以前只有一个 cifs 共享,但现在有三个,所以真的只需要一个密码。(它已经包含了那个测试,以防止非操作员组成员尝试运行它) - Fabby
@Fabby,我仍然认为你对这个问题的处理方式不正确。针对这些情况,有助手可以安全地“记住”CIFS/SMB共享的密码(例如编辑~/.smbcredentials),甚至无需使用sudo(如果你使用gvfs、umount或polkit)。 - Braiam
我之前不知道~/.smbcredentials,但查了一下,这就是我要避免的事情:把密码放在文本文件中。现在密码只存在于内存中,并且仅在挂载执行期间有效。所以,除非有人在输入密码后、最后一个挂载完成前触发了内核崩溃并进行了完全内存转储,否则不会在任何磁盘上写入任何内容。(请去AU综合聊天室看看,因为这似乎变成了一个聊天室... ;-) - Fabby

我很笨!

以下脚本:

#!/bin/bash
read -p "Password: " -s szPassword
printf "%s\n" "$szPassword" | sudo --stdin mount -t cifs //192.168.1.1/home /media/$USER/home -o username=$USER,password="$szPassword"

只需运行,就能:

  1. 不会创建任何包含密码的文件
  2. 允许用户为多个共享(包括Windows共享)只输入一个密码
  3. 无需额外授权。 :-)

11个问题:这是否会将该命令回显到进程列表中? - Rinzwind
3@Rinzwind 作为一个内置命令,它不应该在bash或zsh中存在。但是mount命令可能会有。 - muru
<<< 也可以用来替代 printf,但更好的方法是完全放弃 read,只使用 sudo --stdin。类似这样的命令:$ printf "输入您的密码\n" && sudo --stdin apt-get update 用户仍然可以输入 sudo 密码,而且不会将其显示在进程列表中。当然,还有无数其他可能的安全问题,比如键盘记录器、sudo 的潜在漏洞等等,无穷无尽的问题。 - Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy:放心大胆地[编辑]吧,伙计! - Fabby

执行此命令无需输入sudo密码;对于mount命令,仍然需要输入密码。

sudoers中,添加类似以下内容:

ALL        ALL = NOPASSWD: /bin/mount -t cifs //*/* /media/* -o username=*

包含此内容后,sudo将不再要求输入密码来执行此特定命令;用户仍然需要提供密码给mount命令。
注意:我直接从您在问题中提供的命令中引用了它的原文;我没有检查通配符是否允许用户执行一些恶意操作。请阅读sudoers手册以获取有关恶意操作的示例。特别要注意的是,在sudoers中的这行允许用户添加任意数量的-o开关或其他参数到mount命令中。您可能需要重新考虑您的方法,例如通过添加一个脚本(如@Braiam建议的)并允许通过sudo运行该脚本而无需额外身份验证。然后,该脚本确保用户只能运行您想要他们运行的特定形式的mount命令。
另外,您也可以将此限制为某个特定组的成员,例如,您可以创建一个名为cifsmount的组,然后进行限制。
%cifsmount ALL = NOPASSWD: /bin/mount -t cifs //*/* /media/* -o username=*

@Fabby 抱歉,但这有什么危险之处?我也许察觉到了一丝讽刺,不太确定。 - Oliphaunt
嗯,即使sudo不需要密码,mount可能仍然需要密码。可以通过/etc/sudoers文件来使sudo免去密码要求。这不会影响mount是否要求输入密码。如果一个人无法成功挂载,那么sudo只会执行一个失败的命令,这通常不是问题。 - TOOGAM
那是我的意图。一个密码而不是两个。 - Oliphaunt
我的意图是在sudoers文件中保留一个"操作员"组,这样如果操作员尝试挂载自己的内容,他们仍然需要输入密码,但对于挂载"标准"操作员挂载点,他们只需输入一次密码而不是5次... 但你的解决方案可能适用于其他人,所以给予赞同票...(原始评论已移除) - Fabby

解决这些问题的一般方法是将以下引导放置在您需要使用sudo的脚本顶部:
#!/bin/bash
case $EUID in
   0) : cool we are already root - fall through ;;
   *) # not root, become root for the rest of this session
      # (and ask for the sudo password only once)
      sudo $0 "$@" ;;
esac
# now the present process is effective-UID  (root)
# so there's no need to put sudo in front of commands

any more commands here will run as superuser ...

显然,这种方法的缺点是,如果脚本中的某些命令不需要使用sudo来运行,那么这里就存在一种不必要的权限提升。
无论如何,我想分享一下这个小技巧。最好的一点是,如果你已经以有效用户ID为root(例如,如果你已经在sudo下调用了它),它会优雅地做正确的事情。而且,给出错误并强制你重新输入/重新运行(带有sudo)是不太友好的。
你还可以查看man 5 sudoers中的timestamp_timeout变量,它告诉sudo记住用户凭据的时间限制(可以是小数)。

我过于简化了脚本以获得一个简单的答案:脚本已包含:#测试是否为root用户,如果不是则退出 if [[ $EUID -ne 0 ]]; then echo "必须以root权限运行此脚本,请使用sudo "$0"代替" 1>&2 exit 1 fi 关键是它还包含多个具有相同密码的挂载点(再次:已删除),不过还是谢谢... - Fabby
1重点是上述解决方案只需要输入一次密码(用于sudo)。它不应该需要任何其他密码。此外,这是一个通用的解决方案(比出现错误并重新输入带有sudo的命令更优雅),适用于所有类似问题,即使它们需要多个(超过两个)特权命令。 - arielf
应该,但它没有!**;-)** 这是因为每个CIFS共享挂载都需要一个密码。 (因为它可以与Ubuntu密码不同,但在这种情况下,操作员将Windows和Ubuntu密码保持同步) - Fabby