首页 > 代码库 > nova分析(10)—— nova-rootwrap

nova分析(10)—— nova-rootwrap

一.nova-rootwrap的作用

部署玩过openstack的都应该知道,它会生成一个nova用户来管理所有服务.nova身份在linux中属于普通用户级别,避免了一些需要root身份运行的操作,提高linux系统的安全性.
但是openstack在实际过程中会调用很多外部命令,例如就network服务而言就有:`ip`,`ovs-vsctl`,`iptables`,`dnsmasq`,`brctl`等等,这些命令在linux中都是需要以root身份来运行的,如果是普通用户通常的做法是在命令前加`sudo`切换到root身份再执行这些命令,但是这个方法在执行命令的时候需要输入密码确认操作,为了避免输入密码,我们需要配置一下sudo.
建议的方法是在/etc/sudoers.d/目录下新建一个文件,例如:

[root@localhost sudoers.d]# pwd
/etc/sudoers.d
[root@localhost sudoers.d]# echo ‘zhengtianbao ALL = (root) NOPASSWD: ALL‘ > stack_sh
[root@localhost sudoers.d]# cat stack_sh
zhengtianbao ALL = (root) NOPASSWD: ALL

这样当我们切换到’zhengtianbao’这个用户的时候,只要在想执行的命令前加’sudo’,不需要输入密码就能以root身份运行.

[root@localhost sudoers.d]# su zhengtianbao
[zhengtianbao@localhost sudoers.d]$ ls
ls: cannot open directory .: Permission denied
[zhengtianbao@localhost sudoers.d]$ sudo ls
stack_sh

关于sudoers的配置文件如何定义,这里简单介绍下:
通用格式:
user host run_as command
user:一位或几位用户,在/etc/group中可以用一个%代替它,组对象的名称一定要用百分号%开头.
host:一个或几个主机名.
run_as:作为哪个用户运行,常见选项是root和ALL.
command:想让用户或组运行的一个或几个根级别命令.
例如:
hans ALL=(root) useradd,userdel
授权hans用户在所有计算机上以root身份运行useradd,userdel命令.
%smith ALL=(ALL) NOPASSWD:useradd,userdel
授权smith组全部成员在所有计算机上以所有用户的身份运行useradd,userdel命令;且运行时不必输入密码.
一点点疑问:能否控制命令的参数呢?接下来做个测试:

[root@localhost sudoers.d]# echo ‘zhengtianbao ALL = (root) NOPASSWD: /bin/ls -l, /bin/ls -a‘ > stack_sh
[root@localhost sudoers.d]# su zhengtianbao
[zhengtianbao@localhost sudoers.d]$ ls -a
ls: cannot open directory .: Permission denied
[zhengtianbao@localhost sudoers.d]$ sudo ls -a
.  ..  stack_sh
[zhengtianbao@localhost sudoers.d]$ sudo ls -l
total 8
-rw-r--r-- 1 root root  59 Jan 26 14:43 stack_sh
[zhengtianbao@localhost sudoers.d]$ sudo ls -a -l
[sudo] password for zhengtianbao:

可见能够控制的命令参数还是很严格的.
放在openstack中这也是可行的,但是随着项目的增大,单纯的修改sudoers影响了openstack的可维护性,因此引入了root warpper来管理命令权限相关的内容.

二.nova-rootwrap工作原理

如果是根据devstack来安装openstack的话,查看devstack中的stack.sh里面有关于root权限的内容:

# root Access
# -----------
 
# OpenStack is designed to be run as a non-root user; Horizon will fail to run
# as **root** since Apache will not serve content from **root** user).
# ``stack.sh`` must not be run as **root**.  It aborts and suggests one course of
# action to create a suitable user account.
 
if [[ $EUID -eq 0 ]]; then
    echo "You are running this script as root."
    echo "Cut it out."
    echo "Really."
    echo "If you need an account to run DevStack, do this (as root, heh) to create $STACK_USER:"
    echo "$TOP_DIR/tools/create-stack-user.sh"
    exit 1
fi
 
# We‘re not **root**, make sure ``sudo`` is available
is_package_installed sudo || install_package sudo

好,它这里表示需要先通过脚本tools/create-stack-user.sh来创建一个用户,再通过那个用户来执行,看脚本内容:

# Needed to get ``ENABLED_SERVICES``
source $TOP_DIR/stackrc
 
# Give the non-root user the ability to run as **root** via ``sudo``
is_package_installed sudo || install_package sudo
     
if ! getent group $STACK_USER >/dev/null; then
    echo "Creating a group called $STACK_USER"
    groupadd $STACK_USER
fi 
     
if ! getent passwd $STACK_USER >/dev/null; then
    echo "Creating a user called $STACK_USER"
    useradd -g $STACK_USER -s /bin/bash -d $DEST -m $STACK_USER
fi
 
echo "Giving stack user passwordless sudo privileges"
# UEC images ``/etc/sudoers`` does not have a ``#includedir``, add one
grep -q "^#includedir.*/etc/sudoers.d" /etc/sudoers ||
    echo "#includedir /etc/sudoers.d" >> /etc/sudoers
( umask 226 && echo "$STACK_USER ALL=(ALL) NOPASSWD:ALL" \
    > /etc/sudoers.d/50_stack_sh )

$STACK_USER的值在stackrc文件中定义,当前环境是root身份时则为’stack’:

# Determine stack user
if [[ $EUID -eq 0 ]]; then
    STACK_USER=stack
else
    STACK_USER=$(whoami)
fi

ok,这里发现它在/etc/sudoers.d/目录下生成了一个50_stack_sh的文件,里面的内容是:
stack ALL=(ALL) NOPASSWD:ALL
显然它创建的stack用户现在可以在使用`sudo`执行任何命令都能省略输入密码的过程了.
接下来继续看nova的安装过程,在devstack/lib/目录下的nova脚本中有configure_nova()的方法,它会在stack.sh中被调用到,正如名字所示,它用来设置nova的config文件,创建一些数据等工作:

# configure_nova() - Set config files, create data dirs, etc
function configure_nova() {
    # Put config files in ``/etc/nova`` for everyone to find
    if [[ ! -d $NOVA_CONF_DIR ]]; then
        sudo mkdir -p $NOVA_CONF_DIR
    fi
    sudo chown $STACK_USER $NOVA_CONF_DIR
 
    cp -p $NOVA_DIR/etc/nova/policy.json $NOVA_CONF_DIR
 
    configure_nova_rootwrap
    ...

注意里面的configure_nova_rootwrap,查看该方法:

# configure_nova_rootwrap() - configure Nova‘s rootwrap
function configure_nova_rootwrap() {
    # Deploy new rootwrap filters files (owned by root).
    # Wipe any existing rootwrap.d files first
    if [[ -d $NOVA_CONF_DIR/rootwrap.d ]]; then
        sudo rm -rf $NOVA_CONF_DIR/rootwrap.d
    fi
    # Deploy filters to /etc/nova/rootwrap.d
    sudo mkdir -m 755 $NOVA_CONF_DIR/rootwrap.d
    sudo cp $NOVA_DIR/etc/nova/rootwrap.d/*.filters $NOVA_CONF_DIR/rootwrap.d
    sudo chown -R root:root $NOVA_CONF_DIR/rootwrap.d
    sudo chmod 644 $NOVA_CONF_DIR/rootwrap.d/*
    # Set up rootwrap.conf, pointing to /etc/nova/rootwrap.d
    sudo cp $NOVA_DIR/etc/nova/rootwrap.conf $NOVA_CONF_DIR/
    sudo sed -e "s:^filters_path=.*$:filters_path=$NOVA_CONF_DIR/rootwrap.d:" -i $NOVA_CONF_DIR/rootwrap.conf
    sudo chown root:root $NOVA_CONF_DIR/rootwrap.conf
    sudo chmod 0644 $NOVA_CONF_DIR/rootwrap.conf
    # Specify rootwrap.conf as first parameter to nova-rootwrap
    ROOTWRAP_SUDOER_CMD="$NOVA_ROOTWRAP $NOVA_CONF_DIR/rootwrap.conf *"
 
    # Set up the rootwrap sudoers for nova
    TEMPFILE=`mktemp`
    echo "$STACK_USER ALL=(root) NOPASSWD: $ROOTWRAP_SUDOER_CMD" >$TEMPFILE
    chmod 0440 $TEMPFILE
    sudo chown root:root $TEMPFILE
    sudo mv $TEMPFILE /etc/sudoers.d/nova-rootwrap
}

显然,它在/etc/sudoers.d/目录下创建了nova-rootwrap的文件,里面的内容可能是:
nova ALL = (root) NOPASSWD: /usr/bin/nova-rootwrap /etc/nova/rootwrap.conf *
nova:指用户名.
ALL:指主机名.
root:指运行用户名.
NOPASSWD:指运行下面命令时不需要输入密码.
/usr/bin/nova-rootwrap /etc/nova/rootwrap.conf *:指能够运行的命令.
上面的文件定义就是说:
以nova身份运行命令`sudo /usr/bin/nova-rootwrap /etc/nova/rootwrap.conf * `
时是不需要输入密码的,其中的’*’指的是任意字符串,例如:`ip route show …`.
/usr/bin/nova-rootwrap是一个可执行的脚本文件,/etc/nova/rootwrap.conf则是rootwrap相关的配置,里面定义了filters-path所在路径,以及缺省的可执行命令所在路径,具体的过滤逻辑如下:
1)获取要执行的命令,如:ip
2)通过filters-path加载配置文件中定义的可以执行的命令列表
3)判断命令是否在可执行命令列表中
4)若在则通过python的subprocess模块执行Popen方法;不在则给出错误信息,退出.
这些都可以在nova的bin/nova-rootwrap文件中查看.

三.nova中执行外部命令过程分析

所有的nova代码在执行外部命令的时候都会用到execute函数,这个函数定义在nova顶层目录下的utils.py模块下.
例如:
from nova import utils
utils.execute(‘chmod’, ’777′, tmpdir, run_as_root=True)
execute函数首先根据run_as_root参数进行了一些处理,如下所示:

def _get_root_helper():
    return ‘sudo nova-rootwrap %s‘ % CONF.rootwrap_config
 
 
def execute(*cmd, **kwargs):
    """Convenience wrapper around oslo‘s execute() method."""
    if ‘run_as_root‘ in kwargs and not ‘root_helper‘ in kwargs:
        kwargs[‘root_helper‘] = _get_root_helper()
    return processutils.execute(*cmd, **kwargs)

然后丢给processutils中的execute,查看代码:

def execute(*cmd, **kwargs):
    ...
    if run_as_root and hasattr(os, ‘geteuid‘) and os.geteuid() != 0:
        if not root_helper:
            raise NoRootWrapSpecified(
                message=_(‘Command requested root, but did not ‘
                          ‘specify a root helper.‘))
        cmd = shlex.split(root_helper) + list(cmd)
 
    cmd = map(str, cmd)
 
    while attempts > 0:
        ...
        obj = subprocess.Popen(cmd,
                               stdin=_PIPE,
                               stdout=_PIPE,
                               stderr=_PIPE,
                               close_fds=close_fds,
                               preexec_fn=preexec_fn,
                               shell=shell)
        ...

最终执行的命令cmd就是`sudo /usr/bin/nova-rootwrap /etc/nova/rootwrap.conf chmod 777 tmpdir`
这里`chmod`这个命令在rootwrap.d目录下的filters文件中可以找到对应的配置:
chmod: CommandFilter, chmod, root
CommandFilter的定义是在oslo的rootwrap/filters.py中,里面还定义了其他的filter(RegExp,Path,Kill,ReadFile,Ip,Env,Chaining,IpNetnsExec).
更加详细的内容请查看nova/rootwarp目录下的filters.py与wrapper.py.

本文转自http://zhengtianbao.com/?p=224