[PATCH V3 0/3] check and set search permission for a directory

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> V2 -> V3 read the quem process pid from pid file instead of from iterating the process list. V1 -> V2 resend: qemu user tests rebase: add a method to fix search permissions ShaoHe Feng (3): move RollbackContext from tests/utils to src/kimchi/utils qemu user tests: probe the username of qemu process started by libvirt add a method to fix search permissions src/kimchi/kvmusertests.py | 66 ++++++++++++++++++ src/kimchi/utils.py | 162 ++++++++++++++++++++++++++++++++++++++++++++- tests/utils.py | 47 +------------ 3 files changed, 229 insertions(+), 46 deletions(-) create mode 100644 src/kimchi/kvmusertests.py -- 1.8.4.2

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Then kimchi source code can make use of it Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Zhou Zheng Sheng <zhshzhou@linux.vnet.ibm.com> --- src/kimchi/utils.py | 46 ++++++++++++++++++++++++++++++++++++++++++++++ tests/utils.py | 47 ++--------------------------------------------- 2 files changed, 48 insertions(+), 45 deletions(-) diff --git a/src/kimchi/utils.py b/src/kimchi/utils.py index f7eda93..bf2a760 100644 --- a/src/kimchi/utils.py +++ b/src/kimchi/utils.py @@ -84,3 +84,49 @@ def import_class(class_path): def import_module(module_name): return __import__(module_name, fromlist=['']) + + +class RollbackContext(object): + ''' + A context manager for recording and playing rollback. + The first exception will be remembered and re-raised after rollback + + Sample usage: + with RollbackContext() as rollback: + step1() + rollback.prependDefer(lambda: undo step1) + def undoStep2(arg): pass + step2() + rollback.prependDefer(undoStep2, arg) + ''' + def __init__(self, *args): + self._finally = [] + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + firstException = exc_value + + for undo, args, kwargs in self._finally: + try: + undo(*args, **kwargs) + except Exception as e: + # keep the earliest exception info + if not firstException: + firstException = e + # keep the original traceback info + traceback = sys.exc_info()[2] + + # re-raise the earliest exception + if firstException is not None: + if type(firstException) is str: + sys.stderr.write(firstException) + else: + raise firstException, None, traceback + + def defer(self, func, *args, **kwargs): + self._finally.append((func, args, kwargs)) + + def prependDefer(self, func, *args, **kwargs): + self._finally.insert(0, (func, args, kwargs)) diff --git a/tests/utils.py b/tests/utils.py index c114813..e8acb2b 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -32,6 +32,8 @@ from contextlib import closing import unittest import base64 +from kimchi.utils import RollbackContext + import kimchi.server import kimchi.model @@ -137,51 +139,6 @@ def https_request(host, port, path, data=None, method='GET', headers=None): return _request(conn, path, data, method, headers) -class RollbackContext(object): - ''' - A context manager for recording and playing rollback. - The first exception will be remembered and re-raised after rollback - - Sample usage: - with RollbackContext() as rollback: - step1() - rollback.prependDefer(lambda: undo step1) - def undoStep2(arg): pass - step2() - rollback.prependDefer(undoStep2, arg) - ''' - def __init__(self, *args): - self._finally = [] - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc_value, traceback): - firstException = exc_value - - for undo, args, kwargs in self._finally: - try: - undo(*args, **kwargs) - except Exception as e: - # keep the earliest exception info - if not firstException: - firstException = e - # keep the original traceback info - traceback = sys.exc_info()[2] - - # re-raise the earliest exception - if firstException is not None: - if type(firstException) is str: - sys.stderr.write(firstException) - else: - raise firstException, None, traceback - - def defer(self, func, *args, **kwargs): - self._finally.append((func, args, kwargs)) - - def prependDefer(self, func, *args, **kwargs): - self._finally.insert(0, (func, args, kwargs)) - def patch_auth(): """ Override the authenticate function with a simple test against an -- 1.8.4.2

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> We want to fix the searchable permission for qemu user. But we should find the username of qemu process firstly. searchable permission is a known problem. We have discussed several times on IRC. Royce reports the qemu username is different on different distros Adam, Aline, Royce and I think we can probe qemu username with the similar method of qemu iso stream support. Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/kimchi/kvmusertests.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/kimchi/kvmusertests.py diff --git a/src/kimchi/kvmusertests.py b/src/kimchi/kvmusertests.py new file mode 100644 index 0000000..6552dc1 --- /dev/null +++ b/src/kimchi/kvmusertests.py @@ -0,0 +1,63 @@ +# +# Project Kimchi +# +# Copyright IBM, Corp. 2013 +# +# Authors: +# ShaoHe Feng <shaohef@linux.vnet.ibm.com> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +import libvirt +import uuid +import psutil + +SIMPLE_VM_XML = """ +<domain type='kvm'> + <name>VM-test</name> + <uuid>%s</uuid> + <memory unit='KiB'>32768</memory> + <os> + <type arch='x86_64' machine='pc-0.15'>hvm</type> + <boot dev='hd'/> + </os> +</domain>""" + + +class UserTests(object): + def __init__(self): + self.vm_uuid = uuid.uuid3(uuid.NAMESPACE_DNS, 'vm-test.kimchi.org') + + def probe_user(self): + xml = SIMPLE_VM_XML % self.vm_uuid + user = 'qemu' + try: + conn = libvirt.open('qemu:///system') + dom = conn.defineXML(xml) + dom.create() + for p in psutil.process_iter(): + if self.vm_uuid in p.cmdline: + user = p.username + break + dom.destroy() + dom.undefine() + conn.close() + return user + except libvirt.libvirtError: + return None + +if __name__ == '__main__': + ut = UserTests() + print ut.probe_user() -- 1.8.4.2

Oh, the V2 and V3 are in same patch set. please ignore this patch set. I will resend again. On 12/24/2013 03:43 PM, shaohef@linux.vnet.ibm.com wrote:
From: ShaoHe Feng <shaohef@linux.vnet.ibm.com>
We want to fix the searchable permission for qemu user. But we should find the username of qemu process firstly.
searchable permission is a known problem. We have discussed several times on IRC.
Royce reports the qemu username is different on different distros
Adam, Aline, Royce and I think we can probe qemu username with the similar method of qemu iso stream support.
Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/kimchi/kvmusertests.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/kimchi/kvmusertests.py
diff --git a/src/kimchi/kvmusertests.py b/src/kimchi/kvmusertests.py new file mode 100644 index 0000000..6552dc1 --- /dev/null +++ b/src/kimchi/kvmusertests.py @@ -0,0 +1,63 @@ +# +# Project Kimchi +# +# Copyright IBM, Corp. 2013 +# +# Authors: +# ShaoHe Feng <shaohef@linux.vnet.ibm.com> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +import libvirt +import uuid +import psutil + +SIMPLE_VM_XML = """ +<domain type='kvm'> + <name>VM-test</name> + <uuid>%s</uuid> + <memory unit='KiB'>32768</memory> + <os> + <type arch='x86_64' machine='pc-0.15'>hvm</type> + <boot dev='hd'/> + </os> +</domain>""" + + +class UserTests(object): + def __init__(self): + self.vm_uuid = uuid.uuid3(uuid.NAMESPACE_DNS, 'vm-test.kimchi.org') + + def probe_user(self): + xml = SIMPLE_VM_XML % self.vm_uuid + user = 'qemu' + try: + conn = libvirt.open('qemu:///system') + dom = conn.defineXML(xml) + dom.create() + for p in psutil.process_iter(): + if self.vm_uuid in p.cmdline: + user = p.username + break + dom.destroy() + dom.undefine() + conn.close() + return user + except libvirt.libvirtError: + return None + +if __name__ == '__main__': + ut = UserTests() + print ut.probe_user()
-- Sheldon Feng(冯少合)<shaohef@linux.vnet.ibm.com> IBM Linux Technology Center

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> First will use setfacl to set the path search permission for user or group, if failed then set search permission for others on the path. Usage: check_path_permission("/tmp/need/to/fix/path", username) fix_path_permission("/tmp/need/to/fix/path", username) Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- src/kimchi/utils.py | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/src/kimchi/utils.py b/src/kimchi/utils.py index f7eda93..daf5c8d 100644 --- a/src/kimchi/utils.py +++ b/src/kimchi/utils.py @@ -18,11 +18,15 @@ # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # import cherrypy import os +import pwd +import re +import stat +import subprocess from cherrypy.lib.reprconf import Parser @@ -33,6 +37,7 @@ from kimchi import config kimchi_log = cherrypy.log.error_log + def is_digit(value): if isinstance(value, int): return True @@ -84,3 +89,112 @@ def import_class(class_path): def import_module(module_name): return __import__(module_name, fromlist=['']) + + +def name_uid(user): + return pwd.getpwnam(user).pw_uid + + +def run_command(cmd): + try: + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, error = proc.communicate() + + kimchi_log.debug("Run command '%s'", " ".join(cmd)) + if out or error: + kimchi_log.debug("out:\n %s\nerror:\n %s", out, error) + except OSError: + kimchi_log.debug("Failed to run command %s", " ".join(cmd)) + return (None, None, None) + if proc.returncode != 0: + kimchi_log.debug("Cmd '%s' failed, error code: %s", cmd, error) + return (out, error, proc.returncode) + + +def run_setfacl_set_x(path, user="qemu", group=None): + set_user = ["setfacl", "--modify", "user:%s:x" % user, path] + set_group = ["setfacl", "--modify", "group:%s:x" % group, path] + out, error, ret = run_command(set_user) + if ret != 0 and group: + out, error, ret = run_command(set_group) + return ret == 0 + + +def run_getfacl(path, user="qemu", group=None): + cmd = ["getfacl", path] + out, error, ret = run_command(cmd) + if out and ret == 0: + res = re.findall("user:%s:..x" % user, out) + res = re.findall("group:%s:..x" % group, + out) if group and not res else res + return res != [] + return False + + +def set_x_on_path(path, who="other"): + S_IXWHO = ((stat.S_IXUSR if who == "user" else None) or + (stat.S_IXGRP if who == "group" else None) or + stat.S_IXOTH) + mode = os.stat(path).st_mode | S_IXWHO + os.chmod(path, mode) + ret = os.stat(path).st_mode == mode + if not ret: + kimchi_log.debug("faild to set +x attribute on %s", path) + return ret + + +def get_x_on_path(path, uid): + try: + info = os.stat(path) + mode = info.st_mode + return ((uid == info.st_uid and mode & stat.S_IXUSR == stat.S_IXUSR) or + (uid == info.st_gid and mode & stat.S_IXGRP == stat.S_IXGRP) or + mode & stat.S_IXGRP == stat.S_IXOTH) + except OSError: + kimchi_log.debug("faild to get attribute on %s as user id: %s", + path, uid) + return False + + +def check_path_permission(path, user='qemu', group=None): + return get_x_on_path(path, + name_uid(user)) or run_getfacl(path, user, group) + + +def fix_path_permission(path, user='qemu'): + paths = [] + while path: + paths.append(path) + if path == "/": + break + path = os.path.dirname(path) + + paths.reverse() + for path in paths: + if not (check_path_permission(path) or + run_setfacl_set_x(path) or + set_x_on_path(path)): + return False + return True + + +def check_path_acl_permission(path, user='qemu', group=None): + return run_getfacl(path, user, group) + + +def fix_path_acl_permission(path, user='qemu'): + paths = [] + while path: + paths.append(path) + if path == "/": + break + path = os.path.dirname(path) + + paths.reverse() + for path in paths: + if not (run_getfacl(path, user) or run_setfacl_set_x(path, user)): + kimchi_log.debug("faild to set on researchable permission on %s " + "for user: %s", path, user) + return False + return True -- 1.8.4.2

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> We want to fix the searchable permission for qemu user. But we should find the username of qemu process firstly. searchable permission is a known problem. We have discussed several times on IRC. Royce reports the qemu username is different on different distros Adam, Aline, Royce and I think we can probe qemu username with the similar method of qemu iso stream support. Zhou Zheng Sheng gives a better way to find the qemu process ID Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Zhou Zheng Sheng <zhshzhou@linux.vnet.ibm.com> Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> Signed-off-by: Aline Manera <alinefm@linux.vnet.ibm.com> --- src/kimchi/kvmusertests.py | 66 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/kimchi/kvmusertests.py diff --git a/src/kimchi/kvmusertests.py b/src/kimchi/kvmusertests.py new file mode 100644 index 0000000..ccdb6bc --- /dev/null +++ b/src/kimchi/kvmusertests.py @@ -0,0 +1,66 @@ +# +# Project Kimchi +# +# Copyright IBM, Corp. 2013 +# +# Authors: +# ShaoHe Feng <shaohef@linux.vnet.ibm.com> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +import libvirt +import uuid +import psutil + +from kimchi.utils import RollbackContext + + +SIMPLE_VM_XML = """ +<domain type='kvm'> + <name>%s</name> + <uuid>%s</uuid> + <memory unit='KiB'>32768</memory> + <os> + <type arch='x86_64' machine='pc'>hvm</type> + <boot dev='hd'/> + </os> +</domain>""" + + +class UserTests(object): + def __init__(self): + self.vm_uuid = uuid.uuid3(uuid.NAMESPACE_DNS, 'vm-test.kimchi.org') + self.vm_name = "kimchi_test_%s" % self.vm_uuid + + def probe_user(self): + xml = SIMPLE_VM_XML % (self.vm_name, self.vm_uuid) + user = None + with RollbackContext() as rollback: + conn = libvirt.open('qemu:///system') + rollback.prependDefer(conn.close) + dom = conn.defineXML(xml) + rollback.prependDefer(dom.undefine) + dom.create() + rollback.prependDefer(dom.destroy) + with open('/var/run/libvirt/qemu/%s.pid' % self.vm_name) as f: + pidStr = f.read() + p = psutil.Process(int(pidStr)) + user = p.username + return user + + +if __name__ == '__main__': + ut = UserTests() + print ut.probe_user() -- 1.8.4.2

From: ShaoHe Feng <shaohef@linux.vnet.ibm.com> First will use setfacl to set the path search permission for user or group, if failed then set search permission for others on the path. Usage: check_path_permission("/tmp/need/to/fix/path", username) fix_path_permission("/tmp/need/to/fix/path", username) Signed-off-by: ShaoHe Feng <shaohef@linux.vnet.ibm.com> Signed-off-by: Royce Lv <lvroyce@linux.vnet.ibm.com> --- src/kimchi/utils.py | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/src/kimchi/utils.py b/src/kimchi/utils.py index bf2a760..e94aeb1 100644 --- a/src/kimchi/utils.py +++ b/src/kimchi/utils.py @@ -18,11 +18,15 @@ # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # import cherrypy import os +import pwd +import re +import stat +import subprocess from cherrypy.lib.reprconf import Parser @@ -33,6 +37,7 @@ from kimchi import config kimchi_log = cherrypy.log.error_log + def is_digit(value): if isinstance(value, int): return True @@ -130,3 +135,112 @@ class RollbackContext(object): def prependDefer(self, func, *args, **kwargs): self._finally.insert(0, (func, args, kwargs)) + + +def name_uid(user): + return pwd.getpwnam(user).pw_uid + + +def run_command(cmd): + try: + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + out, error = proc.communicate() + + kimchi_log.debug("Run command '%s'", " ".join(cmd)) + if out or error: + kimchi_log.debug("out:\n %s\nerror:\n %s", out, error) + except OSError: + kimchi_log.debug("Failed to run command %s", " ".join(cmd)) + return (None, None, None) + if proc.returncode != 0: + kimchi_log.debug("Cmd '%s' failed, error code: %s", cmd, error) + return (out, error, proc.returncode) + + +def run_setfacl_set_x(path, user="qemu", group=None): + set_user = ["setfacl", "--modify", "user:%s:x" % user, path] + set_group = ["setfacl", "--modify", "group:%s:x" % group, path] + out, error, ret = run_command(set_user) + if ret != 0 and group: + out, error, ret = run_command(set_group) + return ret == 0 + + +def run_getfacl(path, user="qemu", group=None): + cmd = ["getfacl", path] + out, error, ret = run_command(cmd) + if out and ret == 0: + res = re.findall("user:%s:..x" % user, out) + res = re.findall("group:%s:..x" % group, + out) if group and not res else res + return res != [] + return False + + +def set_x_on_path(path, who="other"): + S_IXWHO = ((stat.S_IXUSR if who == "user" else None) or + (stat.S_IXGRP if who == "group" else None) or + stat.S_IXOTH) + mode = os.stat(path).st_mode | S_IXWHO + os.chmod(path, mode) + ret = os.stat(path).st_mode == mode + if not ret: + kimchi_log.debug("faild to set +x attribute on %s", path) + return ret + + +def get_x_on_path(path, uid): + try: + info = os.stat(path) + mode = info.st_mode + return ((uid == info.st_uid and mode & stat.S_IXUSR == stat.S_IXUSR) or + (uid == info.st_gid and mode & stat.S_IXGRP == stat.S_IXGRP) or + mode & stat.S_IXGRP == stat.S_IXOTH) + except OSError: + kimchi_log.debug("faild to get attribute on %s as user id: %s", + path, uid) + return False + + +def check_path_permission(path, user='qemu', group=None): + return get_x_on_path(path, + name_uid(user)) or run_getfacl(path, user, group) + + +def fix_path_permission(path, user='qemu'): + paths = [] + while path: + paths.append(path) + if path == "/": + break + path = os.path.dirname(path) + + paths.reverse() + for path in paths: + if not (check_path_permission(path) or + run_setfacl_set_x(path) or + set_x_on_path(path)): + return False + return True + + +def check_path_acl_permission(path, user='qemu', group=None): + return run_getfacl(path, user, group) + + +def fix_path_acl_permission(path, user='qemu'): + paths = [] + while path: + paths.append(path) + if path == "/": + break + path = os.path.dirname(path) + + paths.reverse() + for path in paths: + if not (run_getfacl(path, user) or run_setfacl_set_x(path, user)): + kimchi_log.debug("faild to set on researchable permission on %s " + "for user: %s", path, user) + return False + return True -- 1.8.4.2
participants (2)
-
shaohef@linux.vnet.ibm.com
-
Sheldon