rootwrap模块解析以及功能扩展

转载自:

原文地址:https://blog.csdn.net/gaoxingnengjisuan/article/details/47102593

博客地址:http://blog.csdn.net/gaoxingnengjisuan

邮箱地址:dong.liu@siat.ac.cn


   使用rootwrap的目的就是针对系统某些特定的操作,让非特权用户以root用户的身份来安全地执行这些操作。据说nova曾经使用sudoers文件来列出允许执行的特权命令,使用sudo来运行这些命令,但是这样做不容易维护,而且不能进行复杂的参数处理(引自:http://blog.lightcloud.cn/?p=240)。早期版本我没有读过,而rootwrap的出现就是为了解决上述问题。


示例:

    对于文件要求root权限的文件/etc/iscsi/initiatorname.iscsi,如果我们在openstack系统中以非特权用户的身份来查看:

[plain] view plain copy
  1. cat /etc/iscsi/initiatorname.iscsi  
则会提示权限不足:Permission denied;而如果我们应用rootwrap模块对其进行命令行的封装:
[plain] view plain copy
  1. sudo nova-rootwrap /etc/nova/rootwrap.conf cat /etc/iscsi/initiatorname.iscsi  
就可以以非特权用户的身份在免输入密码的情况下顺利执行这条命令,得到想要的结果

[plain] view plain copy
  1. InitiatorName=iqn.1994-05.com.redhat:5536dfb81ec0  

    在这篇博客中,我们就以命令行

[plain] view plain copy
  1. sudo nova-rootwrap /etc/nova/rootwrap.conf cat /etc/iscsi/initiatorname.iscsi  
为例,来解析rootwrap模块具体的执行过程,以及功能扩展的方式。

注:至于配置文件/etc/nova/rootwrap.conf等的作用会在下面模块分析的过程中进行解析;


1.rootwrap模块解析

    rootwrap已经迁移到项目oslo中。我们以rootwrap在nova中的应用为例,在文件setup.cfg中可以看到nova-rootwrap的entrance为nova-rootwrap = oslo.rootwrap.cmd:main;

    首先来看方法:/oslo/rootwrap/cmd.py----def main:

[python] view plain copy
  1. def main():  
  2.     # Split arguments, require at least a command  
  3.     """ 
  4.     sudo nova-rootwrap /etc/nova/rootwrap.conf cat /etc/iscsi/initiatorname.iscsi 
  5.     sys.argv = [ 
  6.         '/usr/bin/nova-rootwrap',  
  7.         '/etc/nova/rootwrap.conf',  
  8.         'cat',  
  9.         '/etc/iscsi/initiatorname.iscsi'] 
  10.     """  
  11.     execname = sys.argv.pop(0)  
  12.     if len(sys.argv) < 2:  
  13.         _exit_error(execname, "No command specified", RC_NOCOMMAND, log=False)  
  14.   
  15.     configfile = sys.argv.pop(0)  
  16.     userargs = sys.argv[:]  
  17.     """ 
  18.     execname = /usr/bin/nova-rootwrap 
  19.     configfile = /etc/nova/rootwrap.conf 
  20.     userargs = ['cat', '/etc/iscsi/initiatorname.iscsi'] 
  21.     """  
  22.   
  23.     # Add ../ to sys.path to allow running from branch  
  24.     possible_topdir = os.path.normpath(os.path.join(os.path.abspath(execname),  
  25.                                                     os.pardir, os.pardir))  
  26.     """ 
  27.     possible_topdir = /usr 
  28.     """  
  29.       
  30.     if os.path.exists(os.path.join(possible_topdir, "oslo""__init__.py")):  
  31.         sys.path.insert(0, possible_topdir)  
  32.   
  33.     from oslo.rootwrap import wrapper  
  34.   
  35.     # Load configuration  
  36.     try:  
  37.         rawconfig = moves.configparser.RawConfigParser()  
  38.         rawconfig.read(configfile)  
  39.         config = wrapper.RootwrapConfig(rawconfig)  
  40.     except ValueError as exc:  
  41.         msg = "Incorrect value in %s: %s" % (configfile, exc.message)  
  42.         _exit_error(execname, msg, RC_BADCONFIG, log=False)  
  43.     except moves.configparser.Error:  
  44.         _exit_error(execname, "Incorrect configuration file: %s" % configfile,  
  45.                     RC_BADCONFIG, log=False)  
  46.   
  47.     if config.use_syslog:  
  48.         wrapper.setup_syslog(execname,  
  49.                              config.syslog_log_facility,  
  50.                              config.syslog_log_level)  
  51.   
  52.     # Execute command if it matches any of the loaded filters  
  53.     filters = wrapper.load_filters(config.filters_path)  
  54.     """ 
  55.     config.filters_path = ['/etc/nova/rootwrap.d', '/usr/share/nova/rootwrap'] 
  56.     filters =  
  57.         [ 
  58.          <oslo.rootwrap.filters.CommandFilter object at 0x7f10f266ecd0>,  
  59.          ...... 
  60.          <oslo.rootwrap.filters.KillFilter object at 0x7f10f266efd0>,  
  61.          ...... 
  62.          <oslo.rootwrap.filters.EnvFilter object at 0x1744210>,  
  63.          ...... 
  64.          <oslo.rootwrap.filters.ReadFileFilter object at 0x1744ed0>,  
  65.          ...... 
  66.          <oslo.rootwrap.filters.RegExpFilter object at 0x174a1d0>,  
  67.          ...... 
  68.         ] 
  69.     注:遍历并加载/usr/share/nova/rootwrap路径下所有过滤文件中的命令行过滤对象; 
  70.     """  
  71.       
  72.     try:  
  73.         filtermatch = wrapper.match_filter(filters, userargs,  
  74.                                            exec_dirs=config.exec_dirs)  
  75.         """ 
  76.         userargs = ['cat', '/etc/iscsi/initiatorname.iscsi'] 
  77.         exec_dirs = ['/sbin', '/usr/sbin', '/bin', '/usr/bin'] 
  78.         注:exec_dirs为读取配置文件configfile = /etc/nova/rootwrap.conf获取; 
  79.         filtermatch = <oslo.rootwrap.filters.ReadFileFilter object at 0x2195e90> 
  80.         """  
  81.           
  82.         if filtermatch:  
  83.             command = filtermatch.get_command(userargs,  
  84.                                               exec_dirs=config.exec_dirs)  
  85.             """ 
  86.             userargs = ['cat', '/etc/iscsi/initiatorname.iscsi'] 
  87.             exec_dirs = ['/sbin', '/usr/sbin', '/bin', '/usr/bin'] 
  88.             command = ['sudo', '-u', 'shinian' ,'/bin/cat', '/etc/iscsi/initiatorname.iscsi'] 
  89.             """  
  90.               
  91.             if config.use_syslog:  
  92.                 logging.info("(%s > %s) Executing %s (filter match = %s)" % (  
  93.                     _getlogin(), pwd.getpwuid(os.getuid())[0],  
  94.                     command, filtermatch.name))  
  95.   
  96.             obj = subprocess.Popen(command,  
  97.                                    stdin=sys.stdin,  
  98.                                    stdout=sys.stdout,  
  99.                                    stderr=sys.stderr,  
  100.                                    preexec_fn=_subprocess_setup,  
  101.                                    env=filtermatch.get_environment(userargs))  
  102.             obj.wait()  
  103.             sys.exit(obj.returncode)  
  104.   
  105.     except wrapper.FilterMatchNotExecutable as exc:  
  106.         msg = ("Executable not found: %s (filter match = %s)"  
  107.                % (exc.match.exec_path, exc.match.name))  
  108.         _exit_error(execname, msg, RC_NOEXECFOUND, log=config.use_syslog)  
  109.   
  110.     except wrapper.NoFilterMatched:  
  111.         msg = ("Unauthorized command: %s (no filter matched)"  
  112.                % ' '.join(userargs))  
  113.         _exit_error(execname, msg, RC_UNAUTHORIZED, log=config.use_syslog)  

    总体来说方法完成了六个步骤的工作:


1.1 命令行解析

[python] view plain copy
  1.     """ 
  2.     sudo nova-rootwrap /etc/nova/rootwrap.conf cat /etc/iscsi/initiatorname.iscsi 
  3.     sys.argv = [ 
  4.         '/usr/bin/nova-rootwrap',  
  5.         '/etc/nova/rootwrap.conf',  
  6.         'cat',  
  7.         '/etc/iscsi/initiatorname.iscsi'] 
  8.     """  
  9.     execname = sys.argv.pop(0)  
  10.     configfile = sys.argv.pop(0)  
  11.     userargs = sys.argv[:]  
  12.     """ 
  13.     execname = /usr/bin/nova-rootwrap 
  14.     configfile = /etc/nova/rootwrap.conf 
  15.     userargs = ['cat', '/etc/iscsi/initiatorname.iscsi'] 
  16.     """  
    可以看到execname = /usr/bin/nova-rootwrap指定了一个脚本,查看其内容后,发现其所实现的功能是退出当前所运行的main方法,从后面所应用execname的代码也可以看出,其都应用在程序异常退出的场景中。

/usr/bin/nova-rootwrap脚本内容:

[python] view plain copy
  1. #!/usr/bin/python  
  2. # PBR Generated from u'console_scripts'  
  3.   
  4. import sys  
  5. from oslo.rootwrap.cmd import main  
  6.   
  7. if __name__ == "__main__":  
  8.     sys.exit(main())  
再来看配置文件configfile = /etc/nova/rootwrap.conf,来看其内容:

[python] view plain copy
  1. # Configuration for nova-rootwrap  
  2. # This file should be owned by (and only-writeable by) the root user  
  3.   
  4. [DEFAULT]  
  5. # List of directories to load filter definitions from (separated by ',').  
  6. # These directories MUST all be only writeable by root !  
  7. filters_path=/etc/nova/rootwrap.d,/usr/share/nova/rootwrap  
  8.   
  9. # List of directories to search executables in, in case filters do not  
  10. # explicitely specify a full path (separated by ',')  
  11. # If not specified, defaults to system PATH environment variable.  
  12. # These directories MUST all be only writeable by root !  
  13. exec_dirs=/sbin,/usr/sbin,/bin,/usr/bin  
  14.   
  15. # Enable logging to syslog  
  16. # Default value is False  
  17. use_syslog=False  
  18.   
  19. # Which syslog facility to use.  
  20. # Valid values include auth, authpriv, syslog, user0, user1...  
  21. # Default value is 'syslog'  
  22. syslog_log_facility=syslog  
  23.   
  24. # Which messages to log.  
  25. # INFO means log all usage  
  26. # ERROR means only log unsuccessful attempts  
  27. syslog_log_level=ERROR  
在这个配置文件中,主要指定了两方面的内容:

A.filters_path=/etc/nova/rootwrap.d,/usr/share/nova/rootwrap

    指定了若干过滤器文件所在的目录,这些过滤器文件有:api-metadata.filters  baremetal-compute-ipmi.filters  baremetal-deploy-helper.filters  compute.filters  network.filters;以compute.filters为例来看看其内容:

[python] view plain copy
  1. # nova-rootwrap command filters for compute nodes  
  2. # This file should be owned by (and only-writeable by) the root user  
  3.   
  4. [Filters]  
  5. # nova/virt/disk/mount/api.py: 'kpartx', '-a', device  
  6. # nova/virt/disk/mount/api.py: 'kpartx', '-d', device  
  7. kpartx: CommandFilter, kpartx, root  
  8.   
  9. # nova/virt/xenapi/vm_utils.py: tune2fs, -O ^has_journal, part_path  
  10. # nova/virt/xenapi/vm_utils.py: tune2fs, -j, partition_path  
  11. tune2fs: CommandFilter, tune2fs, root  
  12.   
  13. # nova/virt/disk/mount/api.py: 'mount', mapped_device  
  14. # nova/virt/disk/api.py: 'mount', '-o', 'bind', src, target  
  15. # nova/virt/xenapi/vm_utils.py: 'mount', '-t', 'ext2,ext3,ext4,reiserfs'..  
  16. # nova/virt/configdrive.py: 'mount', device, mountdir  
  17. # nova/virt/libvirt/volume.py: 'mount', '-t', 'sofs' ...  
  18. mount: CommandFilter, mount, root  
  19.   
  20. # nova/virt/disk/mount/api.py: 'umount', mapped_device  
  21. # nova/virt/disk/api.py: 'umount' target  
  22. # nova/virt/xenapi/vm_utils.py: 'umount', dev_path  
  23. # nova/virt/configdrive.py: 'umount', mountdir  
  24. umount: CommandFilter, umount, root  
  25.   
  26. # nova/virt/libvirt/utils.py: 'blockdev', '--getsize64', path  
  27. # nova/virt/disk/mount/nbd.py: 'blockdev', '--flushbufs', device  
  28. blockdev: RegExpFilter, blockdev, root, blockdev, (--getsize64|--flushbufs), /dev/.*  
  29.   
  30. # nova/virt/disk/vfs/localfs.py: 'tee', canonpath  
  31. tee: CommandFilter, tee, root  
  32.   
  33. # nova/virt/xenapi/vm_utils.py: resize2fs, partition_path  
  34. # nova/virt/disk/api.py: resize2fs, image  
  35. resize2fs: CommandFilter, resize2fs, root  
  36.   
  37. # nova/network/linux_net.py: 'kill', '-9', pid  
  38. # nova/network/linux_net.py: 'kill', '-HUP', pid  
  39. kill_dnsmasq: KillFilter, root, /usr/sbin/dnsmasq, -9, -HUP  
  40.   
  41. # nova/network/linux_net.py: 'kill', pid  
  42. kill_radvd: KillFilter, root, /usr/sbin/radvd  
  43.   
  44. # nova/network/linux_net.py: dnsmasq call  
  45. dnsmasq: EnvFilter, env, root, CONFIG_FILE=, NETWORK_ID=, dnsmasq  
  46.   
  47. # nova/virt/libvirt/connection.py:  
  48. read_initiator: ReadFileFilter, /etc/iscsi/initiatorname.iscsi  
  49.   
  50. # nova/utils.py:read_file_as_root: 'cat', file_path  
  51. # (called from nova/virt/disk/vfs/localfs.py:VFSLocalFS.read_file)  
  52. read_passwd: RegExpFilter, cat, root, cat, (/var|/usr)?/tmp/openstack-vfs-localfs[^/]+/etc/passwd  
  53. read_shadow: RegExpFilter, cat, root, cat, (/var|/usr)?/tmp/openstack-vfs-localfs[^/]+/etc/shadow  
  54. ......  

    可见在这些过滤器文件中,定义了很多具体命令的相关信息,如命令的应用场景,命令封装所调用的过滤器,命令的执行权限和命令的执行参数等等;我们初步就可以想到,这些过滤器文件中定义描述的所有命令,都是能够以非特权用户的身份在免输入密码的情况下执行的,我们用rootwrap模块来封装的命令行,首先应该到这些文件中查询是否有相匹配的命令,具体实现后面接着解析。


1.2 读取解析配置文件

[python] view plain copy
  1.     try:  
  2.         rawconfig = moves.configparser.RawConfigParser()  
  3.         rawconfig.read(configfile)  
  4.         config = wrapper.RootwrapConfig(rawconfig)  
  5.     except ValueError as exc:  
  6.         msg = "Incorrect value in %s: %s" % (configfile, exc.message)  
  7.         _exit_error(execname, msg, RC_BADCONFIG, log=False)  
  8.     except moves.configparser.Error:  
  9.         _exit_error(execname, "Incorrect configuration file: %s" % configfile,  
  10.                     RC_BADCONFIG, log=False)  

    这段代码很明白,就是对配置文件configfile = /etc/nova/rootwrap.conf进行读取解析;


1.3 加载过滤器文件中定义的所有命令行过滤对象

[python] view plain copy
  1.     filters = wrapper.load_filters(config.filters_path)  
  2.     """ 
  3.     config.filters_path = ['/etc/nova/rootwrap.d', '/usr/share/nova/rootwrap'] 
  4.     filters =  
  5.         [ 
  6.          <oslo.rootwrap.filters.CommandFilter object at 0x7f10f266ecd0>,  
  7.          ...... 
  8.          <oslo.rootwrap.filters.KillFilter object at 0x7f10f266efd0>,  
  9.          ...... 
  10.          <oslo.rootwrap.filters.EnvFilter object at 0x1744210>,  
  11.          ...... 
  12.          <oslo.rootwrap.filters.ReadFileFilter object at 0x1744ed0>,  
  13.          ...... 
  14.          <oslo.rootwrap.filters.RegExpFilter object at 0x174a1d0>,  
  15.          ...... 
  16.         ] 
  17.     注:遍历并加载/usr/share/nova/rootwrap路径下所有过滤文件中的命令行过滤对象; 
  18.     """  
    这段代码的作用就是遍历并加载/usr/share/nova/rootwrap路径下所有过滤文件(api-metadata.filters  baremetal-compute-ipmi.filters  baremetal-deploy-helper.filters  compute.filters  network.filters;以compute.filters)中的命令行过滤对象(系统安装后/etc/nova/rootwrap.d是不存在的);

    在1.1中和这里我们都可以看到用于命令行封装过滤的过滤器有很多,实际上共有CommandFilter、RegExpFilter、PathFilter、KillFilter、ReadFileFilter、IpFilter、EnvFilter、ChainingFilter和IpNetnsExecFilter共9种;这些过滤器类都以CommandFilter为父类,它们的具体区别主要体现在其方法match上,所以这9种过滤器主要是根据不同命令应用不同的匹配方式而区分实现的。


1.4 匹配到具体的命令行过滤对象

[python] view plain copy
  1.         filtermatch = wrapper.match_filter(filters, userargs,  
  2.                                            exec_dirs=config.exec_dirs)  
  3.         """ 
  4.         userargs = ['cat', '/etc/iscsi/initiatorname.iscsi'] 
  5.         exec_dirs = ['/sbin', '/usr/sbin', '/bin', '/usr/bin'] 
  6.         注:exec_dirs为读取配置文件configfile = /etc/nova/rootwrap.conf获取; 
  7.         filtermatch = <oslo.rootwrap.filters.ReadFileFilter object at 0x2195e90> 
  8.         """  

    这里的功能实现就是通过具体的命令行信息,调用不同的过滤器类方法,从定义描述的所有的命令行过滤对象中进行过滤匹配操作,看能否找到匹配的结果,如果找到匹配结果,就说明这个命令行是可以通过rootwrap模块封装来实现以非特权身份执行的;否则就说明openstack系统中指定此命令行只能以root身份执行。当然我们是可以通过进行功能扩展来实现我们的需求的。


1.5 通过rootwrap的封装获取具体的命令行实现

[python] view plain copy
  1.             command = filtermatch.get_command(userargs,  
  2.                                               exec_dirs=config.exec_dirs)  
  3.             """ 
  4.             userargs = ['cat', '/etc/iscsi/initiatorname.iscsi'] 
  5.             exec_dirs = ['/sbin', '/usr/sbin', '/bin', '/usr/bin'] 
  6.             command = ['sudo', '-u', 'shinian' ,'/bin/cat', '/etc/iscsi/initiatorname.iscsi'] 
  7.             """  

    这段代码所实现的功能就是获取具体的命令行实现,实际上最后的命令行实现不过就是应用了我们所熟悉的sudo命令赋予普通用户的root权限,最后的命令行实际上就是:

[plain] view plain copy
  1. sudo -u XXXX /bin/cat /etc/iscsi/initiatorname.iscsi  
    来看代码,这里调用了前面所述过滤器类中的get_command方法,有的过滤器类重写了get_command方法,有的过滤器类没有重写,直接调用父类CommandFilter中的get_command方法,但是区别并不大,不过就是通过对命令行参数的加加减减,整合成最后的命令行实现并返回;

    这里调用的是过滤器ReadFileFilter,它直接调用了其父类CommandFilter的get_command方法,我们来简单看一下:

[python] view plain copy
  1. def get_command(self, userargs, exec_dirs=[]):  
  2.     """ 
  3.     Returns command to execute (with sudo -u if run_as != root). 
  4.     """  
  5.     """ 
  6.     userargs = ['cat', '/etc/iscsi/initiatorname.iscsi'] 
  7.     exec_dirs = ['/sbin', '/usr/sbin', '/bin', '/usr/bin'] 
  8.     """  
  9.       
  10.     to_exec = self.get_exec(exec_dirs=exec_dirs) or self.exec_path  
  11.     """ 
  12.     to_exec = /bin/cat 
  13.     """  
  14.       
  15.     if (self.run_as != 'root'):  
  16.         # Used to run commands at lesser privileges  
  17.         return ['sudo''-u'self.run_as, to_exec] + userargs[1:]  
  18.     return [to_exec] + userargs[1:]  

    这个方法很好理解,这里不再赘述;


1.6 派生一个子进程来执行命令行

[python] view plain copy
  1.             obj = subprocess.Popen(command,  
  2.                                    stdin=sys.stdin,  
  3.                                    stdout=sys.stdout,  
  4.                                    stderr=sys.stderr,  
  5.                                    preexec_fn=_subprocess_setup,  
  6.                                    env=filtermatch.get_environment(userargs))  
  7.             obj.wait()  
  8.             sys.exit(obj.returncode)  

    这段代码所实现的功能就是派生一个新的子进程来执行rootwrap封装后的命令行,并等待其运行结束,当获取到命令行执行的返回值后退出这个子进程。


所以rootwarp实现的具体步骤就是:

    1 命令行解析
    2 读取解析配置文件
    3 加载过滤器文件中定义的所有命令行过滤对象
    4 匹配到具体的命令行过滤对象
    5 通过rootwrap的封装获取具体的命令行实现
    6 派生一个子进程来执行命令行
    实际上就是把命令行:
    sudo nova-rootwrap /etc/nova/rootwrap.conf cat /etc/iscsi/initiatorname.iscsi
经过若干参数处理和匹配操作转化为:
    sudo -u XXXX /bin/cat /etc/iscsi/initiatorname.iscsi
之后,启动一个子进程来实现这个命令行的执行操作;


2.rootwrap模块的功能扩展

    如果我们需要针对特定的命令操作,需要实现以非特权身份来执行root用户才能执行的命令,在这个模块中就可以进行自己的功能扩展,当然前提是要充分理解不同的过滤器类的具体区别。功能扩展有两种:

    1.直接在/usr/share/nova/rootwrap目录下的过滤器文件中,按照规则添加自己的命令行定义描述即可;

    2.如果当前的过滤器类实现无法满足命令行匹配的要求,则需要在/oslo/rootwrap/fifter.py中定义新的过滤器类,继承自类CommandFilter,并根据具体需求重写match和get_command等方法;然后再在/usr/share/nova/rootwrap目录下的过滤器文件中,按照规则添加自己的命令行定义描述即可;


阅读更多
想对作者说点什么?
相关热词

博主推荐

换一批

没有更多推荐了,返回首页