Sudo
Sudo 允许系统管理员委派权限,使特定用户(或用户组)能够以 root 或其他用户身份运行命令,同时提供命令及其参数的审计追踪。
Sudo 是以 root 身份运行命令时 su 的替代方案。与 su 不同(su 会启动一个允许后续所有命令拥有 root 权限的 root shell),sudo 为单个命令授予临时的权限提升。通过仅在需要时启用 root 权限,使用 sudo 可以降低因输入错误或所调用命令中的 bug 导致系统损毁的可能性。
Sudo 还可以用于以其他用户身份运行命令;此外,sudo 会将所有命令和失败的访问尝试记录到 日志 (journal) 中,以便进行安全审计。
安装
用法
要开始以非特权用户身份使用 sudo,必须进行正确配置。参见 #配置。
要使用 sudo,只需在命令及其参数前加上 sudo 和一个空格。
$ sudo cmd
例如,使用 pacman:
$ sudo pacman -Syu
更多信息请参见 sudo(8)。
登录 shell
你不能仅仅通过在命令前加 sudo 就在另一个用户下运行所有命令。特别是在使用 重定向 和 命令替换 时,你必须使用登录 shell,可以通过 sudo -iu user 轻松访问(如果目标用户是 root,可以省略 -u user)。
在以下示例中,命令替换在完整的 shell 中可以工作,但在前面加 sudo 时会失败:
$ sudo wpa_supplicant -B -i interface -c <(wpa_passphrase MYSSID passphrase)
Successfully initialized wpa_supplicant Failed to open config file '/dev/fd/63', error: No such file or directory Failed to read or parse configuration '/dev/fd/63'
配置
默认骨架 (Defaults skeleton)
sudoers(5) § SUDOERS OPTIONS 列出了所有可与 /etc/sudoers 文件中的 Defaults 命令一起使用的选项。
参见 [1] 获取以 sudoers 优化格式列出的选项列表(从 1.8.7 版本源代码解析)。
更多信息请参见 sudoers(5),例如如何配置密码超时。
查看当前设置
运行 sudo -ll 打印当前的 sudo 配置,或运行 sudo -lU user 查看特定用户的配置。
使用 visudo
sudo 的配置文件是 /etc/sudoers。应当始终使用 visudo(8) 命令进行编辑。visudo 会锁定 sudoers 文件,将修改保存到临时文件,并在将其复制到 /etc/sudoers 之前检查语法错误。
sudoers绝对不能有语法错误!任何错误都会导致 sudo 无法使用。务必使用 visudo 进行编辑以防止错误。- visudo(8) 警告说,将 visudo 配置为遵循用户环境变量中选择的编辑器可能存在安全漏洞,因为它允许拥有 visudo 权限的用户只需将该变量设置为其他内容,即可在不留日志的情况下以 root 身份运行任意命令。
visudo 的默认编辑器是 vi。sudo 软件包编译时带有 --with-env-editor 选项,并支持使用 SUDO_EDITOR、VISUAL 和 EDITOR 变量。当设置了 VISUAL 时,不会使用 EDITOR。
要在当前 shell 会话期间将 nano 设为 visudo 编辑器,请导出 EDITOR=nano;若只想使用一次不同的编辑器,只需在调用 visudo 之前设置变量:
# EDITOR=nano visudo
或者,你可以编辑 /etc/sudoers 文件的副本,并使用 visudo -c /copy/of/sudoers 进行检查。如果你想规避 visudo 对文件的锁定,这可能会派上用场。
要永久更改编辑器,请参见 环境变量#每用户。要在系统范围内仅为 visudo 永久更改所选编辑器,请在 /etc/sudoers 中添加以下内容(假设 nano 是你首选的编辑器):
# Set default EDITOR to restricted version of nano, and do not allow visudo to use EDITOR/VISUAL. Defaults editor=/usr/bin/rnano, !env_editor
示例条目
要允许用户在命令前加 sudo 时获得完整的 root 权限,请添加以下行:
USER_NAME ALL=(ALL:ALL) ALL
要允许用户在主机名为 HOST_NAME 的机器上以任何用户身份运行所有命令:
USER_NAME HOST_NAME=(ALL:ALL) ALL
要允许 wheel 组的成员拥有 sudo 访问权限:
%wheel ALL=(ALL:ALL) ALL
wheel 组启用 sudo 访问权限并将用户添加到该组,因为默认情况下 Polkit 将 wheel 组的成员视为管理员。如果用户不是 wheel 组的成员,使用 Polkit 的软件可能会要求使用 root 密码而不是用户密码进行验证。要禁用用户 USER_NAME 的密码询问:
Defaults:USER_NAME !authenticate
仅为主机 HOST_NAME 上的用户 USER_NAME 启用显式定义的命令:
USER_NAME HOST_NAME=/usr/bin/halt,/usr/bin/poweroff,/usr/bin/reboot,/usr/bin/pacman -Syu
%wheel 组中,这样的行应该放在 %wheel 行之后。仅为主机 HOST_NAME 上的用户 USER_NAME 启用显式定义的命令且无需密码:
USER_NAME HOST_NAME= NOPASSWD: /usr/bin/halt,/usr/bin/poweroff,/usr/bin/reboot,/usr/bin/pacman -Syu
详细的 sudoers 示例位于 /usr/share/doc/sudo/examples/sudoers。否则,请参阅 sudoers(5) 获取详细信息。
Sudoers 默认文件权限
sudoers 文件的所有者和组都必须是 0(root)。文件权限必须设置为 0440。这些权限是默认设置的,但如果你不小心更改了它们,应立即改回,否则 sudo 将失效。
# chown -c root:root /etc/sudoers # chmod -c 0440 /etc/sudoers
技巧与提示
禁用密码提示符超时
一个常见的烦恼是,在后台终端运行的长时进程平时以普通权限运行,仅在需要时提升权限。这会导致 sudo 密码提示符出现后未被注意到并超时,此时进程会死掉,所做的工作也会丢失,或者充其量只是被缓存了。常见的建议是启用免密码 sudo,或者延长 sudo 记住密码的超时时间。这两者都有负面的安全影响。提示符 (prompt) 超时也可以禁用,由于这不具备任何合理安全目的,它应该是此处的解决方案:
Defaults passwd_timeout=0
传递别名 (Passing aliases)
以下内容仅在 bash completion 不可用时(无论是完整的还是如上所述简化的)才相关:Zsh 和 Bash 中的别名通常仅在命令的第一个单词展开。这意味着在运行 sudo 命令时,你的别名通常不会被展开。使下一个单词展开的一种方法是为 sudo 创建一个以空格结尾的别名。将以下内容添加到你的 shell 配置文件中:
alias sudo='sudo '
zshmisc(1) § ALIASING 描述了其工作原理:
- 如果替换文本以空格结尾,则 shell 输入中的下一个单词始终符合别名展开的目的。
- 如果别名值的最后一个字符是空格,则别名后面的下一个命令单词也会被检查是否进行别名展开。
在密码提示符中添加终端铃声
为了吸引用户注意后台终端中的 sudo 提示符,用户可以简单地让它回显一个 铃声字符 (bell character):
Defaults passprompt="^G[sudo] password for %p: "
注意 ^G 是一个字面铃声字符。例如在 vim 中,使用序列 Ctrl+v Ctrl+g 插入。如果 Ctrl+v 被映射(例如用于粘贴),通常可以使用 Ctrl+q 代替。在 nano 中,使用 Alt+v Ctrl+g。
另一个选项是设置 SUDO_PROMPT 环境变量。例如,将以下内容添加到你的 shell 配置文件中:
export SUDO_PROMPT=$'\a[sudo] password for %p: '
禁用分终端 sudo
如果你对 sudo 的默认设置(每次打开新终端都需要输入密码)感到厌烦,请将 timestamp_type 设置为 global:
Defaults timestamp_type=global
减少输入密码的次数
如果你对每 5 分钟(默认值)就需要重新输入一次密码感到厌烦,可以通过为 timestamp_timeout 设置一个较长的值(以分钟为单位)来更改:
Defaults timestamp_timeout=10
如果你在长脚本中使用 sudo 命令,并且不想在超时过期时等待用户输入,可以在循环中单独运行 sudo -v 来刷新超时(而 sudo -K 会立即撤销它)。
环境变量
如果你有很多环境变量,或者你通过 export http_proxy="..." 导出了代理设置,使用 sudo 时这些变量不会传递给 root 账户,除非你使用 -E/--preserve-env 选项运行 sudo。
$ sudo -E pacman -Syu
推荐的保留环境变量的方法是将它们追加到 env_keep:
/etc/sudoers
Defaults env_keep += "ftp_proxy http_proxy https_proxy no_proxy"
Root 密码
用户可以配置 sudo 询问 root 密码而不是用户密码,方法是在 /etc/sudoers 的 Defaults 行中添加 targetpw(目标用户,默认为 root)或 rootpw:
Defaults targetpw
为了防止向普通用户泄露 root 密码,你可以将其限制在特定组:
Defaults:%wheel targetpw %wheel ALL=(ALL) ALL
禁用 root 登录
用户可能希望禁用 root 登录。没有 root 账户,攻击者必须首先猜出配置为 sudoer 的用户名以及该用户的密码。参见例如 OpenSSH#Deny。
- 请小心,禁用 root 登录可能会导致你被锁在系统外。Sudo 不是自动安装的,其默认配置既不允许无密码 root 访问,也不允许使用你自己的密码进行 root 访问。在禁用 root 账户之前,请确保某个用户已正确配置为 sudoer!
- 如果你已经更改了 sudoers 文件以将 rootpw 作为默认设置,那么请不要使用以下任何命令禁用 root 登录!
- 如果你已经被锁在外面,请参阅 密码恢复 获取帮助。
账户可以通过 passwd 锁定:
# passwd -l root
类似的命令可以解锁 root:
$ sudo passwd -u root
或者,你可以使用以下命令删除密码,然后锁定 root 账户:
$ sudo passwd -dl root
再次启用 root 登录:
$ sudo passwd root
注意,这仅仅是禁用了基于密码的登录。用户仍可能能够使用其他身份验证令牌(例如 SSH 密钥)登录。要彻底禁用该账户,请使用:
$ usermod --expiredate 1 root
在系统紧急情况下,恢复提示符会要求你输入 root 密码,从而导致无法登录恢复 shell。要在紧急情况下自动解锁 root 账户,请使用 补充文件 (drop-in file) 将 SYSTEMD_SULOGIN_FORCE=1 环境变量添加到 rescue.service:
/etc/systemd/system/rescue.service.d/SYSTEMD_SULOGIN_FORCE.conf
[Service] Environment=SYSTEMD_SULOGIN_FORCE=1
sudo -i。kdesu
kdesu 可在 KDE 下使用,以 root 权限启动 GUI 应用程序。默认情况下,kdesu 可能会尝试使用 su,即使 root 账户已被禁用。幸运的是,可以告诉 kdesu 使用 sudo 代替 su。创建/编辑文件 ~/.config/kdesurc:
[super-user-command] super-user-command=sudo
或者使用以下命令:
$ kwriteconfig6 --file kdesurc --group super-user-command --key super-user-command sudo
Sudo 加固示例
假设你创建了 3 个用户:admin、devel 和 archie。用户 "admin" 用于 journalctl、systemctl、mount、kill 和 iptables;"devel" 用于安装软件包和编辑配置文件;而 "archie" 是你登录使用的用户。要让 "archie" 能够重启、关机和使用 netctl,我们将执行以下操作:
编辑 /etc/pam.d/su 和 /etc/pam.d/su-l。要求用户必须在 wheel 组中,但不要将任何人放入该组。
#%PAM-1.0 auth sufficient pam_rootok.so # Uncomment the following line to implicitly trust users in the "wheel" group. #auth sufficient pam_wheel.so trust use_uid # Uncomment the following line to require a user to be in the "wheel" group. auth required pam_wheel.so use_uid auth required pam_unix.so account required pam_unix.so session required pam_unix.so
将 SSH 登录限制在 'ssh' 组。只有 "archie" 将属于这个组。
# groupadd -r ssh # gpasswd -a archie ssh # echo 'AllowGroups ssh' >> /etc/ssh/sshd_config
重启 sshd.service。
将用户添加到其他组。
# for g in power network ;do ;gpasswd -a archie $g ;done # for g in network power storage ;do ;gpasswd -a admin $g ;done
设置配置文件的权限,以便 devel 可以编辑它们。
# chown -R devel:root /etc/{http,openvpn,cups,zsh,vim,screenrc}
Cmnd_Alias POWER = /usr/bin/shutdown -h now, /usr/bin/halt, /usr/bin/poweroff, /usr/bin/reboot Cmnd_Alias STORAGE = /usr/bin/mount -o nosuid\,nodev\,noexec, /usr/bin/umount Cmnd_Alias SYSTEMD = /usr/bin/journalctl, /usr/bin/systemctl Cmnd_Alias KILL = /usr/bin/kill, /usr/bin/killall Cmnd_Alias PKGMAN = /usr/bin/pacman Cmnd_Alias NETWORK = /usr/bin/netctl Cmnd_Alias FIREWALL = /usr/bin/iptables, /usr/bin/ip6tables Cmnd_Alias SHELL = /usr/bin/zsh, /usr/bin/bash %power ALL = (root) NOPASSWD: POWER %network ALL = (root) NETWORK %storage ALL = (root) STORAGE root ALL = (ALL) ALL admin ALL = (root) SYSTEMD, KILL, FIREWALL devel ALL = (root) PKGMAN archie ALL = (devel) SHELL, (admin) SHELL
通过这种设置,你几乎永远不需要以 root 用户身份登录。
"archie" 可以连接到他们的家庭 Wi-Fi。
$ sudo netctl start home $ sudo poweroff
"archie" 不能以任何其他用户身份使用 netctl。
$ sudo -u admin -- netctl start home
当 "archie" 需要使用 journalctl 或 kill 失控进程时,他们可以切换到该用户。
$ sudo -i -u devel $ sudo -i -u admin
但 "archie" 无法切换到 root 用户。
$ sudo -i -u root
如果 "archie" 想以 admin 身份启动 gnu-screen 会话,可以这样做:
$ sudo -i -u admin [admin]$ chown admin:tty `echo $TTY` [admin]$ screen
使用 /etc/sudoers.d 中的补充文件配置 sudo
sudo 会解析目录 /etc/sudoers.d/ 中包含的文件。这意味着你可以不编辑 /etc/sudoers,而是在独立的文件中更改设置并将其放入该目录。这有两个优点:
- 无需编辑
sudoers.pacnew文件; - 如果新条目有问题,你可以删除该问题文件而不是编辑
/etc/sudoers(但请参阅下面的警告)。
这些补充文件中的条目格式与 /etc/sudoers 本身相同。要直接编辑它们,请使用 visudo -f /etc/sudoers.d/somefile。详情请参见 sudoers(5) § Including other files from within sudoers。
/etc/sudoers.d/ 目录中的文件按字典顺序解析,跳过包含 . 或 ~ 的文件名。为了避免排序问题,文件名应以两位数字开头,例如 01_foo。
/etc/sudoers.d/ 中的文件与 /etc/sudoers 本身一样脆弱:任何格式不正确的文件都会导致 sudo 无法工作。因此,出于同样的原因,强烈建议使用 visudo。编辑文件
sudo 提供了 sudoedit 命令(相当于 sudo -e)。这对于编辑只能由 root 编辑的文件非常有用,同时仍然以普通用户身份运行编辑器并使用该用户的配置。
要编辑文件,请将 SUDO_EDITOR 设置为编辑器的名称,并将文件名传递给 sudoedit。例如:
$ SUDO_EDITOR=vim sudoedit /etc/file
参见 #使用 visudo 和 sudo(8) § e 了解设置编辑器的方法,但要警惕可能的安全问题。
如果将多个名称传递给 sudo,所有文件将在一次调用中在编辑器中打开。这对于合并文件很有用:
$ SUDO_EDITOR=vimdiff sudoedit /etc/file /etc/file.pacnew
启用侮辱性信息 (Insults)
用户可以通过使用 visudo 在 sudoers 文件中添加以下行,来启用 sudo 中的侮辱性彩蛋。
/etc/sudoers
Defaults insults
在输入错误密码时,这会将 Sorry, try again. 消息替换为幽默的侮辱性语言。
启用密码输入回显反馈
默认情况下,输入密码时没有视觉反馈。这是出于额外的安全考虑。但是,如果你希望有视觉输入反馈,可以通过添加此行来启用:
/etc/sudoers
Defaults pwfeedback
彩色密码提示符
要使用颜色和/或粗体字体自定义密码提示符,请在 shell 初始化文件中设置 SUDO_PROMPT 环境变量,并使用 tput(1)。
例如,要将密码提示符设置为粗体红色的 Password: ,请使用:
export SUDO_PROMPT="$(tput setaf 1 bold)Password:$(tput sgr0) "
或者对默认消息使用不同的颜色,如下所示:
export SUDO_PROMPT="$(tput setab 1 setaf 7 bold)[sudo]$(tput sgr0) $(tput setaf 6)password for$(tput sgr0) $(tput setaf 5)%p$(tput sgr0): "
更多信息请参见 控制台彩色输出 和 Bash/提示符自定义。
使用 U2F
U2F 非常适合与 sudo 配合使用,因为它可以有效消除在公共区域被 窥屏 (shoulder surfing) 的风险,同时仍然让你能够通过简单的物理触摸来有意识地批准提示。
写入受保护的文件
使用 sudo 时,你可能想要写入受保护的文件。使用 tee 可以实现这种分离:
$ input stream | sudo tee --option protected_file_1 protected_file_2...
此时简单的 >/>> 将无法工作。
在 Vim 中
当你编辑一个由其他用户拥有的文件却忘记使用 sudo 启动 Vim 时,一个类似的概念很有用。在这种情况下,你可以在 Vim 中执行以下操作来保存文件:
:w !sudo tee %
你可以将其添加到 ~/.vimrc 中,以便在命令模式下通过 :w!! 映射轻松使用此技巧:
~/.vimrc
" Allow saving of files as sudo when I forgot to start vim using sudo cmap w!! w !sudo tee > /dev/null %
> /dev/null 部分显式地丢弃标准输出,因为我们不需要将任何内容传递给另一个管道命令。
有关此技巧的工作原理及原因的更详细解释,请参阅 StackOverflow 上的文章:Vim “以 sudo 写入”的技巧是如何工作的?。
在不安装 sudo 软件包的情况下使用 sudo-rs
你可以将 sudo-rs 作为 sudo 的独立替代品使用,而不需要 sudo 软件包。
参考 sudo 的默认配置创建 /etc/pam.d/sudo。
同时为 sudo -i 创建 /etc/pam.d/sudo-i。
# ln -s /etc/pam.d/sudo /etc/pam.d/sudo-i
创建或编辑 /etc/sudo-rs/config.toml。
/etc/sudo-rs/config.toml
[defaults] askpass = false timeout = 15
sudo-rs 同时支持 /etc/sudoers 和 /etc/sudoers-rs,如果后者存在则使用后者。
可选地,你可以通过在优先级较高的 PATH 目录中创建符号链接,将 sudo 替换为 sudo-rs:
# ln -s /usr/bin/sudo-rs /usr/local/bin/sudo
故障排除
没有 TTY 时的 SSH 问题
在运行远程命令时,SSH 默认不会分配 tty。如果没有分配 tty,sudo 就无法防止密码被显示。你可以使用 ssh 的 -t 选项强制其分配 tty。
Defaults 选项 requiretty 仅在用户拥有 tty 时才允许其运行 sudo。
# Disable "ssh hostname sudo <cmd>", because it will show the password in clear text. You have to run "ssh -t hostname sudo <cmd>". # #Defaults requiretty
宽松的 umask
Sudo 会将用户的 umask 值与其自身的 umask(默认为 0022)取并集。这可以防止 sudo 创建比用户 umask 允许的权限更开放的文件。虽然在没有自定义 umask 的情况下这是一个合理的默认设置,但这可能导致由 sudo 运行的工具创建的文件权限与由 root 直接运行时的权限不同。如果因此产生错误,sudo 提供了一种修正 umask 的方法,即使所需的 umask 比用户指定的 umask 更宽松。添加此项(使用 visudo)将覆盖 sudo 的默认行为:
Defaults umask = 0022 Defaults umask_override
这将 sudo 的 umask 设置为 root 的默认 umask (0022) 并覆盖默认行为,始终使用指定的 umask,无论用户设置了什么 umask。