diff -r 23d8ee593895 -r b27aa79729f7 tools/python/xen/util/security.py --- a/tools/python/xen/util/security.py Tue Sep 04 14:48:29 2007 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1301 +0,0 @@ -#=========================================================================== -# This library is free software; you can redistribute it and/or -# modify it under the terms of version 2.1 of the GNU Lesser General Public -# License as published by the Free Software Foundation. -# -# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -#============================================================================ -# Copyright (C) 2006 International Business Machines Corp. -# Author: Reiner Sailer -# Author: Bryan D. Payne -# Author: Stefan Berger -#============================================================================ - -import commands -import logging -import os, string, re -import threading -import struct -import stat -from xen.lowlevel import acm -from xen.xend import sxp -from xen.xend import XendConstants -from xen.xend.XendLogging import log -from xen.xend.XendError import VmError -from xen.util import dictio, xsconstants -from xen.xend.XendConstants import * - -#global directories and tools for security management -policy_dir_prefix = "/etc/xen/acm-security/policies" -res_label_filename = policy_dir_prefix + "/resource_labels" -boot_filename = "/boot/grub/menu.lst" -altboot_filename = "/boot/grub/grub.conf" -xensec_xml2bin = "/usr/sbin/xensec_xml2bin" -xensec_tool = "/usr/sbin/xensec_tool" - -#global patterns for map file -#police_reference_tagname = "POLICYREFERENCENAME" -primary_entry_re = re.compile("\s*PRIMARY\s+.*", re.IGNORECASE) -secondary_entry_re = re.compile("\s*SECONDARY\s+.*", re.IGNORECASE) -label_template_re = re.compile(".*security_label_template.xml", re.IGNORECASE) -mapping_filename_re = re.compile(".*\.map", re.IGNORECASE) -policy_reference_entry_re = re.compile("\s*POLICYREFERENCENAME\s+.*", re.IGNORECASE) -vm_label_re = re.compile("\s*LABEL->SSID\s+VM\s+.*", re.IGNORECASE) -res_label_re = re.compile("\s*LABEL->SSID\s+RES\s+.*", re.IGNORECASE) -all_label_re = re.compile("\s*LABEL->SSID\s+.*", re.IGNORECASE) -access_control_re = re.compile("\s*access_control\s*=", re.IGNORECASE) - -#global patterns for boot configuration file -xen_title_re = re.compile("\s*title\s+XEN", re.IGNORECASE) -any_title_re = re.compile("\s*title\s", re.IGNORECASE) -xen_kernel_re = re.compile("\s*kernel.*xen.*\.gz", re.IGNORECASE) -kernel_ver_re = re.compile("\s*module.*vmlinuz", re.IGNORECASE) -any_module_re = re.compile("\s*module\s", re.IGNORECASE) -empty_line_re = re.compile("^\s*$") -binary_name_re = re.compile(".*[chwall|ste|chwall_ste].*\.bin", re.IGNORECASE) -policy_name_re = re.compile(".*[chwall|ste|chwall_ste].*", re.IGNORECASE) - -#decision hooks known to the hypervisor -ACMHOOK_sharing = 1 -ACMHOOK_authorization = 2 - -#other global variables -NULL_SSIDREF = 0 - -#general Rlock for map files; only one lock for all mapfiles -__mapfile_lock = threading.RLock() -__resfile_lock = threading.RLock() - -log = logging.getLogger("xend.util.security") - -# Our own exception definition. It is masked (pass) if raised and -# whoever raises this exception must provide error information. -class ACMError(Exception): - def __init__(self,value): - self.value = value - def __str__(self): - return repr(self.value) - - - -def err(msg): - """Raise ACM exception. - """ - raise ACMError(msg) - - - -active_policy = None - - -def mapfile_lock(): - __mapfile_lock.acquire() - -def mapfile_unlock(): - __mapfile_lock.release() - - -def refresh_security_policy(): - """ - retrieves security policy - """ - global active_policy - - try: - active_policy = acm.policy() - except: - active_policy = "INACTIVE" - -# now set active_policy -refresh_security_policy() - -def on(): - """ - returns none if security policy is off (not compiled), - any string otherwise, use it: if not security.on() ... - """ - refresh_security_policy() - return (active_policy not in ['INACTIVE', 'NULL']) - - -def calc_dom_ssidref_from_info(info): - """ - Calculate a domain's ssidref from the security_label in its - info. - This function is called before the domain is started and - makes sure that: - - the type of the policy is the same as indicated in the label - - the name of the policy is the same as indicated in the label - - calculates an up-to-date ssidref for the domain - The latter is necessary since the domain's ssidref could have - changed due to changes to the policy. - """ - import xen.xend.XendConfig - if isinstance(info, xen.xend.XendConfig.XendConfig): - if info.has_key('security_label'): - seclab = info['security_label'] - tmp = seclab.split(":") - if len(tmp) != 3: - raise VmError("VM label '%s' in wrong format." % seclab) - typ, policyname, vmlabel = seclab.split(":") - if typ != xsconstants.ACM_POLICY_ID: - raise VmError("Policy type '%s' must be changed." % typ) - refresh_security_policy() - if active_policy != policyname: - raise VmError("Active policy '%s' different than " - "what in VM's label ('%s')." % - (active_policy, policyname)) - ssidref = label2ssidref(vmlabel, policyname, "dom") - return ssidref - else: - return 0x0 - raise VmError("security.calc_dom_ssidref_from_info: info of type '%s'" - "not supported." % type(info)) - - -def getmapfile(policyname): - """ - in: if policyname is None then the currently - active hypervisor policy is used - out: 1. primary policy, 2. secondary policy, - 3. open file descriptor for mapping file, and - 4. True if policy file is available, False otherwise - """ - if not policyname: - policyname = active_policy - map_file_ok = False - primary = None - secondary = None - #strip last part of policy as file name part - policy_dir_list = string.split(policyname, ".") - policy_file = policy_dir_list.pop() - if len(policy_dir_list) > 0: - policy_dir = string.join(policy_dir_list, "/") + "/" - else: - policy_dir = "" - - map_filename = policy_dir_prefix + "/" + policy_dir + policy_file + ".map" - # check if it is there, if not check if policy file is there - if not os.path.isfile(map_filename): - policy_filename = policy_dir_prefix + "/" + policy_dir + policy_file + "-security_policy.xml" - if not os.path.isfile(policy_filename): - err("Policy file \'" + policy_filename + "\' not found.") - else: - err("Mapping file \'" + map_filename + "\' not found." + - " Use xm makepolicy to create it.") - - f = open(map_filename) - for line in f: - if policy_reference_entry_re.match(line): - l = line.split() - if (len(l) == 2) and (l[1] == policyname): - map_file_ok = True - elif primary_entry_re.match(line): - l = line.split() - if len(l) == 2: - primary = l[1] - elif secondary_entry_re.match(line): - l = line.split() - if len(l) == 2: - secondary = l[1] - f.close() - f = open(map_filename) - if map_file_ok and primary and secondary: - return (primary, secondary, f, True) - else: - err("Mapping file inconsistencies found. Try makepolicy to create a new one.") - - - -def ssidref2label(ssidref_var): - """ - returns labelname corresponding to ssidref; - maps current policy to default directory - to find mapping file - """ - #1. translated permitted input formats - if isinstance(ssidref_var, str): - ssidref_var.strip() - if ssidref_var[0:2] == "0x": - ssidref = int(ssidref_var[2:], 16) - else: - ssidref = int(ssidref_var) - elif isinstance(ssidref_var, int): - ssidref = ssidref_var - else: - err("Instance type of ssidref not supported (must be of type 'str' or 'int')") - - if ssidref == 0: - from xen.util.acmpolicy import ACM_LABEL_UNLABELED - return ACM_LABEL_UNLABELED - - try: - mapfile_lock() - - (primary, secondary, f, pol_exists) = getmapfile(None) - if not f: - if (pol_exists): - err("Mapping file for policy not found.\n" + - "Please use makepolicy command to create mapping file!") - else: - err("Policy file for \'" + active_policy + "\' not found.") - - #2. get labelnames for both ssidref parts - pri_ssid = ssidref & 0xffff - sec_ssid = ssidref >> 16 - pri_null_ssid = NULL_SSIDREF & 0xffff - sec_null_ssid = NULL_SSIDREF >> 16 - pri_labels = [] - sec_labels = [] - labels = [] - - for line in f: - l = line.split() - if (len(l) < 5) or (l[0] != "LABEL->SSID"): - continue - if primary and (l[2] == primary) and (int(l[4], 16) == pri_ssid): - pri_labels.append(l[3]) - if secondary and (l[2] == secondary) and (int(l[4], 16) == sec_ssid): - sec_labels.append(l[3]) - f.close() - finally: - mapfile_unlock() - - #3. get the label that is in both lists (combination must be a single label) - if (primary == "CHWALL") and (pri_ssid == pri_null_ssid) and (sec_ssid != sec_null_ssid): - labels = sec_labels - elif (secondary == "CHWALL") and (pri_ssid != pri_null_ssid) and (sec_ssid == sec_null_ssid): - labels = pri_labels - elif secondary == "NULL": - labels = pri_labels - else: - for i in pri_labels: - for j in sec_labels: - if (i==j): - labels.append(i) - if len(labels) != 1: - err("Label for ssidref \'" + str(ssidref) + - "\' unknown or not unique in policy \'" + active_policy + "\'") - - return labels[0] - - - -def label2ssidref(labelname, policyname, typ): - """ - returns ssidref corresponding to labelname; - maps current policy to default directory - to find mapping file """ - - if policyname in ['NULL', 'INACTIVE', 'DEFAULT']: - err("Cannot translate labels for \'" + policyname + "\' policy.") - - allowed_types = ['ANY'] - if typ == 'dom': - allowed_types.append('VM') - elif typ == 'res': - allowed_types.append('RES') - else: - err("Invalid type. Must specify 'dom' or 'res'.") - - try: - mapfile_lock() - (primary, secondary, f, pol_exists) = getmapfile(policyname) - - #2. get labelnames for ssidref parts and find a common label - pri_ssid = [] - sec_ssid = [] - for line in f: - l = line.split() - if (len(l) < 5) or (l[0] != "LABEL->SSID"): - continue - if primary and (l[1] in allowed_types) and \ - (l[2] == primary) and \ - (l[3] == labelname): - pri_ssid.append(int(l[4], 16)) - if secondary and (l[1] in allowed_types) and \ - (l[2] == secondary) and \ - (l[3] == labelname): - sec_ssid.append(int(l[4], 16)) - f.close() - if (typ == 'res') and (primary == "CHWALL") and (len(pri_ssid) == 0): - pri_ssid.append(NULL_SSIDREF) - elif (typ == 'res') and (secondary == "CHWALL") and \ - (len(sec_ssid) == 0): - sec_ssid.append(NULL_SSIDREF) - - #3. sanity check and composition of ssidref - if (len(pri_ssid) == 0) or ((len(sec_ssid) == 0) and \ - (secondary != "NULL")): - err("Label \'" + labelname + "\' not found.") - elif (len(pri_ssid) > 1) or (len(sec_ssid) > 1): - err("Label \'" + labelname + "\' not unique in policy (policy error)") - if secondary == "NULL": - return pri_ssid[0] - else: - return (sec_ssid[0] << 16) | pri_ssid[0] - finally: - mapfile_unlock() - - -def refresh_ssidref(config): - """ - looks up ssidref from security field - and refreshes the value if label exists - """ - #called by dom0, policy could have changed after xen.utils.security was initialized - refresh_security_policy() - - security = None - if isinstance(config, dict): - security = config['security'] - elif isinstance(config, list): - security = sxp.child_value(config, 'security') - else: - err("Instance type of config parameter not supported.") - if not security: - #nothing to do (no security label attached) - return config - - policyname = None - labelname = None - # compose new security field - for idx in range(0, len(security)): - if security[idx][0] == 'ssidref': - security.pop(idx) - break - elif security[idx][0] == 'access_control': - for jdx in [1, 2]: - if security[idx][jdx][0] == 'label': - labelname = security[idx][jdx][1] - elif security[idx][jdx][0] == 'policy': - policyname = security[idx][jdx][1] - else: - err("Illegal field in access_control") - #verify policy is correct - if active_policy != policyname: - err("Policy \'" + str(policyname) + - "\' in label does not match active policy \'" - + str(active_policy) +"\'!") - - new_ssidref = label2ssidref(labelname, policyname, 'dom') - if not new_ssidref: - err("SSIDREF refresh failed!") - - security.append([ 'ssidref',str(new_ssidref)]) - security = ['security', security ] - - for idx in range(0,len(config)): - if config[idx][0] == 'security': - config.pop(idx) - break - config.append(security) - - - -def get_ssid(domain): - """ - enables domains to retrieve the label / ssidref of a running domain - """ - if not on(): - err("No policy active.") - - if isinstance(domain, str): - domain_int = int(domain) - elif isinstance(domain, int): - domain_int = domain - else: - err("Illegal parameter type.") - try: - ssid_info = acm.getssid(int(domain_int)) - except: - err("Cannot determine security information.") - - if active_policy in ["DEFAULT"]: - label = "DEFAULT" - else: - label = ssidref2label(ssid_info["ssidref"]) - return(ssid_info["policyreference"], - label, - ssid_info["policytype"], - ssid_info["ssidref"]) - - - -def get_decision(arg1, arg2): - """ - enables domains to retrieve access control decisions from - the hypervisor Access Control Module. - IN: args format = ['domid', id] or ['ssidref', ssidref] - or ['access_control', ['policy', policy], ['label', label], ['type', type]] - """ - - if not on(): - err("No policy active.") - - #translate labels before calling low-level function - if arg1[0] == 'access_control': - if (arg1[1][0] != 'policy') or (arg1[2][0] != 'label') or (arg1[3][0] != 'type'): - err("Argument type not supported.") - ssidref = label2ssidref(arg1[2][1], arg1[1][1], arg1[3][1]) - arg1 = ['ssidref', str(ssidref)] - if arg2[0] == 'access_control': - if (arg2[1][0] != 'policy') or (arg2[2][0] != 'label') or (arg2[3][0] != 'type'): - err("Argument type not supported.") - ssidref = label2ssidref(arg2[2][1], arg2[1][1], arg2[3][1]) - arg2 = ['ssidref', str(ssidref)] - - # accept only int or string types for domid and ssidref - if isinstance(arg1[1], int): - arg1[1] = str(arg1[1]) - if isinstance(arg2[1], int): - arg2[1] = str(arg2[1]) - if not isinstance(arg1[1], str) or not isinstance(arg2[1], str): - err("Invalid id or ssidref type, string or int required") - - try: - decision = acm.getdecision(arg1[0], arg1[1], arg2[0], arg2[1], - ACMHOOK_sharing) - except: - err("Cannot determine decision.") - - if decision: - return decision - else: - err("Cannot determine decision (Invalid parameter).") - - -def has_authorization(ssidref): - """ Check if the domain with the given ssidref has authorization to - run on this system. To have authoriztion dom0's STE types must - be a superset of that of the domain's given through its ssidref. - """ - rc = True - dom0_ssidref = int(acm.getssid(0)['ssidref']) - decision = acm.getdecision('ssidref', str(dom0_ssidref), - 'ssidref', str(ssidref), - ACMHOOK_authorization) - if decision == "DENIED": - rc = False - return rc - - -def hv_chg_policy(bin_pol, del_array, chg_array): - """ - Change the binary policy in the hypervisor - The 'del_array' and 'chg_array' give hints about deleted ssidrefs - and changed ssidrefs which can be due to deleted VM labels - or reordered VM labels - """ - rc = -xsconstants.XSERR_GENERAL_FAILURE - errors = "" - if not on(): - err("No policy active.") - try: - rc, errors = acm.chgpolicy(bin_pol, del_array, chg_array) - except Exception, e: - pass - if len(errors) > 0: - rc = -xsconstants.XSERR_HV_OP_FAILED - return rc, errors - - -def make_policy(policy_name): - policy_file = string.join(string.split(policy_name, "."), "/") - if not os.path.isfile(policy_dir_prefix + "/" + policy_file + "-security_policy.xml"): - err("Unknown policy \'" + policy_name + "\'") - - (ret, output) = commands.getstatusoutput(xensec_xml2bin + " -d " + policy_dir_prefix + " " + policy_file) - if ret: - err("Creating policy failed:\n" + output) - -def load_policy(policy_name): - global active_policy - policy_file = policy_dir_prefix + "/" + string.join(string.split(policy_name, "."), "/") - if not os.path.isfile(policy_file + ".bin"): - if os.path.isfile(policy_file + "-security_policy.xml"): - err("Binary file does not exist." + - "Please use makepolicy to build the policy binary.") - else: - err("Unknown Policy " + policy_name) - - #require this policy to be the first or the same as installed - if active_policy not in ['DEFAULT', policy_name]: - err("Active policy \'" + active_policy + - "\' incompatible with new policy \'" + policy_name + "\'") - (ret, output) = commands.getstatusoutput(xensec_tool + " loadpolicy " + policy_file + ".bin") - if ret: - err("Loading policy failed:\n" + output) - else: - # refresh active policy - refresh_security_policy() - - - -def dump_policy(): - if active_policy in ['NULL', 'INACTIVE']: - err("\'" + active_policy + "\' policy. Nothing to dump.") - - (ret, output) = commands.getstatusoutput(xensec_tool + " getpolicy") - if ret: - err("Dumping hypervisor policy failed:\n" + output) - print output - - - -def list_labels(policy_name, condition): - if (not policy_name) and (active_policy) in ["NULL", "INACTIVE", "DEFAULT"]: - err("Current policy \'" + active_policy + "\' has no labels defined.\n") - - (primary, secondary, f, pol_exists) = getmapfile(policy_name) - if not f: - if pol_exists: - err("Cannot find mapfile for policy \'" + policy_name + - "\'.\nPlease use makepolicy to create mapping file.") - else: - err("Unknown policy \'" + policy_name + "\'") - - labels = [] - for line in f: - if condition.match(line): - label = line.split()[3] - if label not in labels: - labels.append(label) - return labels - - -def get_res_label(resource): - """Returns resource label information (policytype, label, policy) if - it exists. Otherwise returns null label and policy. - """ - def default_res_label(): - ssidref = NULL_SSIDREF - if on(): - label = ssidref2label(ssidref) - else: - label = None - return (xsconstants.ACM_POLICY_ID, 'NULL', label) - - - tmp = get_resource_label(resource) - if len(tmp) == 2: - policytype = xsconstants.ACM_POLICY_ID - policy, label = tmp - elif len(tmp) == 3: - policytype, policy, label = tmp - else: - policytype, policy, label = default_res_label() - - return (policytype, label, policy) - - -def get_res_security_details(resource): - """Returns the (label, ssidref, policy) associated with a given - resource from the global resource label file. - """ - def default_security_details(): - ssidref = NULL_SSIDREF - if on(): - label = ssidref2label(ssidref) - else: - label = None - policy = active_policy - return (label, ssidref, policy) - - (label, ssidref, policy) = default_security_details() - - # find the entry associated with this resource - (policytype, label, policy) = get_res_label(resource) - if policy == 'NULL': - log.info("Resource label for "+resource+" not in file, using DEFAULT.") - return default_security_details() - - # is this resource label for the running policy? - if policy == active_policy: - ssidref = label2ssidref(label, policy, 'res') - else: - log.info("Resource label not for active policy, using DEFAULT.") - return default_security_details() - - return (label, ssidref, policy) - -def security_label_to_details(seclab): - """ Convert a Xen-API type of security label into details """ - def default_security_details(): - ssidref = NULL_SSIDREF - if on(): - label = ssidref2label(ssidref) - else: - label = None - policy = active_policy - return (label, ssidref, policy) - - (policytype, policy, label) = seclab.split(":") - - # is this resource label for the running policy? - if policy == active_policy: - ssidref = label2ssidref(label, policy, 'res') - else: - log.info("Resource label not for active policy, using DEFAULT.") - return default_security_details() - - return (label, ssidref, policy) - -def unify_resname(resource, mustexist=True): - """Makes all resource locations absolute. In case of physical - resources, '/dev/' is added to local file names""" - - if not resource: - return resource - - # sanity check on resource name - try: - (typ, resfile) = resource.split(":", 1) - except: - err("Resource spec '%s' contains no ':' delimiter" % resource) - - if typ == "tap": - try: - (subtype, resfile) = resfile.split(":") - except: - err("Resource spec '%s' contains no tap subtype" % resource) - - import os - if typ in ["phy", "tap"]: - if not resfile.startswith("/"): - resfile = "/dev/" + resfile - if mustexist: - stats = os.lstat(resfile) - if stat.S_ISLNK(stats[stat.ST_MODE]): - resolved = os.readlink(resfile) - if resolved[0] != "/": - resfile = os.path.join(os.path.dirname(resfile), resolved) - resfile = os.path.abspath(resfile) - else: - resfile = resolved - stats = os.lstat(resfile) - if not (stat.S_ISBLK(stats[stat.ST_MODE])): - err("Invalid resource") - - if typ in [ "file", "tap" ]: - if mustexist: - stats = os.lstat(resfile) - if stat.S_ISLNK(stats[stat.ST_MODE]): - resfile = os.readlink(resfile) - stats = os.lstat(resfile) - if not stat.S_ISREG(stats[stat.ST_MODE]): - err("Invalid resource") - - #file: resources must specified with absolute path - #vlan resources don't start with '/' - if typ != "vlan": - if (not resfile.startswith("/")) or \ - (mustexist and not os.path.exists(resfile)): - err("Invalid resource.") - - # from here on absolute file names with resources - if typ == "tap": - typ = typ + ":" + subtype - resource = typ + ":" + resfile - return resource - - -def res_security_check(resource, domain_label): - """Checks if the given resource can be used by the given domain - label. Returns 1 if the resource can be used, otherwise 0. - """ - rtnval = 1 - - # if security is on, ask the hypervisor for a decision - if on(): - #build canonical resource name - resource = unify_resname(resource) - - (label, ssidref, policy) = get_res_security_details(resource) - domac = ['access_control'] - domac.append(['policy', active_policy]) - domac.append(['label', domain_label]) - domac.append(['type', 'dom']) - decision = get_decision(domac, ['ssidref', str(ssidref)]) - - # provide descriptive error messages - if decision == 'DENIED': - if label == ssidref2label(NULL_SSIDREF): - raise ACMError("Resource '"+resource+"' is not labeled") - rtnval = 0 - else: - raise ACMError("Permission denied for resource '"+resource+"' because label '"+label+"' is not allowed") - rtnval = 0 - - # security is off, make sure resource isn't labeled - else: - # Note, we can't canonicalise the resource here, because people using - # xm without ACM are free to use relative paths. - (policytype, label, policy) = get_res_label(resource) - if policy != 'NULL': - raise ACMError("Security is off, but '"+resource+"' is labeled") - rtnval = 0 - - return rtnval - -def res_security_check_xapi(rlabel, rssidref, rpolicy, xapi_dom_label): - """Checks if the given resource can be used by the given domain - label. Returns 1 if the resource can be used, otherwise 0. - """ - rtnval = 1 - # if security is on, ask the hypervisor for a decision - if on(): - typ, dpolicy, domain_label = xapi_dom_label.split(":") - if not dpolicy or not domain_label: - raise VmError("VM security label in wrong format.") - if active_policy != rpolicy: - raise VmError("Resource's policy '%s' != active policy '%s'" % - (rpolicy, active_policy)) - domac = ['access_control'] - domac.append(['policy', active_policy]) - domac.append(['label', domain_label]) - domac.append(['type', 'dom']) - decision = get_decision(domac, ['ssidref', str(rssidref)]) - - log.info("Access Control Decision : %s" % decision) - # provide descriptive error messages - if decision == 'DENIED': - if rlabel == ssidref2label(NULL_SSIDREF): - #raise ACMError("Resource is not labeled") - rtnval = 0 - else: - #raise ACMError("Permission denied for resource because label '"+rlabel+"' is not allowed") - rtnval = 0 - - # security is off, make sure resource isn't labeled - else: - # Note, we can't canonicalise the resource here, because people using - # xm without ACM are free to use relative paths. - if rpolicy != 'NULL': - #raise ACMError("Security is off, but resource is labeled") - rtnval = 0 - - return rtnval - - -def validate_label(label, policyref): - """ - Make sure that this label is part of the currently enforced policy - and that it reference the current policy. - """ - rc = xsconstants.XSERR_SUCCESS - from xen.xend.XendXSPolicyAdmin import XSPolicyAdminInstance - curpol = XSPolicyAdminInstance().get_loaded_policy() - if not curpol or curpol.get_name() != policyref: - rc = -xsconstants.XSERR_BAD_LABEL - else: - try: - label2ssidref(label, curpol.get_name() , 'res') - except: - rc = -xsconstants.XSERR_BAD_LABEL - return rc - - -def set_resource_label_xapi(resource, reslabel_xapi, oldlabel_xapi): - """Assign a resource label to a resource - @param resource: The name of a resource, i.e., "phy:/dev/hda", or - "tap:qcow:/path/to/file.qcow" - - @param reslabel_xapi: A resource label foramtted as in all other parts of - the Xen-API, i.e., ACM:xm-test:blue" - @rtype: int - @return Success (0) or failure value (< 0) - """ - olabel = "" - if reslabel_xapi == "": - return rm_resource_label(resource, oldlabel_xapi) - typ, policyref, label = reslabel_xapi.split(":") - if typ != xsconstants.ACM_POLICY_ID: - return -xsconstants.XSERR_WRONG_POLICY_TYPE - if not policyref or not label: - return -xsconstants.XSERR_BAD_LABEL_FORMAT - if oldlabel_xapi not in [ "" ]: - tmp = oldlabel_xapi.split(":") - if len(tmp) != 3: - return -xsconstants.XSERR_BAD_LABEL_FORMAT - otyp, opolicyref, olabel = tmp - # Only ACM is supported - if otyp != xsconstants.ACM_POLICY_ID and \ - otyp != xsconstants.INVALID_POLICY_PREFIX + \ - xsconstants.ACM_POLICY_ID: - return -xsconstants.XSERR_WRONG_POLICY_TYPE - rc = validate_label(label, policyref) - if rc != xsconstants.XSERR_SUCCESS: - return rc - return set_resource_label(resource, typ, policyref, label, olabel) - - -def is_resource_in_use(resource): - """ - Domain-0 'owns' resources of type 'VLAN', the rest are owned by - the guests. - """ - from xen.xend import XendDomain - lst = [] - if resource.startswith('vlan'): - from xen.xend.XendXSPolicyAdmin import XSPolicyAdminInstance - curpol = XSPolicyAdminInstance().get_loaded_policy() - policytype, label, policy = get_res_label(resource) - if curpol and \ - policytype == xsconstants.ACM_POLICY_ID and \ - policy == curpol.get_name() and \ - label in curpol.policy_get_resourcelabel_names(): - # VLAN is in use. - lst.append(XendDomain.instance(). - get_vm_by_uuid(XendDomain.DOM0_UUID)) - else: - dominfos = XendDomain.instance().list('all') - for dominfo in dominfos: - if is_resource_in_use_by_dom(dominfo, resource): - lst.append(dominfo) - return lst - -def devices_equal(res1, res2, mustexist=True): - """ Determine whether two devices are equal """ - return (unify_resname(res1, mustexist) == - unify_resname(res2, mustexist)) - -def is_resource_in_use_by_dom(dominfo, resource): - """ Determine whether a resources is in use by a given domain - @return True or False - """ - if not dominfo.domid: - return False - if dominfo._stateGet() not in [ DOM_STATE_RUNNING ]: - return False - devs = dominfo.info['devices'] - uuids = devs.keys() - for uuid in uuids: - dev = devs[uuid] - if len(dev) >= 2 and dev[1].has_key('uname'): - # dev[0] is type, i.e. 'vbd' - if devices_equal(dev[1]['uname'], resource, mustexist=False): - log.info("RESOURCE IN USE: Domain %d uses %s." % - (dominfo.domid, resource)) - return True - return False - - -def get_domain_resources(dominfo): - """ Collect all resources of a domain in a map where each entry of - the map is a list. - Entries are strored in the following formats: - tap:qcow:/path/xyz.qcow - """ - resources = { 'vbd' : [], 'tap' : [], 'vif' : []} - devs = dominfo.info['devices'] - uuids = devs.keys() - for uuid in uuids: - dev = devs[uuid] - typ = dev[0] - if typ in [ 'vbd', 'tap' ]: - resources[typ].append(dev[1]['uname']) - if typ in [ 'vif' ]: - sec_lab = dev[1].get('security_label') - if sec_lab: - resources[typ].append(sec_lab) - else: - # !!! This should really get the label of the domain - # or at least a resource label that has the same STE type - # as the domain has - from xen.util.acmpolicy import ACM_LABEL_UNLABELED - resources[typ].append("%s:%s:%s" % - (xsconstants.ACM_POLICY_ID, - active_policy, - ACM_LABEL_UNLABELED)) - - return resources - - -def resources_compatible_with_vmlabel(xspol, dominfo, vmlabel): - """ - Check whether the resources' labels are compatible with the - given VM label. This is a function to be used when for example - a running domain is to get the new label 'vmlabel' - """ - if not xspol: - return False - - try: - __resfile_lock.acquire() - try: - access_control = dictio.dict_read("resources", - res_label_filename) - except: - # No labeled resources -> must be compatible - return True - return __resources_compatible_with_vmlabel(xspol, dominfo, vmlabel, - access_control) - finally: - __resfile_lock.release() - return False - - -def __resources_compatible_with_vmlabel(xspol, dominfo, vmlabel, - access_control, - is_policy_update=False): - """ - Check whether the resources' labels are compatible with the - given VM label. The access_control parameter provides a - dictionary of the resource name to resource label mappings - under which the evaluation should be done. - Call this only for a paused or running domain. - """ - def collect_labels(reslabels, s_label, polname): - if len(s_label) != 3 or polname != s_label[1]: - return False - label = s_label[2] - if not label in reslabels: - reslabels.append(label) - return True - - resources = get_domain_resources(dominfo) - reslabels = [] # all resource labels - - polname = xspol.get_name() - for key, value in resources.items(): - if key in [ 'vbd', 'tap' ]: - for res in resources[key]: - try: - label = access_control[res] - if not collect_labels(reslabels, label, polname): - return False - except: - return False - elif key in [ 'vif' ]: - for xapi_label in value: - label = xapi_label.split(":") - from xen.util.acmpolicy import ACM_LABEL_UNLABELED - if not (is_policy_update and \ - label[2] == ACM_LABEL_UNLABELED): - if not collect_labels(reslabels, label, polname): - return False - else: - log.error("Unhandled device type: %s" % key) - return False - - # Check that all resource labes have a common STE type with the - # vmlabel - if len(reslabels) > 0: - rc = xspol.policy_check_vmlabel_against_reslabels(vmlabel, reslabels) - else: - rc = True - log.info("vmlabel=%s, reslabels=%s, rc=%s" % - (vmlabel, reslabels, str(rc))) - return rc; - -def set_resource_label(resource, policytype, policyref, reslabel, \ - oreslabel = None): - """Assign a label to a resource - If the old label (oreslabel) is given, then the resource must have - that old label. - A resource label may be changed if - - the resource is not in use - @param resource : The name of a resource, i.e., "phy:/dev/hda" - @param policyref : The name of the policy - @param reslabel : the resource label within the policy - @param oreslabel : optional current resource label - - @rtype: int - @return Success (0) or failure value (< 0) - """ - try: - resource = unify_resname(resource, mustexist=False) - except Exception: - return -xsconstants.XSERR_BAD_RESOURCE_FORMAT - - domains = is_resource_in_use(resource) - if len(domains) > 0: - return -xsconstants.XSERR_RESOURCE_IN_USE - - try: - __resfile_lock.acquire() - access_control = {} - try: - access_control = dictio.dict_read("resources", res_label_filename) - except: - pass - if oreslabel: - if not access_control.has_key(resource): - return -xsconstants.XSERR_BAD_LABEL - tmp = access_control[resource] - if len(tmp) != 3: - return -xsconstants.XSERR_BAD_LABEL - if tmp[2] != oreslabel: - return -xsconstants.XSERR_BAD_LABEL - if reslabel != "": - new_entry = { resource : tuple([policytype, policyref, reslabel])} - access_control.update(new_entry) - else: - if access_control.has_key(resource): - del access_control[resource] - dictio.dict_write(access_control, "resources", res_label_filename) - finally: - __resfile_lock.release() - return xsconstants.XSERR_SUCCESS - -def rm_resource_label(resource, oldlabel_xapi): - """Remove a resource label from a physical resource - @param resource: The name of a resource, i.e., "phy:/dev/hda" - - @rtype: int - @return Success (0) or failure value (< 0) - """ - tmp = oldlabel_xapi.split(":") - if len(tmp) != 3: - return -xsconstants.XSERR_BAD_LABEL_FORMAT - otyp, opolicyref, olabel = tmp - # Only ACM is supported - if otyp != xsconstants.ACM_POLICY_ID and \ - otyp != xsconstants.INVALID_POLICY_PREFIX + xsconstants.ACM_POLICY_ID: - return -xsconstants.XSERR_WRONG_POLICY_TYPE - return set_resource_label(resource, "", "", "", olabel) - -def get_resource_label_xapi(resource): - """Get the assigned resource label of a physical resource - in the format used by then Xen-API, i.e., "ACM:xm-test:blue" - - @rtype: string - @return the string representing policy type, policy name and label of - the resource - """ - res = get_resource_label(resource) - return format_resource_label(res) - -def format_resource_label(res): - if res: - if len(res) == 2: - return xsconstants.ACM_POLICY_ID + ":" + res[0] + ":" + res[1] - if len(res) == 3: - return ":".join(res) - return "" - -def get_resource_label(resource): - """Get the assigned resource label of a given resource - @param resource: The name of a resource, i.e., "phy:/dev/hda" - - @rtype: list - @return tuple of (policy name, resource label), i.e., (xm-test, blue) - """ - try: - resource = unify_resname(resource, mustexist=False) - except Exception: - return [] - - reslabel_map = get_labeled_resources() - - if reslabel_map.has_key(resource): - return list(reslabel_map[resource]) - else: - #Try to resolve each label entry - for key, value in reslabel_map.items(): - try: - if resource == unify_resname(key): - return list(value) - except: - pass - - return [] - - -def get_labeled_resources_xapi(): - """ Get a map of all labeled resource with the labels formatted in the - xen-api resource label format. - """ - reslabel_map = get_labeled_resources() - for key, labeldata in reslabel_map.items(): - reslabel_map[key] = format_resource_label(labeldata) - return reslabel_map - - -def get_labeled_resources(): - """Get a map of all labeled resources - @rtype: list - @return list of labeled resources - """ - try: - __resfile_lock.acquire() - try: - access_control = dictio.dict_read("resources", res_label_filename) - except: - return {} - finally: - __resfile_lock.release() - return access_control - - -def relabel_domains(relabel_list): - """ - Relabel the given domains to have a new ssidref. - @param relabel_list: a list containing tuples of domid, ssidref - example: [ [0, 0x00020002] ] - """ - rel_rules = "" - for r in relabel_list: - log.info("Relabeling domain with domid %d to new ssidref 0x%08x", - r[0], r[1]) - rel_rules += struct.pack("ii", r[0], r[1]) - try: - rc, errors = acm.relabel_domains(rel_rules) - except Exception, e: - log.info("Error after relabel_domains: %s" % str(e)) - rc = -xsconstants.XSERR_GENERAL_FAILURE - errors = "" - if (len(errors) > 0): - rc = -xsconstants.XSERR_HV_OP_FAILED - return rc, errors - - -def change_acm_policy(bin_pol, del_array, chg_array, - vmlabel_map, reslabel_map, cur_acmpol, new_acmpol): - """ - Change the ACM policy of the system by relabeling - domains and resources first and doing some access checks. - Then update the policy in the hypervisor. If this is all successful, - relabel the domains permanently and commit the relabed resources. - - Need to do / check the following: - - relabel all resources where there is a 'from' field in - the policy. [ NOT DOING THIS: and mark those as unlabeled where the label - does not appear in the new policy anymore (deletion) ] - - relabel all VMs where there is a 'from' field in the - policy and mark those as unlabeled where the label - does not appear in the new policy anymore; no running - or paused VM may be unlabeled through this - - check that under the new labeling conditions the VMs - still have access to their resources as before. Unlabeled - resources are inaccessible. If this check fails, the - update failed. - - Attempt changes in the hypervisor; if this step fails, - roll back the relabeling of resources and VMs - - Make the relabeling of resources and VMs permanent - """ - rc = xsconstants.XSERR_SUCCESS - - domain_label_map = {} - new_policyname = new_acmpol.get_name() - new_policytype = new_acmpol.get_type_name() - cur_policyname = cur_acmpol.get_name() - cur_policytype = cur_acmpol.get_type_name() - polnew_reslabels = new_acmpol.policy_get_resourcelabel_names() - errors="" - - try: - __resfile_lock.acquire() - mapfile_lock() - - # Get all domains' dominfo. - from xen.xend import XendDomain - dominfos = XendDomain.instance().list('all') - - log.info("----------------------------------------------") - # relabel resources - - access_control = {} - try: - access_control = dictio.dict_read("resources", res_label_filename) - except: - pass - for key, labeldata in access_control.items(): - if len(labeldata) == 2: - policy, label = labeldata - policytype = xsconstants.ACM_POLICY_ID - elif len(labeldata) == 3: - policytype, policy, label = labeldata - else: - return -xsconstants.XSERR_BAD_LABEL_FORMAT, "" - - if policytype != cur_policytype or \ - policy != cur_policyname: - continue - - # label been renamed or deleted? - if reslabel_map.has_key(label) and cur_policyname == policy: - label = reslabel_map[label] - elif label not in polnew_reslabels: - policytype = xsconstants.INVALID_POLICY_PREFIX + policytype - # Update entry - access_control[key] = \ - tuple([ policytype, new_policyname, label ]) - - # All resources have new labels in the access_control map - # There may still be labels in there that are invalid now. - - # Do this in memory without writing to disk: - # - Relabel all domains independent of whether they are running - # or not - # - later write back to config files - polnew_vmlabels = new_acmpol.policy_get_virtualmachinelabel_names() - - for dominfo in dominfos: - sec_lab = dominfo.get_security_label() - if not sec_lab: - continue - policytype, policy, vmlabel = sec_lab.split(":") - name = dominfo.getName() - - if policytype != cur_policytype or \ - policy != cur_policyname: - continue - - new_vmlabel = vmlabel - if vmlabel_map.has_key(vmlabel): - new_vmlabel = vmlabel_map[vmlabel] - if new_vmlabel not in polnew_vmlabels: - policytype = xsconstants.INVALID_POLICY_PREFIX + policytype - new_seclab = "%s:%s:%s" % \ - (policytype, new_policyname, new_vmlabel) - - domain_label_map[dominfo] = [ sec_lab, new_seclab ] - - if dominfo._stateGet() in (DOM_STATE_PAUSED, DOM_STATE_RUNNING): - compatible = __resources_compatible_with_vmlabel(new_acmpol, - dominfo, - new_vmlabel, - access_control, - is_policy_update=True) - log.info("Domain %s with new label '%s' can access its " - "resources? : %s" % - (name, new_vmlabel, str(compatible))) - log.info("VM labels in new policy: %s" % - new_acmpol.policy_get_virtualmachinelabel_names()) - if not compatible: - return (-xsconstants.XSERR_RESOURCE_ACCESS, "") - - rc, errors = hv_chg_policy(bin_pol, del_array, chg_array) - if rc == 0: - # Write the relabeled resources back into the file - dictio.dict_write(access_control, "resources", res_label_filename) - # Properly update all VMs to their new labels - for dominfo, labels in domain_label_map.items(): - sec_lab, new_seclab = labels - if sec_lab != new_seclab: - log.info("Updating domain %s to new label '%s'." % \ - (dominfo.getName(), new_seclab)) - # This better be working! - res = dominfo.set_security_label(new_seclab, - sec_lab, - new_acmpol, - cur_acmpol) - if res[0] != xsconstants.XSERR_SUCCESS: - log.info("ERROR: Could not chg label on domain %s: %s" % - (dominfo.getName(), - xsconstants.xserr2string(-int(res[0])))) - finally: - log.info("----------------------------------------------") - mapfile_unlock() - __resfile_lock.release() - - return rc, errors diff -r 23d8ee593895 -r b27aa79729f7 tools/python/xen/util/xsm/acm/acm.py --- a/tools/python/xen/util/xsm/acm/acm.py Tue Sep 04 14:48:29 2007 -0400 +++ b/tools/python/xen/util/xsm/acm/acm.py Sun Feb 15 05:51:24 2015 -0500 @@ -838,13 +838,28 @@ def set_resource_label_xapi(resource, re def is_resource_in_use(resource): - """ Investigate all running domains whether they use this device """ + """ + Domain-0 'owns' resources of type 'VLAN', the rest are owned by + the guests. + """ from xen.xend import XendDomain - dominfos = XendDomain.instance().list('all') lst = [] - for dominfo in dominfos: - if is_resource_in_use_by_dom(dominfo, resource): - lst.append(dominfo) + if resource.startswith('vlan'): + from xen.xend.XendXSPolicyAdmin import XSPolicyAdminInstance + curpol = XSPolicyAdminInstance().get_loaded_policy() + policytype, label, policy = get_res_label(resource) + if curpol and \ + policytype == xsconstants.ACM_POLICY_ID and \ + policy == curpol.get_name() and \ + label in curpol.policy_get_resourcelabel_names(): + # VLAN is in use. + lst.append(XendDomain.instance(). + get_vm_by_uuid(XendDomain.DOM0_UUID)) + else: + dominfos = XendDomain.instance().list('all') + for dominfo in dominfos: + if is_resource_in_use_by_dom(dominfo, resource): + lst.append(dominfo) return lst def devices_equal(res1, res2, mustexist=True): @@ -892,6 +907,10 @@ def get_domain_resources(dominfo): if sec_lab: resources[typ].append(sec_lab) else: + # !!! This should really get the label of the domain + # or at least a resource label that has the same STE type + # as the domain has + from xen.util.acmpolicy import ACM_LABEL_UNLABELED resources[typ].append("%s:%s:%s" % (xsconstants.ACM_POLICY_ID, active_policy, @@ -915,7 +934,8 @@ def resources_compatible_with_vmlabel(xs access_control = dictio.dict_read("resources", res_label_filename) except: - return False + # No labeled resources -> must be compatible + return True return __resources_compatible_with_vmlabel(xspol, dominfo, vmlabel, access_control) finally: @@ -924,12 +944,14 @@ def resources_compatible_with_vmlabel(xs def __resources_compatible_with_vmlabel(xspol, dominfo, vmlabel, - access_control): + access_control, + is_policy_update=False): """ Check whether the resources' labels are compatible with the given VM label. The access_control parameter provides a dictionary of the resource name to resource label mappings under which the evaluation should be done. + Call this only for a paused or running domain. """ def collect_labels(reslabels, s_label, polname): if len(s_label) != 3 or polname != s_label[1]: @@ -955,15 +977,23 @@ def __resources_compatible_with_vmlabel( elif key in [ 'vif' ]: for xapi_label in value: label = xapi_label.split(":") - if not collect_labels(reslabels, label, polname): - return False + from xen.util.acmpolicy import ACM_LABEL_UNLABELED + if not (is_policy_update and \ + label[2] == ACM_LABEL_UNLABELED): + if not collect_labels(reslabels, label, polname): + return False else: log.error("Unhandled device type: %s" % key) return False # Check that all resource labes have a common STE type with the # vmlabel - rc = xspol.policy_check_vmlabel_against_reslabels(vmlabel, reslabels) + if len(reslabels) > 0: + rc = xspol.policy_check_vmlabel_against_reslabels(vmlabel, reslabels) + else: + rc = True + log.info("vmlabel=%s, reslabels=%s, rc=%s" % + (vmlabel, reslabels, str(rc))) return rc; def set_resource_label(resource, policytype, policyref, reslabel, \ @@ -1176,7 +1206,7 @@ def change_acm_policy(bin_pol, del_array access_control = {} try: access_control = dictio.dict_read("resources", res_label_filename) - finally: + except: pass for key, labeldata in access_control.items(): if len(labeldata) == 2: @@ -1234,11 +1264,12 @@ def change_acm_policy(bin_pol, del_array compatible = __resources_compatible_with_vmlabel(new_acmpol, dominfo, new_vmlabel, - access_control) + access_control, + is_policy_update=True) log.info("Domain %s with new label '%s' can access its " "resources? : %s" % (name, new_vmlabel, str(compatible))) - log.info("VM labels in new domain: %s" % + log.info("VM labels in new policy: %s" % new_acmpol.policy_get_virtualmachinelabel_names()) if not compatible: return (-xsconstants.XSERR_RESOURCE_ACCESS, "") @@ -1252,11 +1283,16 @@ def change_acm_policy(bin_pol, del_array sec_lab, new_seclab = labels if sec_lab != new_seclab: log.info("Updating domain %s to new label '%s'." % \ - (sec_lab, new_seclab)) + (dominfo.getName(), new_seclab)) # This better be working! - dominfo.set_security_label(new_seclab, - sec_lab, - new_acmpol) + res = dominfo.set_security_label(new_seclab, + sec_lab, + new_acmpol, + cur_acmpol) + if res[0] != xsconstants.XSERR_SUCCESS: + log.info("ERROR: Could not chg label on domain %s: %s" % + (dominfo.getName(), + xsconstants.xserr2string(-int(res[0])))) finally: log.info("----------------------------------------------") mapfile_unlock()