简介
系统管理员可以通过 sudo 实用程序让用户或组能够作为另一个用户运行命令。换句话说,可以分派命令特权,而不需要另一个用户的密码。root 用户通过在 /etc/sudoers 文件中设置 sudo 条目完成这个过程。使用 visudo 命令编辑此文件。在分派特权时,必须相信得到特权的用户会慎重地使用它。这里要澄清一个误解:sudo 不仅用于让用户作为 root 用户运行某些命令;它主要用于让另一个用户作为应用程序用户/所有者运行应用程序或系统命令。如果系统上当前安装了 sudo,它不会覆盖现有的 sudoers 文件。但是,一定要保留 /etc/sudoers 的备份并阅读升级说明。
安装 sudo
下载 sudo 的最新版本。
对于本文,我使用 sudo version 1.7.2。如果您使用 AIX® 5.3,那么一定要有最新的 gcc 版本 (4.0.0)。
# export LIBPATH=/usr/lib # ./configure --with-aixauth # make # make install
作为 root 用户使用以下命令检查安装的版本,查看构建选项和当前安装的配置:
# sudo -V Sudo version 1.7.2 Sudoers path: /etc/sudoers Authentication methods: 'aixauth' < rest of output truncated>
在默认情况下:
- sudo 放在 /usr/local/bin 中。使用它作为另一个用户运行命令。
- visudo 放在 /usr/local/sbin 中。使用它编辑 sudoers 文件。
- sudoers 文件(如果还没有的话)放在 /etc 中。这个文件包含 sudo 条目。
sudoers 文件
/etc/sudoers(通常简称为 sudoers)文件控制谁可以使用 sudo 以及使用 sudo 运行什么。 root 用户或具有根特权的用户可以设置这些条目。sudoers 中 sudo 条目最基本的形式如下:
<user> <host> = <user to alias> <password required> < command to run>
作为某一用户运行以下命令,可以看到此用户能够使用 sudo 运行什么命令以及其他限制:
sudo -l
运行 sudo 命令的一般格式为:
sudo -u < user to run as> <command to run>
日志记录
通过在 /etc/syslog.conf 文件中使用以下设置,可以使用 syslog 在 /var/adm/messages 中记录通过 sudo 运行的所有命令:
*.debug /var/adm/messages
但是,我认为 sudo 命令应该记录在一个单独的文件中,这样便于查看已经运行的 sudo 命令。当然,这也有助于监视失败的 sudo 活动。创建 /var/adm/sudo.log 文件,然后在 /etc/sudoers 文件中添加以下条目:
Defaults logfile=/var/adm/sudo.log Defaults !syslog
现在,无论执行成功还是失败,所有 sudo 活动都会记录在 /var/adm/sudo.log 中。
管理 sudoers
随着时间的推移,sudoers 文件中的条目会越来越多。这是因为服务器上设置的应用程序环境越来越多,也可能因为进一步分派当前任务以隔离责任。随着条目增加,输入错误会越来越常见。让 root 用户更方便地管理 sudoers 文件是有积极意义的。我们来讨论两种实现这个目标(至少建立良好的标准)的方法。如果有许多静态条目(在有 sudo 的每台机器上都运行相同的命令),那么把这些命令放在一个单独的 sudoers 文件中,这可以使用 include 指令来实现。
如果有许多用户条目,在添加或删除条目时会耗时很长。对于这种情况,好做法是对用户进行分组。可以把用户直接分组在一起,这些组是有效的 AIX 组。
现在详细讨论这两种方法。
包含文件
在大型企业环境中,维护 sudoers 文件是一项重要且必须经常执行的任务。简化这项任务的一个解决方案是重新组织 sudoers 文件。一种做法是提取出静态或可重用的条目,它们在每台机器上都运行相同的命令。与审计/安全或 storix 备份或一般性能报告一样,现在可以对 sudo 使用 include 指令。主 sudoers 文件可以包含本地条目,包含文件包含静态条目,所以很少需要编辑。在调用 visudo 时,它会扫描 sudoers。当看到 include 条目时,它会扫描此文件,然后返回到主 sudoers 文件继续扫描。在从主 sudoers 文件退出 visudo 时,它会进入包含文件进行编辑。退出包含文件之后,返回到 AIX 提示。可以有多个包含文件,但是我不认为有必要这么做。
假设我们的辅助 sudoers 文件名为 sudo_static.<hostname>。在本文的示例中,使用的主机名是 rs6000。在主 sudoers 文件中,设置以下条目:
#include /etc/sudo_static.rs6000
接下来,在 /etc/sudo_static.rs6000 文件中添加一些条目。不必设置所有 sudoers 指令或小节。如果这个文件包含不必要的条目,就不要包含它们。例如,我的包含文件只包含以下文本。
bravo rs6000 = (root) NOPASSWD: /usr/opt/db2_08_01/adm/db2licd -end bravo rs6000 = (root) NOPASSWD: /usr/opt/db2_08_01/adm/db2licd bravo rs6000 = (db2inst) NOPASSWD: /home/db2inst/sqllib/adm/db2start bravo rs6000 = (db2inst) NOPASSWD: /home/db2inst/sqllib/adm/db2stop force
在运行 visudo、保存并退出文件时,visudo 会让您按 Enter 开始编辑包含 sudoers 文件。与主文件一样,编辑此文件之后,sudo 会指出语法错误(如果有的话)。另外,可以使用
visudo -f /etc/sudo_static.rs6000
直接编辑包含文件。
使用组
属于有效的 AIX 组的用户可以包含在 sudoers 中,这样针对单一用户的条目更少,sudoers 文件更容易管理。在使用组重新组织 sudoers 条目时,可能需要在 AIX 中创建一个新的组,其中只包含允许使用 sudo 运行某些命令的用户。要想使用组,只需在条目前面加上前缀 ‘%’。假设您有 devops 和 devuat 组,其中包含以下用户:
# lsgroup -f -a users devops devops: users=joex,delta,charlie,tstgn # lsgroup -f -a users devuat devuat: users=zebra,spsys,charlie
允许 devops 组作为 dbdftst 运行 /usr/local/bin/data_ext.sh 命令。
允许 devuat 组作为 dbukuat 运行命令 /usr/local/bin/data_mvup.sh 和 /usr/local/bin/data_rep.sh。
可以设置以下 sudoers 条目:
%devops rs6000 = (dbdftst) NOPASSWD: /usr/local/bin/data_ext.sh %devuat rs6000 = (dbukuat) /usr/local/bin/data_mvup.sh %devuat rs6000 = (dbukuat) /usr/local/bin/data_rep.sh
注意,对于前面的条目,devops 组中的用户在执行 /usr/local/bin/data_ext.sh 时不会提示他们输入密码。但是,会提示 devuat 组中的用户输入密码。用户 “charlie” 是这两个组(devops 和 devuat)的成员,所以他可以执行以上所有命令。
对 sudo 使用超时
sudo 的一个特性使用时间票据判断自从最后一次运行 sudo 命令以来已经过了多长时间。在指定的时间段内,用户可以重新运行命令而不会提示输入密码(此用户自己的密码)。超过这段时间之后,用户必须再次输入密码才能重新运行命令。如果用户提供了正确的密码,就执行命令,票据复位,计时重新开始。如果在 sudoers 中此用户的条目中有 NOPASSWD,票据特性是无效的。默认的超时是 5 分钟。如果希望修改默认值,只需在 sudoers 中添加一个条目。例如,可以使用以下条目把用户 “bravo” 运行任何命令的超时值设置为 20 分钟:
Defaults:bravo timestamp_timeout=20
作为用户使用以下命令销毁票据:
$ sudo -k
销毁票据之后,在运行 sudo 命令时会再次提示用户输入密码。
请不要设置所有用户的超时值,因为这会造成问题,尤其是在运行花费时间比较长的作业集合时。在 timestamp_timeout 变量中使用 -1 值可以禁用这个特性。时间票据是 /var/run/sudo 中包含用户名的目录条目。
变量
正如前面提到的,sudo 会缩减系统变量,这可能有危险。可以使用 sudo -V 检查哪些变量保持不变,哪些变量会被缩减。输出显示保持不变和缩减的变量列表。缩减 LIBPATH 显然很不方便。有两个解决方法 —— 编写一个包装器脚本或在命令行上指定环境。先看一下包装器脚本解决方案,假设您的一个应用程序会停止或启动 DB2® 实例。可以编写一个脚本来保持变量不变。在 清单 1. rc.db2 中,使用以下代码访问实例配置文件,进而导出各个 LIBPATH 和 DB2 环境变量,这会保持环境变量不变:
. /home/$inst/sqllib/db2profile
sudoers 中执行此脚本的条目如下,它们不会缩减任何系统环境变量:
bravo rs6000 = (dbinst4) NOPASSWD: /home/dbinst4/sqllib/adm/db2start bravo rs6000 = (dbinst4) NOPASSWD: /home/dbinst4/sqllib/adm/db2stop force bravo rs6000 = (dbinst4) NOPASSWD: /usr/local/bin/rc.db2 stop db2inst4 bravo rs6000 = (dbinst4) NOPASSWD: /usr/local/bin/rc.db2 start db2inst4
在这个示例中,用户 “bravo” 可以作为用户 “dbinst4” 执行以上命令。通常,这个用户会运行:
sudo -u dbinst4 /usr/local/bin/rc.db2 stop db2inst4 sudo -u dbinst4 /usr/local/bin/rc.db2 start db2inst4
清单 1. rc.db2
#!/bin/sh # rc.db2 # stop/start db2 instances # check to see if db2 inst is runningdb2_running(){state=`ps -ef |grep db2sysc |grep -v grep| awk '$1=="'${inst}'" { print $1 }'` if [ "$state" = "" ] then return 1 else return 0 fi} usage () { echo "`basename $0` start | stop <instance>" } # stop db2 stop_db2 () { echo "stopping db2 instance as user $inst" if [ -f /home/$inst/sqllib/db2profile ]; then . /home/$inst/sqllib/db2profile else echo "Cannot source DB2..exiting" exit 1 fi /home/$inst/sqllib/adm/db2stop force } # start db2 start_db2 () { echo "starting db2 instance as user $inst" if [ -f /home/$inst/sqllib/db2profile ]; then . /home/$inst/sqllib/db2profile else echo "Cannot source DB2..exiting" exit 1 fi /home/$inst/sqllib/adm/db2start } # check we get 2 params if [ $# != 2 ] then usage exit 1 fi inst=$2 case "$1" in Start|start) if db2_running then echo "db2 instance $inst appears to be already running" exit 0 else echo " instance not running as user $inst..attempting to start it" start_db2 $inst fi ;; Stop|stop) if db2_running then echo "instance running as $inst..attempting to stop it" stop_db2 $inst else echo "db2 instance $inst appears to be not running anyway" exit 0 fi ;; *) usage ;; esac
保留系统环境变量的另一种方法是在 sudoers 中使用 Defaults !env_reset 指令:
Defaults !env_reset
然后,在命令行上指定环境变量名和值:
$ sudo LIBPATH=″/usr/lib:/opt/db2_09_05/lib64″ -u delta /usr/local/bin/datapmp
如果没有设置 !env_reset 条目,那么在试图运行命令时会从 sudo 收到以下错误:
sudo: sorry, you are not allowed to set the following environment variables: LIBPATH
如果发现 sudo 还会缩减其他环境变量,那么可以在 sudoers 中指定变量名(使用 Defaults env_keep += 指令),让 sudo 保持这些变量不变。例如,假设对于我的一个使用 sudo 的脚本,sudo 会缩减应用程序变量 DSTAGE_SUP 和 DSTAGE_META。为了保留这些变量,可以在 sudoers 中设置以下条目:
Defaults env_keep += "DSTAGE_SUP" Defaults env_keep += "DSTAGE_META"
注意,我指定了变量名,但是没有指定变量值。值已经包含在我的脚本中,比如:
export DSTAGE_SUP=/opt/dstage/dsengine; export DSTAGE_META=/opt/dstage/db2
现在,在执行这个脚本时,这两个环境变量会保持不变。
保护 sudo 路径
可以使用 secure_path 指令修改 sudoers 中默认的 PATH。这个指令指定当用户执行 sudo 命令时在什么地方寻找二进制代码和命令。这个选项的目的显然是要限制用户运行 sudo 命令的范围,这是一种好做法。在 sudoers 中使用以下指令,指定安全 PATH 及其搜索目录:
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/opt/freeware/bin:/usr/sbin"
实施限制
可以对用户能够运行的命令实施限制。假设有一个名为 dataex 的组,其成员是 “alpha”、“bravo” 和 “charlie”。现在,已经允许这个组运行 sudo 命令 /usr/local/bin/mis_ext *,这里的星号代表传递给脚本的许多参数。但是,如果参数是 import,就不允许用户 “charlie” 执行此脚本。可以使用逻辑否 '!' 操作符设置这种条件。下面是在 sudoers 中的实现方法:
%dataex rs6000 = (dbmis) NOPASSWD: /usr/local/bin/mis_ext * charlie rs6000 = (dbmis) NOPASSWD: !/usr/local/bin/mis_ext import
注意,逻辑否操作符条目出现在无限制的条目后面。可以在同一行上应用多个逻辑否条目;只需以逗号分隔它们,比如:
charlie rs6000 = (dbmis) NOPASSWD: /usr/local/bin/aut_pmp * charlie rs6000 = (dbmis) NOPASSWD: !/usr/local/bin/aut_pmp create, !/usr/local/bin/aut_pmp delete, !/usr/local/bin/aut_pmp amend
提交 sudo 命令
在企业环境中,把 sudo 命令提交给远程主机最好是作为 root 用户使用 ssh 脚本来完成,对于无密码登录,还应该在主机之间交换密钥。我们来看一个示例。对于地理上远程的机器,如果遇到某种硬件问题(磁盘或内存),IBM® 工程师会到现场更换发生故障的硬件。在这种情况下,他们需要有 root 密码才能完成工作。一种实现方法是让工程师必须使用 sudo 访问 root 用户。在工程师访问站点之前告诉他们密码是有好处的。清单 2 演示提交这种配置的一种方法。仔细看看 清单 2,这里使用一个 for 循环包含要提交配置的主机列表。(但是,一般情况下,把这些主机名放在一个文本文件中并使用 while 循环读取它们。)使用 ‘here’ 文档方法建立 sudoers 的备份,然后在 sudoers 中添加下面的条目:
# -- ibmeng sudo root ibmeng host1 = (root) NOPASSWD:ALL
接下来,创建用户 “ibmeng”,使用 chpasswd 设置此用户的密码。在这个示例中,密码是 ibmpw。然后在此用户的配置文件中添加一条消息,告诉用户如何使用 sudo 访问 root 用户。因此,在工程师登录时,他会看到以下消息:
IBM Engineer, to access root account type: sudo -u root su -
当然,在工程师离开之后,应该锁住 ibmeng 账户。
清单 2. dis_ibm
#!/bin/sh # dis_ibm dest_hosts='host1 host2 host3 host4' for host in $dest_hosts do echo "doing [$host]" $ssh -T -t -l root $host<<'mayday' host=`hostname` cp /etc/sudoers /etc/sudoers.bak if [ $? != 0 ] then echo "error: unable to cp sudoers file" exit 1 fi echo "# -- ibmeng sudo root\nibmeng $host = (root) NOPASSWD:ALL">>/etc/sudoers mkuser su=false ibmeng if [ $? = 0 ] then echo "ibmeng:ibmpw" | chpasswd -c else echo "error: unable to create user ibmeng and or passwd" exit 1 fi chuser gecos='IBM engineer acc' ibmeng if [ -f /home/ibmeng/.profile ] then echo "echo \"IBM Engineer, to access root account type: sudo -u root su -"\" >>/home/ibmeng/.profile fi mayday done