--- xen-unstable.hg/tools/python/xen/util/acmpolicy.py | 1128 ++++++++++++ xen-unstable.hg/tools/python/xen/util/bootloader.py | 384 ++++ xen-unstable.hg/tools/python/xen/util/security.py | 781 +++++++- xen-unstable.hg/tools/python/xen/util/xsconstants.py | 102 + xen-unstable.hg/tools/python/xen/util/xspolicy.py | 57 xen-unstable.hg/tools/python/xen/util/xspolicyadmin.py | 320 +++ xen-unstable.hg/tools/python/xen/xend/XendAPI.py | 70 xen-unstable.hg/tools/python/xen/xend/XendConfig.py | 27 xen-unstable.hg/tools/python/xen/xend/XendDomainInfo.py | 182 + xen-unstable.hg/tools/python/xen/xend/XendError.py | 12 xen-unstable.hg/tools/python/xen/xend/XendVDI.py | 11 xen-unstable.hg/tools/python/xen/xend/XendXSPolicy.py | 240 ++ xen-unstable.hg/tools/python/xen/xend/server/blkif.py | 15 xen-unstable.hg/tools/security/policies/security_policy.xsd | 29 14 files changed, 3229 insertions(+), 129 deletions(-) Index: root/xen-unstable.hg/tools/python/xen/xend/XendAPI.py =================================================================== --- root.orig/xen-unstable.hg/tools/python/xen/xend/XendAPI.py +++ root/xen-unstable.hg/tools/python/xen/xend/XendAPI.py @@ -40,11 +40,13 @@ from XendPIFMetrics import XendPIFMetric from XendVMMetrics import XendVMMetrics from XendPIF import XendPIF from XendPBD import XendPBD +from XendXSPolicy import XendXSPolicy, XendACMPolicy from XendAPIConstants import * from xen.util.xmlrpclib2 import stringify from xen.util.blkif import blkdev_name_to_number +from xen.util import xsconstants AUTH_NONE = 'none' @@ -467,6 +469,8 @@ classes = { 'console' : valid_console, 'SR' : valid_sr, 'task' : valid_task, + 'XSPolicy' : valid_object("XSPolicy"), + 'ACMPolicy' : valid_object("ACMPolicy"), 'debug' : valid_debug, 'network' : valid_object("network"), 'PIF' : valid_object("PIF"), @@ -481,6 +485,8 @@ autoplug_classes = { 'VM_metrics' : XendVMMetrics, 'PBD' : XendPBD, 'PIF_metrics' : XendPIFMetrics, + 'XSPolicy' : XendXSPolicy, + 'ACMPolicy' : XendACMPolicy, } class XendAPI(object): @@ -1170,7 +1176,8 @@ class XendAPI(object): 'HVM_boot_params', 'platform', 'PCI_bus', - 'other_config'] + 'other_config', + 'security_label'] VM_methods = [('clone', 'VM'), ('start', None), @@ -1230,7 +1237,8 @@ class XendAPI(object): 'HVM_boot_params', 'platform', 'PCI_bus', - 'other_config'] + 'other_config', + 'security_label'] def VM_get(self, name, session, vm_ref): return xen_api_success( @@ -1595,7 +1603,26 @@ class XendAPI(object): if dom: return xen_api_success([dom.get_uuid()]) return xen_api_success([]) - + + def VM_get_security_label(self, session, vm_ref): + dom = XendDomain.instance().get_vm_by_uuid(vm_ref) + if not dom: + return xen_api_error(['VM_HANDLE_INVALID', vm_ref]) + label = dom.get_security_label() + return xen_api_success(label) + + def VM_set_security_label(self, session, vm_ref, sec_label, old_label): + dom = XendDomain.instance().get_vm_by_uuid(vm_ref) + if not dom: + return xen_api_error(['VM_HANDLE_INVALID', vm_ref]) + (rc, errors, oldlabel, new_ssidref) = \ + dom.set_security_label(sec_label, old_label) + if rc < 0: + return xen_api_error(['XSERROR', xsconstants.xserr2string(-rc)]) + if rc == 0: + rc = new_ssidref + return xen_api_success(rc) + def VM_create(self, session, vm_struct): xendom = XendDomain.instance() domuuid = XendTask.log_progress(0, 100, @@ -1649,6 +1676,7 @@ class XendAPI(object): 'domid': domid is None and -1 or domid, 'is_control_domain': xeninfo.info['is_control_domain'], 'metrics': xeninfo.get_metrics(), + 'security_label': xeninfo.get_security_label(), 'crash_dumps': [] } return xen_api_success(record) @@ -1935,7 +1963,8 @@ class XendAPI(object): 'runtime_properties'] VIF_attr_rw = ['device', 'MAC', - 'MTU'] + 'MTU', + 'security_label'] VIF_attr_inst = VIF_attr_rw @@ -2037,7 +2066,10 @@ class XendAPI(object): except Exception, exn: log.exception(exn) return xen_api_success({}) - + + def VIF_get_security_label(self, session, vif_ref): + return self._VIF_get(vif_ref, 'security_label') + # Xen API: Class VIF_metrics # ---------------------------------------------------------------- @@ -2081,7 +2113,8 @@ class XendAPI(object): 'virtual_size', 'sharable', 'read_only', - 'other_config'] + 'other_config', + 'security_label'] VDI_attr_inst = VDI_attr_ro + VDI_attr_rw VDI_methods = [('destroy', None)] @@ -2189,13 +2222,24 @@ class XendAPI(object): xennode = XendNode.instance() return xen_api_success(xennode.get_vdi_by_name_label(name)) + def VDI_set_security_label(self, session, vdi_ref, sec_lab, old_lab): + vdi = XendNode.instance().get_vdi_by_uuid(vdi_ref) + rc = vdi.set_security_label(sec_lab, old_lab) + if rc < 0: + return xen_api_error(['XSERROR', xsconstants.xserr2string(-rc)]) + return xen_api_success(rc) + + def VDI_get_security_label(self, session, vdi_ref): + vdi = XendNode.instance().get_vdi_by_uuid(vdi_ref) + return xen_api_success(vdi.get_security_label()) # Xen API: Class VTPM # ---------------------------------------------------------------- VTPM_attr_rw = [ ] VTPM_attr_ro = ['VM', - 'backend'] + 'backend', + 'runtime_properties' ] VTPM_attr_inst = VTPM_attr_rw @@ -2273,6 +2317,18 @@ class XendAPI(object): vtpms = reduce(lambda x, y: x + y, vtpms) return xen_api_success(vtpms) + def VTPM_get_runtime_properties(self, _, vtpm_ref): + xendom = XendDomain.instance() + dominfo = xendom.get_vm_with_dev_uuid('vtpm', vtpm_ref) + device = dominfo.get_dev_config_by_uuid('vtpm', vtpm_ref) + + try: + device_sxps = dominfo.getDeviceSxprs('vtpm') + device_dict = dict(device_sxps[0][1]) + return xen_api_success(device_dict) + except: + return xen_api_success({}) + # Xen API: Class console # ---------------------------------------------------------------- Index: root/xen-unstable.hg/tools/python/xen/xend/XendConfig.py =================================================================== --- root.orig/xen-unstable.hg/tools/python/xen/xend/XendConfig.py +++ root/xen-unstable.hg/tools/python/xen/xend/XendConfig.py @@ -157,6 +157,7 @@ XENAPI_CFG_TYPES = { 'platform': dict, 'tools_version': dict, 'other_config': dict, + 'security_label': str, } # List of legacy configuration keys that have no equivalent in the @@ -419,9 +420,9 @@ class XendConfig(dict): self._memory_sanity_check() self['cpu_time'] = dominfo['cpu_time']/1e9 - # TODO: i don't know what the security stuff expects here if dominfo.get('ssidref'): self['security'] = [['ssidref', dominfo['ssidref']]] + self['shutdown_reason'] = dominfo['shutdown_reason'] # parse state into Xen API states @@ -612,8 +613,27 @@ class XendConfig(dict): except ValueError, e: raise XendConfigError('cpus = %s: %s' % (cfg['cpus'], e)) - if 'security' in cfg and isinstance(cfg['security'], str): - cfg['security'] = sxp.from_string(cfg['security']) + if 'security' in cfg and not cfg.get('security_label'): + secinfo = cfg['security'] + if isinstance(secinfo, list): + # This list is formatted like this: + # [['access_control', ['policy', 'xm-test'],['label', 'red']], + # ['ssidref', 196611]] + policy = "" + label = "" + from xen.util import xsconstants + policytype = xsconstants.ACM_POLICY_ID + for idx in range(0, len(secinfo)): + if secinfo[idx][0] == "access_control": + for aidx in range(1, len(secinfo[idx])): + if secinfo[idx][aidx][0] == "policy": + policy = secinfo[idx][aidx][1] + if secinfo[idx][aidx][0] == "label": + label = secinfo[idx][aidx][1] + if label != "" and policy != "": + cfg['security_label'] = "%s:%s:%s" % \ + (policytype, policy, label) + del cfg['security'] old_state = sxp.child_value(sxp_cfg, 'state') if old_state: @@ -756,7 +776,6 @@ class XendConfig(dict): self[sxp_arg] = val _set_cfg_if_exists('shadow_memory') - _set_cfg_if_exists('security') _set_cfg_if_exists('features') _set_cfg_if_exists('on_xend_stop') _set_cfg_if_exists('on_xend_start') Index: root/xen-unstable.hg/tools/python/xen/xend/XendDomainInfo.py =================================================================== --- root.orig/xen-unstable.hg/tools/python/xen/xend/XendDomainInfo.py +++ root/xen-unstable.hg/tools/python/xen/xend/XendDomainInfo.py @@ -821,6 +821,9 @@ class XendDomainInfo: else: f('image/%s' % n, v) + if self.info.has_key('security_label'): + f('security_label', self.info['security_label']) + to_store.update(self._vcpuDomDetails()) log.debug("Storing domain details: %s", scrub_password(to_store)) @@ -991,9 +994,6 @@ class XendDomainInfo: log.info("Set VCPU count on domain %s to %d", self.info['name_label'], vcpus) - def getLabel(self): - return security.get_security_info(self.info, 'label') - def getMemoryTarget(self): """Get this domain's target memory size, in KB.""" return self.info['memory_dynamic_max'] / 1024 @@ -1432,11 +1432,20 @@ class XendDomainInfo: # allocation of 1MB. We free up 2MB here to be on the safe side. balloon.free(2*1024) # 2MB should be plenty - self.domid = xc.domain_create( - domid = 0, - ssidref = security.get_security_info(self.info, 'ssidref'), - handle = uuid.fromString(self.info['uuid']), - hvm = int(hvm)) + ssidref = security.calc_dom_ssidref_from_info(self.info) + if ssidref == 0 and security.on(): + raise VmError('VM is not properly labeled.') + + try: + self.domid = xc.domain_create( + domid = 0, + ssidref = ssidref, + handle = uuid.fromString(self.info['uuid']), + hvm = int(hvm)) + except Exception, e: + # may get here if due to ACM the operation is not permitted + if security.on: + raise VmError('Domain in conflict set with running domain?') if self.domid < 0: raise VmError('Creating domain failed: name=%s' % @@ -2088,13 +2097,20 @@ class XendDomainInfo: .physinfo_dict()['total_memory'] * 1024 #manually update ssidref / security fields - if security.on() and info.has_key('ssidref'): + if security.on(): if (info['ssidref'] != 0) and self.info.has_key('security'): - security_field = self.info['security'] - if not security_field: - #create new security element - self.info.update({'security': - [['ssidref', str(info['ssidref'])]]}) + if self.info.has_key('security_label'): + tmp = [] + sec_label = self.info['security_label'] + tmp = sec_label.split(":") + if len(tmp) == 3: + label = tmp[2] + policy = tmp[1] + self.info.update({'security': + [['access_control', + ['label', label], + ['policy', policy]], + ['ssidref', str(info['ssidref'])]]}) #ssidref field not used any longer if 'ssidref' in info: @@ -2179,7 +2195,143 @@ class XendDomainInfo: return self.info.get('tools_version', {}) def get_metrics(self): return self.metrics.get_uuid(); - + + + def get_security_label(self): + domid = self.getDomid() + + from xen.util.xspolicyadmin import XSPolicyAdminInstance + xspol = XSPolicyAdminInstance().get_loaded_policy() + + if domid == 0: + if xspol: + label = xspol.policy_get_domain_label_formatted(domid) + else: + label = "" + else: + label = self.info.get('security_label', '') + return label + + def set_security_label(self, seclab, old_seclab, xspol=None): + """ + Set the security label of a domain from its old to + a new value. + @param seclab New security label formatted in the form + :: + @param old_seclab The current security label that the + VM must have or "*" for 'any'. + @param xspol An optional policy under which this + update should be done. If not given, + then the current active policy is used. + @return Returns return code, a string with errors from + the hypervisor's operation, old label of the + domain + """ + rc = 0 + errors = "" + old_label = "" + new_ssidref = 0 + domid = self.getDomid() + res_labels = None + + if domid == None: + log.info("Relabeling dormant domain %s with new label '%s'" % + (self.getName(), seclab)) + else: + log.info("Relabeling domain %d with new label '%s'" % + (domid, seclab)) + + from xen.util.xspolicyadmin import XSPolicyAdminInstance + from xen.util import xsconstants + + state = self._stateGet() + # Relabel only HALTED or RUNNING or PAUSED domains + if domid != 0 and \ + state not in \ + [ DOM_STATE_HALTED, DOM_STATE_RUNNING, DOM_STATE_PAUSED, \ + DOM_STATE_SUSPENDED ]: + log.warn("Relabeling domain not possible in state '%s'" % + DOM_STATES[state]) + return (-xsconstants.XSERR_VM_WRONG_STATE, "", "", 0) + + # Remove security label. Works only for halted domains + if not seclab or seclab == "": + if state not in [ DOM_STATE_HALTED ]: + return (-xsconstants.XSERR_VM_WRONG_STATE, "", "", 0) + + if self.info.has_key('security_label'): + old_label = self.info['security_label'] + # Check label against expected one. + if old_seclab != "*": + if old_label != old_seclab: + return (-xsconstants.XSERR_BAD_LABEL, "", "", 0) + del self.info['security_label'] + xen.xend.XendDomain.instance().managed_config_save(self) + return (xsconstants.XSERR_SUCCESS, "", "", 0) + + tmp = seclab.split(":") + if len(tmp) != 3: + return (-xsconstants.XSERR_BAD_LABEL_FORMAT, "", "", 0) + typ, policy, label = tmp + + poladmin = XSPolicyAdminInstance() + if not xspol: + xspol = poladmin.get_policy_by_name(policy) + + if state in [ DOM_STATE_RUNNING, DOM_STATE_PAUSED ]: + #if domain is running or paused try to relabel in hypervisor + if not xspol: + return (-xsconstants.XSERR_POLICY_NOT_LOADED, "", "", 0) + + if typ != xspol.get_type_name() or \ + policy != xspol.get_name(): + return (-xsconstants.XSERR_BAD_LABEL, "", "", 0) + + if typ == xsconstants.ACM_POLICY_ID: + new_ssidref = xspol.vmlabel_to_ssidref(label) + if new_ssidref == xsconstants.INVALID_SSIDREF: + return (-xsconstants.XSERR_BAD_LABEL, "", "", 0) + + # Check that all used resources are accessible under the + # new label + if not security.resources_compatible_with_vmlabel(xspol, + self, label): + return (-xsconstants.XSERR_BAD_LABEL, "", "", 0) + + # relabel domain in the hypervisor + old_label = xspol.policy_get_domain_label_formatted(domid) + #Check label against expected one. + if old_seclab != "*": + if old_label != old_seclab: + return (-xsconstants.XSERR_BAD_LABEL, "", "", 0) + + rc, errors = security.relabel_domains([[domid, new_ssidref]]) + log.info("rc from relabeling in HV: %d" % rc) + else: + return (-xsconstants.XSERR_POLICY_TYPE_UNSUPPORTED, "", "", 0) + + if rc == 0: + # HALTED, RUNNING or PAUSED + if domid == 0: + if xspol: + ssidref = poladmin.set_domain0_bootlabel(xspol, label) + else: + return (-xsconstants.XSERR_POLICY_NOT_LOADED, "", "", 0) + else: + if self.info.has_key('security_label'): + old_label = self.info['security_label'] + # Check label against expected one, unless wildcard + if old_seclab != "*": + if old_label != old_seclab: + return (-xsconstants.XSERR_BAD_LABEL, "", "", 0) + + self.info['security_label'] = seclab + try: + xen.xend.XendDomain.instance().managed_config_save(self) + except: + pass + return (rc, errors, old_label, new_ssidref) + def get_on_shutdown(self): after_shutdown = self.info.get('actions_after_shutdown') if not after_shutdown or after_shutdown not in XEN_API_ON_NORMAL_EXIT: Index: root/xen-unstable.hg/tools/python/xen/xend/server/blkif.py =================================================================== --- root.orig/xen-unstable.hg/tools/python/xen/xend/server/blkif.py +++ root/xen-unstable.hg/tools/python/xen/xend/server/blkif.py @@ -73,10 +73,17 @@ class BlkifController(DevController): back['uuid'] = uuid if security.on(): - (label, ssidref, policy) = security.get_res_security_details(uname) - back.update({'acm_label' : label, - 'acm_ssidref': str(ssidref), - 'acm_policy' : policy}) + (label, ssidref, policy) = \ + security.get_res_security_details(uname) + domain_label = self.vm.get_security_label() + if domain_label: + rc = security.res_security_check_xapi(label, ssidref, policy, + domain_label) + if rc == 0: + raise VmError("VM's access to block device '%s' denied." % + uname) + else: + raise VmError("VM must have a security label.") devid = blkif.blkdev_name_to_number(dev) if devid is None: Index: root/xen-unstable.hg/tools/python/xen/xend/XendError.py =================================================================== --- root.orig/xen-unstable.hg/tools/python/xen/xend/XendError.py +++ root/xen-unstable.hg/tools/python/xen/xend/XendError.py @@ -174,6 +174,17 @@ class NetworkError(XendAPIError): def __str__(self): return 'NETWORK_ERROR: %s %s' % (self.error, self.network) + +class XenSecurityError(XendAPIError): + def __init__(self, error): + XendAPIError.__init__(self) + self.error = error + + def get_api_error(self): + return ['XENSECURITY_ERROR', self.error] + + def __str__(self): + return 'XENSECURITY_ERROR: %s' % (self.error) XEND_ERROR_AUTHENTICATION_FAILED = ('ELUSER', 'Authentication Failed') XEND_ERROR_SESSION_INVALID = ('EPERMDENIED', 'Session Invalid') @@ -188,4 +199,5 @@ XEND_ERROR_VIF_INVALID = ('EVI XEND_ERROR_VTPM_INVALID = ('EVTPMINVALID', 'VTPM Invalid') XEND_ERROR_VDI_INVALID = ('EVDIINVALID', 'VDI Invalid') XEND_ERROR_SR_INVALID = ('ESRINVALID', 'SR Invalid') +XEND_ERROR_XSPOLICY_INVALID = ('EXSPOLICYINVALID', 'XS Invalid') XEND_ERROR_TODO = ('ETODO', 'Lazy Programmer Error') Index: root/xen-unstable.hg/tools/python/xen/util/xsconstants.py =================================================================== --- /dev/null +++ root/xen-unstable.hg/tools/python/xen/util/xsconstants.py @@ -0,0 +1,102 @@ +#============================================================================ +# 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) 2007 International Business Machines Corp. +# Author: Stefan Berger +#============================================================================ + +XS_INST_NONE = 0 +XS_INST_BOOT = (1 << 0) +XS_INST_LOAD = (1 << 1) + +XS_POLICY_NONE = 0 +XS_POLICY_ACM = (1 << 0) + +# Some internal variables used by the Xen-API +ACM_LABEL_VM = (1 << 0) +ACM_LABEL_RES = (1 << 1) + +# Base for XS error codes for collision avoidance with other error codes +XSERR_BASE = 0x1000 + +# XS error codes as used by the Xen-API +XSERR_SUCCESS = 0 +XSERR_GENERAL_FAILURE = 1 + XSERR_BASE +XSERR_BAD_XML = 2 + XSERR_BASE # XML is wrong (not according to schema) +XSERR_XML_PROCESSING = 3 + XSERR_BASE +XSERR_POLICY_INCONSISTENT = 4 + XSERR_BASE # i.e., bootstrap name not a VM label +XSERR_FILE_ERROR = 5 + XSERR_BASE +XSERR_BAD_RESOURCE_FORMAT = 6 + XSERR_BASE # badly formatted resource +XSERR_BAD_LABEL_FORMAT = 7 + XSERR_BASE +XSERR_RESOURCE_NOT_LABELED = 8 + XSERR_BASE +XSERR_RESOURCE_ALREADY_LABELED = 9 + XSERR_BASE +XSERR_WRONG_POLICY_TYPE = 10 + XSERR_BASE +XSERR_BOOTPOLICY_INSTALLED = 11 + XSERR_BASE +XSERR_NO_DEFAULT_BOOT_TITLE = 12 + XSERR_BASE +XSERR_POLICY_LOAD_FAILED = 13 + XSERR_BASE +XSERR_POLICY_LOADED = 14 + XSERR_BASE +XSERR_POLICY_TYPE_UNSUPPORTED = 15 + XSERR_BASE +XSERR_BAD_CONFLICTSET = 16 + XSERR_BASE +XSERR_RESOURCE_IN_USE = 17 + XSERR_BASE +XSERR_BAD_POLICY_NAME = 18 + XSERR_BASE +XSERR_VERSION_PREVENTS_UPDATE = 19 + XSERR_BASE +XSERR_BAD_LABEL = 20 + XSERR_BASE +XSERR_VM_WRONG_STATE = 21 + XSERR_BASE +XSERR_POLICY_NOT_LOADED = 22 + XSERR_BASE +XSERR_RESOURCE_ACCESS = 23 + XSERR_BASE +XSERR_HV_OP_FAILED = 24 + XSERR_BASE +XSERR_BOOTPOLICY_INSTALL_ERROR = 25 + XSERR_BASE +XSERR_LAST = 25 + XSERR_BASE ## KEEP LAST + +XSERR_MESSAGES = [ + '', + 'General Failure', + 'XML is malformed', + 'Error while processing XML', + 'Policy has inconsistencies', + 'A file access error occurred', + 'The resource format is not valid', + 'The label format is not valid', + 'The resource is not labeld', + 'The resource is already labeld', + 'The policy type is wrong', + 'The system boot policy is installed', + 'Could not find the default boot title', + 'Loading of the policy failed', + 'The policy is loaded', + 'The policy type is unsupported', + 'There is a bad conflict set', + 'The resource is in use', + 'The policy has an invalid name', + 'The version of the policy prevents an update', + 'The label is bad', + 'Operation not premittend - the VM is in the wrong state', + 'The policy is not loaded', + 'Error accessing resource', + 'Operation failed in hypervisor', + 'Boot policy installation error' +] + +def xserr2string(err): + if err == XSERR_SUCCESS: + return "Success" + if err >= XSERR_GENERAL_FAILURE and \ + err <= XSERR_LAST: + return XSERR_MESSAGES[err - XSERR_BASE] + return "Unknown XSERR code '0x%x'." % err + +# Policy identifiers used in labels +ACM_POLICY_ID = "ACM" + +INVALID_SSIDREF = 0xFFFFFFFF Index: root/xen-unstable.hg/tools/python/xen/xend/XendVDI.py =================================================================== --- root.orig/xen-unstable.hg/tools/python/xen/xend/XendVDI.py +++ root/xen-unstable.hg/tools/python/xen/xend/XendVDI.py @@ -23,6 +23,7 @@ import os from xen.util.xmlrpclib2 import stringify from xmlrpclib import dumps, loads +from xen.util import security KB = 1024 MB = 1024 * 1024 @@ -160,6 +161,16 @@ class XendVDI(AutoSaveObject): def get_location(self): raise NotImplementedError() + + def set_security_label(self, sec_lab, old_lab): + image = self.get_location() + from xen.xend.XendLogging import log + return security.set_resource_label_xapi(image, sec_lab, old_lab) + + def get_security_label(self): + image = self.get_location() + from xen.xend.XendLogging import log + return security.get_resource_label_xapi(image) class XendQCoWVDI(XendVDI): Index: root/xen-unstable.hg/tools/python/xen/util/security.py =================================================================== --- root.orig/xen-unstable.hg/tools/python/xen/util/security.py +++ root/xen-unstable.hg/tools/python/xen/util/security.py @@ -15,17 +15,22 @@ # Copyright (C) 2006 International Business Machines Corp. # Author: Reiner Sailer # Author: Bryan D. Payne +# Author: Stefan Berger #============================================================================ import commands import logging -import sys, os, string, re -import traceback -import shutil +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.util import dictio +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" @@ -60,6 +65,10 @@ policy_name_re = re.compile(".*[chwall|s #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 @@ -75,7 +84,6 @@ class ACMError(Exception): def err(msg): """Raise ACM exception. """ - sys.stderr.write("ACMError: " + msg + "\n") raise ACMError(msg) @@ -83,6 +91,13 @@ def err(msg): active_policy = None +def mapfile_lock(): + __mapfile_lock.acquire() + +def mapfile_unlock(): + __mapfile_lock.release() + + def refresh_security_policy(): """ retrieves security policy @@ -106,6 +121,39 @@ def on(): 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' not supported." % 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 0 + raise VmError("security.calc_dom_ssidref_from_info: info of type '%s'" + "not supported." % type(info)) # Assumes a 'security' info [security access_control ...] [ssidref ...] def get_security_info(info, field): @@ -146,7 +194,6 @@ def get_security_info(info, field): return None - def get_security_printlabel(info): """retrieves printable security label from self.info['security']), preferably the label name and otherwise (if label is not specified @@ -250,32 +297,37 @@ def ssidref2label(ssidref_var): else: err("Instance type of ssidref not supported (must be of type 'str' or 'int')") - (primary, secondary, f, pol_exists) = getmapfile(None) - if not f: - if (pol_exists): - err("Mapping file for policy \'" + policyname + "\' 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 = [] + try: + mapfile_lock() - 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() + (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): @@ -297,7 +349,7 @@ def ssidref2label(ssidref_var): -def label2ssidref(labelname, policyname, type): +def label2ssidref(labelname, policyname, typ): """ returns ssidref corresponding to labelname; maps current policy to default directory @@ -307,42 +359,51 @@ def label2ssidref(labelname, policyname, err("Cannot translate labels for \'" + policyname + "\' policy.") allowed_types = ['ANY'] - if type == 'dom': + if typ == 'dom': allowed_types.append('VM') - elif type == 'res': + elif typ == 'res': allowed_types.append('RES') else: err("Invalid type. Must specify 'dom' or 'res'.") - (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 (type == 'res') and (primary == "CHWALL") and (len(pri_ssid) == 0): - pri_ssid.append(NULL_SSIDREF) - elif (type == '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] + 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): @@ -381,8 +442,9 @@ def refresh_ssidref(config): err("Illegal field in access_control") #verify policy is correct if active_policy != policyname: - err("Policy \'" + policyname + "\' in label does not match active policy \'" - + active_policy +"\'!") + err("Policy \'" + str(policyname) + + "\' in label does not match active policy \'" + + str(active_policy) +"\'!") new_ssidref = label2ssidref(labelname, policyname, 'dom') if not new_ssidref: @@ -470,6 +532,25 @@ def get_decision(arg1, arg2): err("Cannot determine decision (Invalid parameter).") +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, "."), "/") @@ -480,8 +561,6 @@ def make_policy(policy_name): 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, "."), "/") @@ -538,8 +617,8 @@ def list_labels(policy_name, condition): def get_res_label(resource): - """Returns resource label information (label, policy) if it exists. - Otherwise returns null label and policy. + """Returns resource label information (policytype, label, policy) if + it exists. Otherwise returns null label and policy. """ def default_res_label(): ssidref = NULL_SSIDREF @@ -547,23 +626,19 @@ def get_res_label(resource): label = ssidref2label(ssidref) else: label = None - return (label, 'NULL') + return (xsconstants.ACM_POLICY_ID, 'NULL', label) - (label, policy) = default_res_label() - # load the resource label file - res_label_cache = {} - try: - res_label_cache = dictio.dict_read("resources", res_label_filename) - except: - log.info("Resource label file not found.") - return default_res_label() - - # find the resource information - if res_label_cache.has_key(resource): - (policy, label) = res_label_cache[resource] + 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 (label, policy) + return (policytype, label, policy) def get_res_security_details(resource): @@ -582,7 +657,7 @@ def get_res_security_details(resource): (label, ssidref, policy) = default_security_details() # find the entry associated with this resource - (label, policy) = get_res_label(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() @@ -596,8 +671,29 @@ def get_res_security_details(resource): 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): +def unify_resname(resource, mustexist=True): """Makes all resource locations absolute. In case of physical resources, '/dev/' is added to local file names""" @@ -606,28 +702,53 @@ def unify_resname(resource): # sanity check on resource name try: - (type, resfile) = resource.split(":", 1) + (typ, resfile) = resource.split(":", 1) except: err("Resource spec '%s' contains no ':' delimiter" % resource) - if type == "tap": + if typ == "tap": try: (subtype, resfile) = resfile.split(":") except: err("Resource spec '%s' contains no tap subtype" % resource) - if type in ["phy", "tap"]: + 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 - if (not resfile.startswith("/")) or (not os.path.exists(resfile)): - err("Invalid resource.") + #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 type == "tap": - type = type + ":" + subtype - resource = type + ":" + resfile + if typ == "tap": + typ = typ + ":" + subtype + resource = typ + ":" + resfile return resource @@ -662,9 +783,483 @@ def res_security_check(resource, domain_ else: # Note, we can't canonicalise the resource here, because people using # xm without ACM are free to use relative paths. - (label, policy) = get_res_label(resource) + (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 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) + 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: + return -xsconstants.XSERR_WRONG_POLICY_TYPE + return set_resource_label(resource, typ, policyref, label, olabel) + +def is_resource_in_use(resource): + """ Investigate all running domains whether they use this device """ + 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) + return lst + +def is_resource_in_use_by_dom(dominfo, resource): + 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 dev[1]['uname'] == resource: + 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 or + ... + """ + resources = { 'vbd' : [], 'tap' : []} + 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']) + + 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: + return False + 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): + """ + 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. + """ + resources = get_domain_resources(dominfo) + reslabels = [] # all resource labels + polname = xspol.get_name() + for key in resources.keys(): + for res in resources[key]: + try: + tmp = access_control[res] + if len(tmp) != 3: + return False + + if polname != tmp[1]: + return False + label = tmp[2] + if not label in reslabels: + reslabels.append(label) + except: + return False + # Check that all resource labes have a common STE type with the + # vmlabel + rc = xspol.policy_check_vmlabel_against_reslabels(vmlabel, reslabels) + 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 + - the resource is in use and the domain that is using it + has a VMlabel where one of its STE types is contained in + the STE type of the new resource + @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 + + # IF no policy is loaded, anything may be labeled + from xen.util.xspolicyadmin import XSPolicyAdminInstance + poladmin = XSPolicyAdminInstance() + xspol = poladmin.get_policy_by_name(policyref) + + if xspol: + domains = is_resource_in_use(resource) + if len(domains) > 0: + # Resource is in use. Make sure device remains accessible + for dominfo in domains: + vmlabel_xapi = dominfo.get_security_label() + tmp = vmlabel_xapi.split(":") + if len(tmp) != 3: + return -xsconstants.XSERR_GENERAL_FAILURE + vmlabel = tmp[2] + if not xspol.policy_check_vmlabel_against_reslabels(vmlabel, + [ reslabel ]): + return -xsconstants.XSERR_RESOURCE_ACCESS + + try: + __resfile_lock.acquire() + access_control = {} + try: + access_control = dictio.dict_read("resources", res_label_filename) + except: + pass + if oreslabel and 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): + """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) + """ + return set_resource_label(resource, "", "", "", "*") + +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) + finally: + 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 = "INV_" + 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 = "INV_" + 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) + log.info("Domain %s can access its resource? : %s" % + (name, str(compatible))) + 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'." % \ + (new_seclab, sec_lab)) + # This better be working! + dominfo.set_security_label(new_seclab, + sec_lab, + new_acmpol) + finally: + log.info("----------------------------------------------") + mapfile_unlock() + __resfile_lock.release() + + return rc, errors Index: root/xen-unstable.hg/tools/python/xen/util/xspolicyadmin.py =================================================================== --- /dev/null +++ root/xen-unstable.hg/tools/python/xen/util/xspolicyadmin.py @@ -0,0 +1,320 @@ +#============================================================================ +# 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,2007 International Business Machines Corp. +# Author: Stefan Berger +#============================================================================ +import os +import shutil + +from xml.dom import minidom, Node +from xen.xend.XendLogging import log +from xen.xend import uuid +from xen.util import security +from xen.util import xsconstants +from xen.util import dictio +from xen.util import bootloader +from xen.util.xspolicy import XSPolicy +from xen.util.acmpolicy import ACMPolicy +from xen.util.xserror import XSError + +XS_MANAGED_POLICIES_FILE = "/etc/xen/acm-security/policies/managed_policies" + +class XSPolicyAdmin: + """ The class that handles the managed policies in the system. + Handles adding and removing managed policies. All managed + policies are handled using a reference (UUID) which is + assigned to the policy by this class. + """ + + def __init__(self, maxpolicies): + """ Create a management class for managing the system's + policies. + + @param maxpolicies: The max. number of policies allowed + on the system (currently '1') + """ + self.maxpolicies = maxpolicies + try: + self.policies = dictio.dict_read("managed_policies", + XS_MANAGED_POLICIES_FILE) + except Exception, e: + self.policies = {} + + self.xsobjs = {} + for ref, data in self.policies.items(): + name = data[0] + typ = data[1] + try: + if typ == xsconstants.ACM_POLICY_ID: + self.xsobjs[ref] = ACMPolicy(name=name, ref=ref) + from xen.xend.XendXSPolicy import XendXSPolicy + from xen.xend.XendXSPolicy import XendACMPolicy + XendXSPolicy({}, ref) + XendACMPolicy({}, ref) + else: + del self.policies[ref] + except Exception, e: + log.error("XSPolicyAdmin: Could not find policy '%s': %s" % + (name, str(e))) + del self.policies[ref] + log.debug("XSPolicyAdmin: Known policies: %s" % self.policies) + + def isXSEnabled(self): + """ Check whether 'security' is enabled on this system. + This currently only checks for ACM-enablement. + """ + rc = 0 + if security.on(): + rc |= xsconstants.XS_POLICY_ACM + return rc + + def add_acmpolicy_to_system(self, xmltext, flags, overwrite): + """ Add an ACM policy's xml representation to the system. The + policy will automatically be compiled + flags: + XS_INST_BOOT : make policy the one to boot the system with + by default; if there's a policy already installed, + refuse to install this policy unless its one with + the same name + XS_INST_LOAD : load the policy immediately; if this does not work + refuse to install this policy + overwrite: + If any policy is installed and this is False, refuse to install + this policy + If flags is True, then any existing policy will be removed from + the system and the new one will be installed + """ + errors = "" + loadedpol = self.get_loaded_policy() + if loadedpol: + # This is meant as an update to a currently loaded policy + rc, errors = loadedpol.update(xmltext) + if rc == 0: + irc = self.activate_xspolicy(loadedpol, flags) + return (loadedpol, rc, errors) + + try: + dom = minidom.parseString(xmltext) + except: + raise XSError(-xsconstants.XSERR_BAD_XML) + + ref = uuid.createString() + + acmpol = ACMPolicy(dom=dom, ref=ref) + + #First some basic tests that do not modify anything: + title = bootloader.get_default_title() + bootpolicies = bootloader.get_boot_policies() + + if int(flags) & xsconstants.XS_INST_BOOT: + if int(title) != -1 and \ + bootpolicies.has_key(title) and \ + bootpolicies[title] != acmpol.get_filename(".bin",""): + raise XSError(-xsconstants.XSERR_BOOTPOLICY_INSTALLED) + + if not overwrite and len(self.policies) >= self.maxpolicies: + raise XSError(-xsconstants.XSERR_BOOTPOLICY_INSTALLED) + + + if overwrite: + #This should only give one key since only one policy is + #allowed. + keys = self.policies.keys() + for k in keys: + rc = self.rm_policy_from_system(k, force=overwrite) + if rc != xsconstants.XSERR_SUCCESS: + raise XSError(rc) + + rc = acmpol.compile() + if rc != 0: + raise XSError(rc) + + if int(flags) & xsconstants.XS_INST_LOAD: + rc = acmpol.loadintohv() + if rc != 0: + raise XSError(rc) + + if int(flags) & xsconstants.XS_INST_BOOT: + rc = self.make_boot_policy(acmpol) + if rc != 0: + # If it cannot be installed due to unsupported + # bootloader, let it be ok. + pass + + if dom: + new_entry = { ref : tuple([acmpol.get_name(), + xsconstants.ACM_POLICY_ID]) } + self.policies.update(new_entry) + self.xsobjs[ref] = acmpol + dictio.dict_write(self.policies, + "managed_policies", + XS_MANAGED_POLICIES_FILE) + return (acmpol, xsconstants.XSERR_SUCCESS, errors) + + def make_boot_policy(self, acmpol): + spolfile = acmpol.get_filename(".bin") + dpolfile = "/boot/" + acmpol.get_filename(".bin","",dotted=True) + if not os.path.isfile(spolfile): + log.error("binary policy file does not exist.") + return -xsconstants.XSERR_FILE_ERROR + try: + shutil.copyfile(spolfile, dpolfile) + except: + return -xsconstants.XSERR_FILE_ERROR + + try: + title = bootloader.get_default_title() + success = bootloader.set_boot_policy(title, + acmpol.get_filename(".bin","",dotted=True)) + if success != True: + return xsconstants.XSERR_BOOTPOLICY_INSTALL_ERROR + except: + return xsconstants.XSERR_FILE_ERROR + return xsconstants.XSERR_SUCCESS + + def activate_xspolicy(self, xspol, flags): + rc = xsconstants.XSERR_SUCCESS + if flags & xsconstants.XS_INST_LOAD: + rc = xspol.loadintohv() + if rc == xsconstants.XSERR_SUCCESS and \ + flags & xsconstants.XS_INST_BOOT: + rc = self.make_boot_policy(xspol) + if rc == xsconstants.XSERR_SUCCESS: + rc = flags + return rc + + def rm_policy_from_system(self, ref, force=False): + if self.policies.has_key(ref): + acmpol = self.xsobjs[ref] + rc = acmpol.destroy() + if rc == xsconstants.XSERR_SUCCESS or force: + del self.policies[ref] + del self.xsobjs[ref] + dictio.dict_write(self.policies, + "managed_policies", + XS_MANAGED_POLICIES_FILE) + rc = xsconstants.XSERR_SUCCESS + return rc + + def rm_bootpolicy(self): + """ Remove any (ACM) boot policy from the grub configuration file + """ + rc = 0 + title = bootloader.get_default_title() + if int(title) != -1: + polnames = [] + for (k, v) in self.xsobjs.items(): + polnames.append(v.get_filename(".bin","",dotted=True)) + bootloader.rm_policy_from_boottitle(title, polnames) + else: + rc = -xsconstants.XSERR_NO_DEFAULT_BOOT_TITLE + return rc + + def get_policy_flags(self, acmpol): + """ Get the currently active flags of a policy, i.e., whether the + system is using this policy as its boot policy for the default + boot title. + """ + flags = 0 + + filename = acmpol.get_filename(".bin","", dotted=True) + title = bootloader.get_default_title() + policies = bootloader.get_boot_policies() + + if policies.has_key(title): + polfile = policies[title] + if polfile == filename or \ + "/"+polfile == filename: + flags |= xsconstants.XS_INST_BOOT + + # Is this policy the active one? + if acmpol.isloaded(): + flags |= xsconstants.XS_INST_LOAD + return flags + + def get_policies(self): + """ Get all managed policies. """ + return self.xsobjs.values() + + def get_policies_refs(self): + """ Get all managed policies' references. """ + return self.xsobjs.keys() + + def has_ref(self, ref): + """ Check whether there is a policy with the given reference """ + return self.xsobjs.has_key(ref) + + def policy_from_ref(self, ref): + """ Get the policy's object given its reference """ + if ref in self.xsobjs.keys(): + return self.xsobjs[ref] + return None + + def ref_from_polname(self, polname): + """ Get the reference of the policy given its name """ + ref = None + for (k, v) in self.xsobjs.items(): + if v.get_name() == polname: + ref = k + break + return ref + + def lock_policy(self, ref): + """ get exclusive access to a policy """ + self.xsobjs[ref].grab_lock() + + def unlock_policy(self, ref): + """ release exclusive access to a policy """ + self.xsobjs[ref].unlock() + + def get_loaded_policy(self): + for pol in self.xsobjs.values(): + if pol.isloaded(): + return pol + return None + + def get_policy_by_name(self, name): + for pol in self.xsobjs.values(): + if pol.get_name() == name: + return pol + return None + + def get_domain0_bootlabel(self): + """ Get the domain0 bootlabel from the default boot title """ + title = "" + def_title = bootloader.get_default_title() + line = bootloader.get_kernel_val(def_title, "ssidref") + if line: + parms = line.split(":",1) + if len(parms) > 1: + title = parms[1] + return title + + def set_domain0_bootlabel(self, xspol, label): + """ Set the domain-0 bootlabel under the given policy """ + return xspol.set_vm_bootlabel(label) + + def rm_domain0_bootlabel(self): + """ Remove the domain-0 bootlabel from the default boot title """ + def_title = bootloader.get_default_title() + return bootloader.set_kernel_attval(def_title, "ssidref", None) + +poladmin = None + +def XSPolicyAdminInstance(maxpolicies=1): + global poladmin + if poladmin == None: + poladmin = XSPolicyAdmin(maxpolicies) + return poladmin Index: root/xen-unstable.hg/tools/python/xen/util/bootloader.py =================================================================== --- /dev/null +++ root/xen-unstable.hg/tools/python/xen/util/bootloader.py @@ -0,0 +1,384 @@ +#============================================================================ +# 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,2007 International Business Machines Corp. +# Author: Stefan Berger +#============================================================================ + +import re +import os, stat +import tempfile +import shutil +import threading + +from xen.xend.XendLogging import log + + +__bootfile_lock = threading.RLock() + +# +# Functions related to grub boot loader and adding a +# binary policy to the bootloader's default entry +# + +def get_bootfile(): + """ Get the name of the bootfile """ + boot_file = "/boot/grub/grub.conf" + alt_boot_file = "/boot/grub/menu.lst" + + if not os.path.isfile(boot_file): + #take alternate boot file instead + boot_file = alt_boot_file + + #follow symlink since menue.lst might be linked to grub.conf + if not os.path.exists(boot_file): + raise IOError("Boot file \'%s\' not found." % boot_file) + + if stat.S_ISLNK(os.lstat(boot_file)[stat.ST_MODE]): + new_name = os.readlink(boot_file) + if new_name[0] == "/": + boot_file = new_name + else: + path = boot_file.split('/') + path[len(path)-1] = new_name + boot_file = '/'.join(path) + if not os.path.exists(boot_file): + raise IOError("Boot file \'%s\' not found." % boot_file) + return boot_file + +def get_titles(): + """ Get the names of all boot titles in the grub config file + @rtype: list + @return: list of names of available boot titles + """ + title_re = re.compile("\s*title\s", re.IGNORECASE) + titles = [] + try: + boot_file = get_bootfile() + except: + return [] + try: + __bootfile_lock.acquire() + grub_fd = open(boot_file) + for line in grub_fd: + if title_re.match(line): + line = line.rstrip().lstrip() + titles.append(line.lstrip('title').lstrip()) + finally: + __bootfile_lock.release() + return titles + +def get_default_title(): + """ Get the index (starting with 0) of the default boot title + This number is read from the grub configuration file. + In case of an error '-1' is returned + @rtype: int + @return: the index of the default boot title + """ + def_re = re.compile("default", re.IGNORECASE) + default = -1 + try: + boot_file = get_bootfile() + except: + return default + try: + __bootfile_lock.acquire() + grub_fd = open(boot_file) + for line in grub_fd: + line = line.rstrip() + if def_re.match(line): + line = line.rstrip() + line = line.lstrip("default=") + default = int(line) + break + finally: + __bootfile_lock.release() + return default + +def get_boot_policies(): + """ Get a dictionary of policies that the system is booting with. + @rtype: dict + @return: dictionary of boot titles where the keys are the + indices of the boot titles + """ + title_re = re.compile("\s*title\s", re.IGNORECASE) + module_re = re.compile("\smodule\s", re.IGNORECASE) + policy_re = re.compile(".*\.bin", re.IGNORECASE) + policies = {} + within_title = 0 + idx = -1 + try: + boot_file = get_bootfile() + except: + return policies + try: + __bootfile_lock.acquire() + + grub_fd = open(boot_file) + for line in grub_fd: + if title_re.match(line): + within_title = 1 + idx = idx + 1 + if within_title and module_re.match(line): + if policy_re.match(line): + start = line.find("module") + pol = line[start+6:len(line)] + pol = pol.lstrip().rstrip() + if pol[0] == '/': + pol = pol[1:len(pol)] + policies[idx] = pol + finally: + __bootfile_lock.release() + return policies + +def add_boot_policy(index, binpolname): + """ Add the binary policy for automatic loading when + booting the system. Add it to the boot title at index + 'index'. + """ + ctr = 0 + module_line = "" + title_re = re.compile("\s*title\s", re.IGNORECASE) + module_re = re.compile("\smodule\s", re.IGNORECASE) + within_title = 0 + found = False + try: + boot_file = get_bootfile() + except: + return False + try: + __bootfile_lock.acquire() + grub_fd = open(boot_file) + (tmp_fd, tmp_grub) = tempfile.mkstemp() + for line in grub_fd: + if title_re.match(line): + if module_line != "" and not found: + os.write(tmp_fd, module_line) + found = True + + if ctr == index: + within_title = 1 + else: + within_title = 0 + ctr = ctr + 1 + elif within_title and module_re.match(line): + start = line.find("module") + l = line[start+6:len(line)] + l = l.lstrip() + if l[0] == '/': + prefix = "/" + else: + prefix = "" + prefix = get_prefix() + module_line = "\tmodule %s%s\n" % (prefix,binpolname) + else: + if module_line != "" and not found: + os.write(tmp_fd, module_line) + found = True + + os.write(tmp_fd, line) + + if module_line != "" and not found: + os.write(tmp_fd, module_line) + found = True + + shutil.move(boot_file, boot_file+"_save") + shutil.copyfile(tmp_grub, boot_file) + os.close(tmp_fd) + try: + os.remove(tmp_grub) + except: + pass + finally: + __bootfile_lock.release() + return found + +def set_boot_policy(title_idx, filename): + boottitles = get_boot_policies() + if boottitles.has_key(title_idx): + rm_policy_from_boottitle(title_idx, [ boottitles[title_idx] ]) + rc = add_boot_policy(title_idx, filename) + return rc + + +def rm_policy_from_boottitle(index, unamelist): + """ Remove a policy from the given title. A list of possible policies + must be given to detect what module to remove + """ + found = False + ctr = 0 + title_re = re.compile("\s*title\s", re.IGNORECASE) + module_re = re.compile("\smodule\s", re.IGNORECASE) + policy_re = re.compile(".*\.bin", re.IGNORECASE) + within_title = 0 + + prefix = get_prefix() + namelist = [prefix+name for name in unamelist] + + try: + boot_file = get_bootfile() + except: + return False + try: + __bootfile_lock.acquire() + + grub_fd = open(boot_file) + (tmp_fd, tmp_grub) = tempfile.mkstemp() + for line in grub_fd: + omit_line = False + if title_re.match(line): + if ctr == index: + within_title = 1 + else: + within_title = 0 + ctr = ctr + 1 + if within_title and module_re.match(line): + if policy_re.match(line): + start = line.find("module") + pol = line[start+6:len(line)] + pol = pol.lstrip().rstrip() + if pol in namelist: + omit_line = True + found = True + if not omit_line: + os.write(tmp_fd, line) + if found: + shutil.move(boot_file, boot_file+"_save") + shutil.copyfile(tmp_grub, boot_file) + os.close(tmp_fd) + try: + os.remove(tmp_grub) + except: + pass + finally: + __bootfile_lock.release() + return found + +def set_kernel_attval(index, att, val): + """ + Append an attribut/value pair to the kernel line. + @param index : The index of the title to modify + @param att : The attribute to add + @param val : The value to add. If no value or the special value + '<>' is given, then the attribute will be removed. + If an empty value is given, then only the attribute + is added in the format "att", otherwise "att=val" + is added. + """ + found = False + ctr = 0 + title_re = re.compile("\s*title\s", re.IGNORECASE) + kernel_re = re.compile("\s*kernel\s", re.IGNORECASE) + within_title = 0 + try: + boot_file = get_bootfile() + except: + False + try: + __bootfile_lock.acquire() + + grub_fd = open(boot_file) + (tmp_fd, tmp_grub) = tempfile.mkstemp() + for line in grub_fd: + if title_re.match(line): + if ctr == index: + within_title = 1 + else: + within_title = 0 + ctr = ctr + 1 + if within_title and kernel_re.match(line): + nitems = [] + items = line.split(" ") + i = 0 + while i < len(items): + el = items[i].split("=",1) + if el[0] != att: + nitems.append(items[i].rstrip("\n")) + i += 1 + if val == "": + nitems.append("%s" % (att)) + elif val != None and val != "<>": + nitems.append("%s=%s" % (att,val)) + line = " ".join(nitems) + "\n" + os.write(tmp_fd, line) + shutil.move(boot_file, boot_file+"_save") + shutil.copyfile(tmp_grub, boot_file) + os.close(tmp_fd) + try: + os.remove(tmp_grub) + except: + pass + finally: + __bootfile_lock.release() + return found + +def get_kernel_val(index, att): + """ + Get an attribute's value from the kernel line. + @param index : The index of the title to get the attribute/value from + @param att : The attribute to read the value of + """ + ctr = 0 + title_re = re.compile("\s*title\s", re.IGNORECASE) + kernel_re = re.compile("\s*kernel\s", re.IGNORECASE) + within_title = 0 + try: + boot_file = get_bootfile() + except: + return None + try: + __bootfile_lock.acquire() + + grub_fd = open(boot_file) + for line in grub_fd: + if title_re.match(line): + if ctr == index: + within_title = 1 + else: + within_title = 0 + ctr = ctr + 1 + if within_title and kernel_re.match(line): + line = line.rstrip().lstrip() + items = line.split(" ") + i = 0 + while i < len(items): + el = items[i].split("=",1) + if el[0] == att: + if len(el) == 1: + return "<>" + return el[1] + i += 1 + finally: + __bootfile_lock.release() + return None # Not found + + +def is_boot_mounted(): + """ + Determine whether the boot partition /boot is mounted or not + """ + rc = False + file = open("/proc/mounts") + for line in file: + tmp = line.split(" ") + if tmp[1] == "/boot": + rc = True + break + return rc + +def get_prefix(): + if is_boot_mounted(): + return "/" + else: + return "/boot/" Index: root/xen-unstable.hg/tools/python/xen/util/xspolicy.py =================================================================== --- /dev/null +++ root/xen-unstable.hg/tools/python/xen/util/xspolicy.py @@ -0,0 +1,57 @@ +#============================================================================ +# 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,2007 International Business Machines Corp. +# Author: Stefan Berger +#============================================================================ + +import threading +import xsconstants + +class XSPolicy: + """ + The base policy class for all policies administered through + XSPolicyAdmin. + """ + + def __init__(self, name=None, ref=None): + self.lock = threading.Lock() + self.ref = ref + self.name = name + + def grab_lock(self): + self.lock.acquire() + + def unlock(self): + self.lock.release() + + def get_ref(self): + return self.ref + + # All methods below should be overwritten by the inheriting class + + def isloaded(self): + return False + + def loadintohv(self): + return xsconstants.XSERR_POLICY_LOAD_FAILED + + def get_type(self): + return xsconstants.XS_POLICY_NONE + + def get_type_name(self): + return "" + + def update(self, repr_new): + return -xsconstants.XSERR_GENERAL_FAILURE, "" Index: root/xen-unstable.hg/tools/python/xen/util/acmpolicy.py =================================================================== --- /dev/null +++ root/xen-unstable.hg/tools/python/xen/util/acmpolicy.py @@ -0,0 +1,1128 @@ +#============================================================================ +# 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,2007 International Business Machines Corp. +# Author: Stefan Berger +#============================================================================ + +import os +import commands +import struct +import stat +from xml.dom import minidom, Node +from xen.xend.XendLogging import log +from xen.util import security, xsconstants, bootloader, mkdir +from xen.util.xspolicy import XSPolicy +from xen.util.security import ACMError +from xen.util.xserror import XSError + +ACM_POLICIES_DIR = security.policy_dir_prefix + "/" + +# Constants needed for generating a binary policy from its XML +# representation +ACM_POLICY_VERSION = 3 # Latest one +ACM_CHWALL_VERSION = 1 + +ACM_STE_VERSION = 1 + +ACM_MAGIC = 0x001debc; + +ACM_NULL_POLICY = 0 +ACM_CHINESE_WALL_POLICY = 1 +ACM_SIMPLE_TYPE_ENFORCEMENT_POLICY = 2 +ACM_POLICY_UNDEFINED = 15 + + +ACM_SCHEMA_FILE = "/etc/xen/acm-security/policies/security_policy.xsd" + +# +# Utility functions +# + +#Check whethet the two lists have any overlapping elements +def lists_overlap(list1, list2): + for l in list1: + if l in list2: + return True + return False + + +class ACMPolicy(XSPolicy): + """ + ACMPolicy class. Implements methods for getting information from + the XML representation of the policy as well as compilation and + loading of a policy into the HV. + """ + + def __init__(self, name=None, dom=None, ref=None, xml=None): + if name: + self.name = name + self.dom = minidom.parse(self.path_from_policy_name(name)) + elif dom: + self.dom = dom + self.name = self.get_name() + elif xml: + self.dom = minidom.parseString(xml) + self.name = self.get_name() + rc = self.validate() + if rc != xsconstants.XSERR_SUCCESS: + raise XSError(rc) + mkdir.parents(ACM_POLICIES_DIR, stat.S_IRWXU) + XSPolicy.__init__(self, name=self.name, ref=ref) + + def get_dom(self): + return self.dom + + def get_name(self): + return self.policy_dom_get_hdr_item("PolicyName") + + def get_type(self): + return xsconstants.XS_POLICY_ACM + + def get_type_name(self): + return xsconstants.ACM_POLICY_ID + + def __str__(self): + return self.get_name() + + + def validate(self): + """ + validate against the policy's schema Does not fail if the + libxml2 python lib is not installed + """ + rc = xsconstants.XSERR_SUCCESS + try: + import libxml2 + except Exception, e: + log.warn("Libxml2 python-wrapper is not installed on the system.") + return xsconstants.XSERR_SUCCESS + try: + parserctxt = libxml2.schemaNewParserCtxt(ACM_SCHEMA_FILE) + schemaparser = parserctxt.schemaParse() + valid = schemaparser.schemaNewValidCtxt() + doc = libxml2.parseDoc(self.toxml()) + if doc.schemaValidateDoc(valid) != 0: + rc = -xsconstants.XSERR_BAD_XML + except Exception, e: + log.warn("Problem with the schema: %s" % str(e)) + rc = -xsconstants.XSERR_GENERAL_FAILURE + if rc != xsconstants.XSERR_SUCCESS: + log.warn("XML did not validate against schema") + return rc + + + def update(self, xml_new): + """ + Update the policy with the new XML. The hypervisor decides + whether the new policy can be applied. + """ + rc = -xsconstants.XSERR_XML_PROCESSING + errors = "" + acmpol_old = self + try: + acmpol_new = ACMPolicy(xml=xml_new) + except Exception: + return -xsconstants.XSERR_XML_PROCESSING, errors + + vmlabel_map = acmpol_new.policy_get_vmlabel_translation_map() + # An update requires version information in the current + # and new policy. The version number of the current policy + # must be the same as what is in the FromPolicy/Version node + # in the new one and the current policy's name must be the + # same as in FromPolicy/PolicyName + + now_vers = acmpol_old.policy_dom_get_hdr_item("Version") + now_name = acmpol_old.policy_dom_get_hdr_item("PolicyName") + req_oldvers = acmpol_new.policy_dom_get_frompol_item("Version") + req_oldname = acmpol_new.policy_dom_get_frompol_item("PolicyName") + + if now_vers == "" or \ + now_vers != req_oldvers or \ + now_name != req_oldname: + log.info("Policy rejected: %s != %s or %s != %s" % \ + (now_vers,req_oldvers,now_name,req_oldname)) + return -xsconstants.XSERR_VERSION_PREVENTS_UPDATE, errors + + if not self.isVersionUpdate(acmpol_new): + return -xsconstants.XSERR_VERSION_PREVENTS_UPDATE, errors + + if self.isloaded(): + newvmnames = acmpol_new.policy_get_virtualmachinelabel_names_sorted() + oldvmnames = acmpol_old.policy_get_virtualmachinelabel_names_sorted() + del_array = "" + chg_array = "" + for o in oldvmnames: + if o not in newvmnames: + old_idx = oldvmnames.index(o) + 1 # for _NULL_LABEL_ + if vmlabel_map.has_key(o): + #not a deletion, but a renaming + new = vmlabel_map[o] + new_idx = newvmnames.index(new) + 1 # for _NULL_LABEL_ + chg_array += struct.pack("ii", old_idx, new_idx) + else: + del_array += struct.pack("i", old_idx) + for v in newvmnames: + if v in oldvmnames: + old_idx = oldvmnames.index(v) + 1 # for _NULL_LABEL_ + new_idx = newvmnames.index(v) + 1 # for _NULL_LABEL_ + if old_idx != new_idx: + chg_array += struct.pack("ii", old_idx, new_idx) + #Compile the new policy + try: + acmpol_new.compile() + except Exception: + return rc, errors + bin_pol = acmpol_new.get_bin() + + #Need to do / check the following: + # - relabel all resources 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 + # - 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 + # - Commit the relabeling of resources + + reslabel_map = acmpol_new.policy_get_reslabel_translation_map() + + rc, errors = security.change_acm_policy(bin_pol, del_array, + chg_array, vmlabel_map, + reslabel_map, self, acmpol_new) + + if rc == 0: + # Replace the old DOM with the new one and save it + self.dom = acmpol_new.dom + self.compile() + log.info("ACM policy update was successful") + else: + #Not loaded in HV + self.dom = acmpol_new.dom + self.compile() + return rc, errors + + def compareVersions(self, v1, v2): + """ + Compare two policy versions given their tuples of major and + minor. + Return '0' if versions are equal, '>0' if v1 > v2 and + '<' if v1 < v2 + """ + rc = v1[0] - v2[0] + if rc == 0: + rc = v1[1] - v2[1] + return rc + + def getVersionTuple(self, item="Version"): + v_str = self.policy_dom_get_hdr_item(item) + return self.__convVersionToTuple(v_str) + + def get_version(self): + return self.policy_dom_get_hdr_item("Version") + + def isVersionUpdate(self, polnew): + if self.compareVersions(polnew.getVersionTuple(), + self.getVersionTuple()) > 0: + return True + return False + + def __convVersionToTuple(self, v_str): + """ Convert a version string, formatted according to the scheme + "%d.%d" into a tuple of (major, minor). Return (0,0) if the + string is empty. + """ + major = 0 + minor = 0 + if v_str != "": + tmp = v_str.split(".") + major = int(tmp[0]) + if len(tmp) > 1: + minor = int(tmp[1]) + return (major, minor) + + + def policy_path(self, name, prefix = ACM_POLICIES_DIR ): + path = prefix + name.replace('.','/') + _path = path.split("/") + del _path[-1] + mkdir.parents("/".join(_path), stat.S_IRWXU) + return path + + def path_from_policy_name(self, name): + return self.policy_path(name) + "-security_policy.xml" + + # + # Functions interacting with the bootloader + # + def vmlabel_to_ssidref(self, vm_label): + """ Convert a VMlabel into an ssidref given the current + policy + Return xsconstants.INVALID_SSIDREF if conversion failed. + """ + ssidref = xsconstants.INVALID_SSIDREF + names = self.policy_get_virtualmachinelabel_names_sorted() + try: + vmidx = names.index(vm_label) + 1 # for _NULL_LABEL_ + ssidref = (vmidx << 16) | vmidx + except: + pass + return ssidref + + def set_vm_bootlabel(self, vm_label): + parms="<>" + if vm_label != "": + ssidref = self.vmlabel_to_ssidref(vm_label) + if ssidref == xsconstants.INVALID_SSIDREF: + return -xsconstants.XSERR_BAD_LABEL + parms = "0x%08x:%s:%s:%s" % \ + (ssidref, xsconstants.ACM_POLICY_ID, \ + self.get_name(),vm_label) + else: + ssidref = 0 #Identifier for removal + try: + def_title = bootloader.get_default_title() + bootloader.set_kernel_attval(def_title, "ssidref", parms) + except: + return -xsconstants.XSERR_GENERAL_FAILURE + return ssidref + + # + # Utility functions related to the policy's files + # + def get_filename(self, postfix, prefix = ACM_POLICIES_DIR, dotted=False): + """ + Create the filename for the policy. The prefix is prepended + to the path. If dotted is True, then a policy name like + 'a.b.c' with remain as is, otherwise it will become 'a/b/c' + """ + name = self.get_name() + if name: + p = name.split(".") + path = "" + if dotted == True: + sep = "." + else: + sep = "/" + if len(p) > 1: + path = sep.join(p[0:len(p)-1]) + if prefix != "" or path != "": + allpath = prefix + path + sep + p[-1] + postfix + else: + allpath = p[-1] + postfix + return allpath + return None + + def __readfile(self, name): + cont = "" + filename = self.get_filename(name) + f = open(filename, "r") + if f: + cont = f.read() + f.close() + return cont + + def get_map(self): + return self.__readfile(".map") + + def get_bin(self): + return self.__readfile(".bin") + + # + # DOM-related functions + # + + def policy_dom_get(self, parent, key, createit=False): + for node in parent.childNodes: + if node.nodeType == Node.ELEMENT_NODE: + if node.nodeName == key: + return node + if createit: + self.dom_create_node(parent, key) + return self.policy_dom_get(parent, key) + + def dom_create_node(self, parent, newname, value=" "): + xml = "<"+newname+">"+ value +"" + frag = minidom.parseString(xml) + frag.childNodes[0].nodeType = Node.DOCUMENT_FRAGMENT_NODE + parent.appendChild(frag.childNodes[0]) + return frag.childNodes[0] + + def dom_get_node(self, path, createit=False): + node = None + parts = path.split("/") + doc = self.get_dom() + if len(parts) > 0: + node = self.policy_dom_get(doc.documentElement, parts[0]) + if node: + i = 1 + while i < len(parts): + _node = self.policy_dom_get(node, parts[i], createit) + if not _node: + if not createit: + break + else: + log.info("Creating node %s" % parts[i]) + self.dom_create_node(node, parts[i]) + log.info("Searching for newly created node") + _node = self.policy_dom_get(node, parts[i]) + log.info("Found: %s" % _node) + node = _node + i += 1 + return node + + # + # Header-related functions + # + def policy_dom_get_header_subnode(self, nodename): + node = self.dom_get_node("PolicyHeader/%s" % nodename) + return node + + def policy_dom_get_hdr_item(self, name, default=""): + node = self.policy_dom_get_header_subnode(name) + if node and len(node.childNodes) > 0: + return node.childNodes[0].nodeValue + return default + + def policy_dom_get_frompol_item(self, name, default="", createit=False): + node = self.dom_get_node("PolicyHeader/FromPolicy",createit) + if node: + node = self.policy_dom_get(node, name, createit) + if node and len(node.childNodes) > 0: + return node.childNodes[0].nodeValue + return default + + def get_header_fields_map(self): + header = { + 'policyname' : self.policy_dom_get_hdr_item("PolicyName"), + 'policyurl' : self.policy_dom_get_hdr_item("PolicyUrl"), + 'reference' : self.policy_dom_get_hdr_item("Reference"), + 'date' : self.policy_dom_get_hdr_item("Date"), + 'namespaceurl' : self.policy_dom_get_hdr_item("NameSpaceUrl"), + 'version' : self.policy_dom_get_hdr_item("Version") + } + return header + + def set_frompolicy_name(self, name): + """ For tools to adapt the header of the policy """ + node = self.dom_get_node("PolicyHeader/FromPolicy/PolicyName", + createit=True) + node.childNodes[0].nodeValue = name + + def set_frompolicy_version(self, version): + """ For tools to adapt the header of the policy """ + node = self.dom_get_node("PolicyHeader/FromPolicy/Version", + createit=True) + node.childNodes[0].nodeValue = version + + def set_policy_name(self, name): + """ For tools to adapt the header of the policy """ + node = self.dom_get_node("PolicyHeader/PolicyName") + node.childNodes[0].nodeValue = name + + def set_policy_version(self, version): + """ For tools to adapt the header of the policy """ + node = self.dom_get_node("PolicyHeader/Version") + node.childNodes[0].nodeValue = version + + def update_frompolicy(self, curpol): + self.set_frompolicy_name(curpol.policy_dom_get_hdr_item("PolicyName")) + version = curpol.policy_dom_get_hdr_item("Version") + self.set_frompolicy_version(version) + (maj, min) = self.__convVersionToTuple(version) + self.set_policy_version("%s.%s" % (maj, min+1)) + + # + # Get all types that are part of a node + # + + def policy_get_types(self, node): + strings = [] + i = 0 + while i < len(node.childNodes): + if node.childNodes[i].nodeName == "Type": + strings.append(node.childNodes[i].childNodes[0].nodeValue) + i += 1 + return strings + + # + # Simple Type Enforcement-related functions + # + + def policy_get_stetypes_node(self): + node = self.dom_get_node("SimpleTypeEnforcement/SimpleTypeEnforcementTypes") + return node + + def policy_get_stetypes_types(self): + strings = [] + node = self.policy_get_stetypes_node() + if node: + strings = self.policy_get_types(node) + return strings + + # + # Chinese Wall Type-related functions + # + + def policy_get_chwall_types(self): + strings = [] + node = self.dom_get_node("ChineseWall/ChineseWallTypes") + if node: + strings = self.policy_get_types(node) + return strings + + def policy_get_chwall_cfses(self): + cfs = [] + node = self.dom_get_node("ChineseWall/ConflictSets") + if node: + i = 0 + while i < len(node.childNodes): + _cfs = {} + if node.childNodes[i].nodeName == "Conflict": + _cfs['name'] = node.childNodes[i].getAttribute('name') + _cfs['chws'] = self.policy_get_types(node.childNodes[i]) + cfs.append(_cfs) + i += 1 + return cfs + + def policy_get_chwall_cfses_names_sorted(self): + """ + Return the list of all conflict set names in alphabetical + order. + """ + cfs_names = [] + node = self.dom_get_node("ChineseWall/ConflictSets") + if node: + i = 0 + while i < len(node.childNodes): + if node.childNodes[i].nodeName == "Conflict": + n = node.childNodes[i].getAttribute('name') + #it better have a name! + if n: + cfs_names.append(n) + i += 1 + cfs_names.sort() + return cfs_names + + # + # Subject Label-related functions + # + + def policy_get_bootstrap_vmlabel(self): + node = self.dom_get_node("SecurityLabelTemplate/SubjectLabels") + if node: + vmlabel = node.getAttribute("bootstrap") + return vmlabel + + # Get the names of all virtual machine labels; returns an array + def policy_get_virtualmachinelabel_names(self): + strings = [] + node = self.dom_get_node("SecurityLabelTemplate/SubjectLabels") + if node: + i = 0 + while i < len(node.childNodes): + if node.childNodes[i].nodeName == "VirtualMachineLabel": + name = self.policy_dom_get(node.childNodes[i], "Name") + strings.append(name.childNodes[0].nodeValue) + i += 1 + return strings + + def policy_sort_virtualmachinelabel_names(self, vmnames): + bootstrap = self.policy_get_bootstrap_vmlabel() + if bootstrap not in vmnames: + return -xsconstants.XSERR_POLICY_INCONSISTENT + vmnames.remove(bootstrap) + vmnames.sort() + vmnames.insert(0, bootstrap) + return vmnames + + def policy_get_virtualmachinelabel_names_sorted(self): + """ Get a sorted list of VMlabel names. The bootstrap VM's + label will be the first one in that list, followed + by an alphabetically sorted list of VM label names """ + vmnames = self.policy_get_virtualmachinelabel_names() + return self.policy_sort_virtualmachinelabel_names(vmnames) + + def policy_get_virtualmachinelabels(self): + """ Get a list of all virtual machine labels in this policy """ + res = [] + node = self.dom_get_node("SecurityLabelTemplate/SubjectLabels") + if node: + i = 0 + while i < len(node.childNodes): + if node.childNodes[i].nodeName == "VirtualMachineLabel": + _res = {} + _res['type'] = xsconstants.ACM_LABEL_VM + name = self.policy_dom_get(node.childNodes[i], "Name") + _res['name'] = name.childNodes[0].nodeValue + stes = self.policy_dom_get(node.childNodes[i], + "SimpleTypeEnforcementTypes") + if stes: + _res['stes'] = self.policy_get_types(stes) + else: + _res['stes'] = [] + chws = self.policy_dom_get(node.childNodes[i], + "ChineseWallTypes") + if chws: + _res['chws'] = self.policy_get_types(chws) + else: + _res['chws'] = [] + res.append(_res) + i += 1 + return res + + def policy_get_stes_of_vmlabel(self, vmlabel): + """ Get a list of all STEs of a given VMlabel """ + return self.__policy_get_stes_of_labeltype(vmlabel, + "VirtualMachineLabel") + + def policy_get_stes_of_resource(self, reslabel): + """ Get a list of all resources of a given VMlabel """ + return self.__policy_get_stes_of_labeltype(reslabel, "ResourceLabel") + + def __policy_get_stes_of_labeltype(self, label, labeltype): + node = self.dom_get_node("SecurityLabelTemplate/SubjectLabels") + if node: + i = 0 + while i < len(node.childNodes): + if node.childNodes[i].nodeName == labeltype: + name = self.policy_dom_get(node.childNodes[i], "Name") + if name.childNodes[0].nodeValue == label: + stes = self.policy_dom_get(node.childNodes[i], + "SimpleTypeEnforcementTypes") + if not stes: + return [] + return self.policy_get_types(stes) + i += 1 + return [] + + def policy_check_vmlabel_against_reslabels(self, vmlabel, resources): + """ + Check whether the given vmlabel is compatible with the given + resource labels. Do this by getting all the STEs of the + vmlabel and the STEs of the resources. Any STE type of the + VM label must match an STE type of the resource. + """ + vm_stes = self.policy_get_stes_of_vmlabel(vmlabel) + if len(vm_stes) == 0: + return False + for res in resources: + res_stes = self.policy_get_stes_of_resource(res) + if not lists_overlap(res_stes, vm_stes): + return False + return True + + def policy_get_label_translation_map(self, labeltype): + res = {} + node = self.dom_get_node("SecurityLabelTemplate/SubjectLabels") + if node: + i = 0 + while i < len(node.childNodes): + if node.childNodes[i].nodeName == labeltype: + name = self.policy_dom_get(node.childNodes[i], "Name") + from_name = name.getAttribute("from") + if from_name: + res.update({from_name : name.childNodes[0].nodeValue}) + i += 1 + return res + + def policy_get_vmlabel_translation_map(self): + """ + Get a dictionary of virtual machine mappings from their + old VMlabel name to the new VMlabel name. + """ + return self.policy_get_label_translation_map("VirtualMachineLabel") + + def policy_get_reslabel_translation_map(self): + """ + Get a dictionary of resource mappings from their + old resource label name to the new resource label name. + """ + return self.policy_get_label_translation_map("ResourceLabel") + + # + # Object Label-related functions + # + def policy_get_resourcelabel_names(self): + """ + Get the names of all resource labels in an array but + only those that actually have types + """ + strings = [] + node = self.dom_get_node("SecurityLabelTemplate/ObjectLabels") + if node: + i = 0 + while i < len(node.childNodes): + if node.childNodes[i].nodeName == "ResourceLabel": + name = self.policy_dom_get(node.childNodes[i], "Name") + stes = self.policy_dom_get(node.childNodes[i], + "SimpleTypeEnforcementTypes") + if stes: + strings.append(name.childNodes[0].nodeValue) + i += 1 + return strings + + def policy_get_resourcelabels(self): + """ + Get all information about all resource labels of this policy. + """ + res = [] + node = self.dom_get_node("SecurityLabelTemplate/ObjectLabels") + if node: + i = 0 + while i < len(node.childNodes): + if node.childNodes[i].nodeName == "ResourceLabel": + _res = {} + _res['type'] = xsconstants.ACM_LABEL_RES + name = self.policy_dom_get(node.childNodes[i], "Name") + _res['name'] = name.childNodes[0].nodeValue + stes = self.policy_dom_get(node.childNodes[i], + "SimpleTypeEnforcementTypes") + if stes: + _res['stes'] = self.policy_get_types(stes) + else: + _res['stes'] = [] + _res['chws'] = [] + res.append(_res) + i += 1 + return res + + + def policy_find_reslabels_with_stetype(self, stetype): + """ + Find those resource labels that hold a given STE type. + """ + res = [] + reslabels = self.policy_get_resourcelabels() + for resl in reslabels: + if stetype in resl['stes']: + res.append(resl['name']) + return res + + + def toxml(self): + dom = self.get_dom() + if dom: + return dom.toxml() + return None + + def save(self): + rc = -xsconstants.XSERR_FILE_ERROR + name = self.get_name() + if name: + path = self.path_from_policy_name(name) + if path: + f = open(path, 'w') + if f: + f.write(self.toxml()) + f.close() + rc = 0 + return rc + + def compile(self): + rc = self.save() + if rc == 0: + rc = self.policy_write_map_and_bin() + return rc + + def loadintohv(self): + """ + load this policy into the hypervisor + if successful,the policy's flags will indicate that the + policy is the one loaded into the hypervisor + """ + (ret, output) = commands.getstatusoutput( + security.xensec_tool + + " loadpolicy " + + self.get_filename(".bin")) + if ret != 0: + return -xsconstants.XSERR_POLICY_LOAD_FAILED + return xsconstants.XSERR_SUCCESS + + def isloaded(self): + """ + Determine whether this policy is the active one. + """ + security.refresh_security_policy() + if self.get_name() == security.active_policy: + return True + return False + + def destroy(self): + """ + Destroy the policy including its binary, mapping and + XML files. + This only works if the policy is not the one that's loaded + """ + if self.isloaded(): + return -xsconstants.XSERR_POLICY_LOADED + files = [ self.get_filename(".map",""), + self.get_filename(".bin",""), + self.path_from_policy_name(self.get_name())] + for f in files: + try: + os.unlink(f) + except: + pass + return xsconstants.XSERR_SUCCESS + + def policy_get_domain_label(self, domid): + """ + Given a domain's ID, retrieve the label it has using + its ssidref for reverse calculation. + """ + try: + mgmt_dom = security.get_ssid(domid) + except: + return "" + + chwall_ref = int(mgmt_dom[3]) & 0xffff + allvmtypes = self.policy_get_virtualmachinelabel_names_sorted() + return allvmtypes[chwall_ref-1] # skip _NULL_LABLE_ + + def policy_get_domain_label_formatted(self, domid): + label = self.policy_get_domain_label(domid) + if label == "": + return "" + return "%s:%s:%s" % (xsconstants.ACM_POLICY_ID, self.get_name(), label) + + def policy_write_map_and_bin(self, dontwrite=False): + """ + Write the policy's map and binary files -- compile the policy. + """ + def roundup8(len): + return ((len + 7) & ~7) + + rc = 0 + mapfile = "" + primpolcode = ACM_POLICY_UNDEFINED + secpolcode = ACM_POLICY_UNDEFINED + + rc = self.validate() + if rc: + return rc + + stes = self.policy_get_stetypes_types() + if stes: + stes.sort() + + chws = self.policy_get_chwall_types() + if chws: + chws.sort() + + vms = self.policy_get_virtualmachinelabels() + bootstrap = self.policy_get_bootstrap_vmlabel() + + vms_with_chws = [] + chws_by_vm = {} + for v in vms: + if v.has_key("chws"): + vms_with_chws.append(v["name"]) + chws_by_vm[v["name"]] = v["chws"] + if bootstrap in vms_with_chws: + vms_with_chws.remove(bootstrap) + vms_with_chws.sort() + vms_with_chws.insert(0, bootstrap) + else: + vms_with_chws.sort() + + vms_with_stes = [] + stes_by_vm = {} + for v in vms: + if v.has_key("stes"): + vms_with_stes.append(v["name"]) + stes_by_vm[v["name"]] = v["stes"] + if bootstrap in vms_with_stes: + vms_with_stes.remove(bootstrap) + vms_with_stes.sort() + vms_with_stes.insert(0, bootstrap) + else: + vms_with_stes.sort() + + resnames = self.policy_get_resourcelabel_names() + resnames.sort() + stes_by_res = {} + res = self.policy_get_resourcelabels() + for r in res: + if r.has_key("stes"): + stes_by_res[r["name"]] = r["stes"] + + max_chw_ssids = 1 + len(vms_with_chws) + max_chw_types = 1 + len(vms_with_chws) + max_ste_ssids = 1 + len(vms_with_stes) + len(resnames) + max_ste_types = 1 + len(vms_with_stes) + len(resnames) + + mapfile = "POLICYREFERENCENAME %s\n" % self.get_name() + mapfile += "MAGIC %08x\n" % ACM_MAGIC + mapfile += "POLICFILE %s\n" % \ + self.path_from_policy_name(self.get_name()) + mapfile += "BINARYFILE %s\n" % self.get_filename(".bin") + mapfile += "MAX-CHWALL-TYPES %08x\n" % len(chws) + mapfile += "MAX-CHWALL-SSIDS %08x\n" % max_chw_ssids + mapfile += "MAX-CHWALL-LABELS %08x\n" % max_chw_ssids + mapfile += "MAX-STE-TYPES %08x\n" % len(stes) + mapfile += "MAX-STE-SSIDS %08x\n" % max_ste_ssids + mapfile += "MAX-STE-LABELS %08x\n" % max_ste_ssids + mapfile += "\n" + + if chws: + mapfile += \ + "PRIMARY CHWALL\n" + primpolcode = ACM_CHINESE_WALL_POLICY + if stes: + mapfile += \ + "SECONDARY STE\n" + else: + mapfile += \ + "SECONDARY NULL\n" + secpolcode = ACM_SIMPLE_TYPE_ENFORCEMENT_POLICY + else: + if stes: + mapfile += \ + "PRIMARY STE\n" + primpolcode = ACM_SIMPLE_TYPE_ENFORCEMENT_POLICY + mapfile += \ + "SECONDARY NULL\n" + + mapfile += "\n" + + if len(vms_with_chws) > 0: + mapfile += \ + "LABEL->SSID ANY CHWALL __NULL_LABEL__ %x\n" % 0 + i = 0 + for v in vms_with_chws: + mapfile += \ + "LABEL->SSID VM CHWALL %-20s %x\n" % \ + (v, i+1) + i += 1 + mapfile += "\n" + + if len(vms_with_stes) > 0 or len(resnames) > 0: + mapfile += \ + "LABEL->SSID ANY STE __NULL_LABEL__ %08x\n" % 0 + i = 0 + for v in vms_with_stes: + mapfile += \ + "LABEL->SSID VM STE %-20s %x\n" % (v, i+1) + i += 1 + j = 0 + for r in resnames: + mapfile += \ + "LABEL->SSID RES STE %-20s %x\n" % (r, j+i+1) + j += 1 + mapfile += "\n" + + if vms_with_chws: + mapfile += \ + "SSID->TYPE CHWALL %08x\n" % 0 + i = 1 + for v in vms_with_chws: + mapfile += \ + "SSID->TYPE CHWALL %08x" % i + for c in chws_by_vm[v]: + mapfile += " %s" % c + mapfile += "\n" + i += 1 + mapfile += "\n" + + if len(vms_with_stes) > 0 or len(resnames) > 0: + mapfile += \ + "SSID->TYPE STE %08x\n" % 0 + i = 1 + for v in vms_with_stes: + mapfile += \ + "SSID->TYPE STE %08x" % i + for s in stes_by_vm[v]: + mapfile += " %s" % s + mapfile += "\n" + i += 1 + + for r in resnames: + mapfile += \ + "SSID->TYPE STE %08x" % i + for s in stes_by_res[r]: + mapfile += " %s" % s + mapfile += "\n" + i += 1 + mapfile += "\n" + + if chws: + i = 0 + while i < len(chws): + mapfile += \ + "TYPE CHWALL %-20s %d\n" % (chws[i], i) + i += 1 + mapfile += "\n" + if stes: + i = 0 + while i < len(stes): + mapfile += \ + "TYPE STE %-20s %d\n" % (stes[i], i) + i += 1 + mapfile += "\n" + + mapfile += "\n" + + # Build header with policy name + length = roundup8(4 + len(self.get_name()) + 1) + polname = self.get_name(); + pr_bin = struct.pack("!i", len(polname)+1) + pr_bin += polname; + while len(pr_bin) < length: + pr_bin += "\x00" + + # Build chinese wall part + cfses_names = self.policy_get_chwall_cfses_names_sorted() + cfses = self.policy_get_chwall_cfses() + + chwformat = "!iiiiiiiii" + max_chw_cfs = len(cfses) + chw_ssid_offset = struct.calcsize(chwformat) + chw_confset_offset = chw_ssid_offset + \ + 2 * len(chws) * max_chw_types + chw_running_types_offset = 0 + chw_conf_agg_offset = 0 + + chw_bin = struct.pack(chwformat, + ACM_CHWALL_VERSION, + ACM_CHINESE_WALL_POLICY, + len(chws), + max_chw_ssids, + max_chw_cfs, + chw_ssid_offset, + chw_confset_offset, + chw_running_types_offset, + chw_conf_agg_offset) + chw_bin_body = "" + # simulate __NULL_LABEL__ + for c in chws: + chw_bin_body += struct.pack("!h",0) + # VMs that are listed and their chinese walls + for v in vms_with_chws: + for c in chws: + if c in chws_by_vm[v]: + chw_bin_body += struct.pack("!h",1) + else: + chw_bin_body += struct.pack("!h",0) + + # Conflict sets -- they need to be processed in alphabetical order + for cn in cfses_names: + if cn == "" or cn is None: + return -xsconstants.XSERR_BAD_CONFLICTSET + i = 0 + while i < len(cfses): + if cfses[i]['name'] == cn: + conf = cfses[i]['chws'] + break + i += 1 + for c in chws: + if c in conf: + chw_bin_body += struct.pack("!h",1) + else: + chw_bin_body += struct.pack("!h",0) + del cfses[i] + + if len(cfses) != 0: + return -xsconstants.XSERR_BAD_CONFLICTSET + + chw_bin += chw_bin_body + + while len(chw_bin) < roundup8(len(chw_bin)): + chw_bin += "\x00" + + # Build STE part + steformat="!iiiii" + ste_bin = struct.pack(steformat, + ACM_STE_VERSION, + ACM_SIMPLE_TYPE_ENFORCEMENT_POLICY, + len(stes), + max_ste_types, + struct.calcsize(steformat)) + ste_bin_body = "" + if stes: + # Simulate __NULL_LABEL__ + for s in stes: + ste_bin_body += struct.pack("!h",0) + # VMs that are listed and their chinese walls + for v in vms_with_stes: + for s in stes: + if s in stes_by_vm[v]: + ste_bin_body += struct.pack("!h",1) + else: + ste_bin_body += struct.pack("!h",0) + for r in resnames: + for s in stes: + if s in stes_by_res[r]: + ste_bin_body += struct.pack("!h",1) + else: + ste_bin_body += struct.pack("!h",0) + + ste_bin += ste_bin_body; + + while len(ste_bin) < roundup8(len(ste_bin)): + ste_bin += "\x00" + + #Write binary header: + headerformat="!iiiiiiiiii" + totallen_bin = struct.calcsize(headerformat) + \ + len(pr_bin) + len(chw_bin) + len(ste_bin) + polref_offset = struct.calcsize(headerformat) + primpoloffset = polref_offset + len(pr_bin) + if primpolcode == ACM_CHINESE_WALL_POLICY: + secpoloffset = primpoloffset + len(chw_bin) + elif primpolcode == ACM_SIMPLE_TYPE_ENFORCEMENT_POLICY: + secpoloffset = primpoloffset + len(ste_bin) + else: + secpoloffset = primpoloffset + + (major, minor) = self.getVersionTuple() + hdr_bin = struct.pack(headerformat, + ACM_POLICY_VERSION, + ACM_MAGIC, + totallen_bin, + polref_offset, + primpolcode, + primpoloffset, + secpolcode, + secpoloffset, + major, minor) + + if not dontwrite: + #write the map file + f = open(self.get_filename(".map"),"w") + if f: + f.write(mapfile) + f.close() + else: + return -xsconstants.XSERR_FILE_ERROR + + #write the binary file + f = open(self.get_filename(".bin"),"w") + if f: + f.write(hdr_bin) + f.write(pr_bin) + if chws: + f.write(chw_bin) + if stes: + f.write(ste_bin) + f.close() + else: + return -xsconstants.XSERR_FILE_ERROR + return rc Index: root/xen-unstable.hg/tools/security/policies/security_policy.xsd =================================================================== --- root.orig/xen-unstable.hg/tools/security/policies/security_policy.xsd +++ root/xen-unstable.hg/tools/security/policies/security_policy.xsd @@ -22,7 +22,7 @@ - + @@ -91,23 +91,23 @@ - + - + - + - - + + @@ -131,4 +131,21 @@ + + + + + + + + + + + + + + + + + Index: root/xen-unstable.hg/tools/python/xen/xend/XendXSPolicy.py =================================================================== --- /dev/null +++ root/xen-unstable.hg/tools/python/xen/xend/XendXSPolicy.py @@ -0,0 +1,240 @@ +#============================================================================ +# 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) 2007 IBM Corporation +# Copyright (c) 2006 Xensource +#============================================================================ + +import logging +from xen.xend.XendBase import XendBase +from xen.xend.XendError import * +from xen.util import xsconstants, xspolicy, security +from xen.util.acmpolicy import ACMPolicy +from xen.util.xspolicyadmin import XSPolicyAdminInstance +import base64 + +log = logging.getLogger("xend.XendXSPolicy") +log.setLevel(logging.TRACE) + + +def xspolicy_from_ref(xs_ref): + poladmin = XSPolicyAdminInstance() + return poladmin.policy_from_ref(xs_ref) + + +class XendXSPolicy(XendBase): + """ Representation of an XSPolicy. """ + + def getClass(self): + return "XSPolicy" + + def getMethods(self): + methods = ['activate_xspolicy'] + return XendBase.getMethods() + methods + + def getFuncs(self): + funcs = [ 'get_xstype', + 'set_xspolicy', + 'get_xspolicy', + 'rm_xsbootpolicy', + 'get_resource_label', + 'set_resource_label', + 'get_labeled_resources' ] + return XendBase.getFuncs() + funcs + + getClass = classmethod(getClass) + getMethods = classmethod(getMethods) + getFuncs = classmethod(getFuncs) + + def __init__(self, record, uuid): + XendBase.__init__(self, uuid, record) + + def get_all(self): + poladmin = XSPolicyAdminInstance() + refs = poladmin.get_policies_refs() + return refs + + def get_record(self): + xspol = xspolicy_from_ref(self.get_uuid()) + + xspol_record = { + 'uuid' : xs_ref, + 'flags' : XSPolicyAdminInstance().get_policy_flags(xspol), + 'repr' : xspol.toxml(), + 'type' : xspol.get_type(), + } + return xspol_record + + def get_xstype(self): + return XSPolicyAdminInstance().isXSEnabled() + + def set_xspolicy(self, xstype, xml, flags, overwrite): + ref = "" + xstype = int(xstype) + flags = int(flags) + + polstate = { 'xs_ref' : "", 'repr' : "", 'type' : 0, + 'flags' : 0 , 'version': 0 , 'errors' : "", 'xserr' : 0 } + if xstype == xsconstants.XS_POLICY_ACM: + poladmin = XSPolicyAdminInstance() + try: + (xspol, rc, errors) = poladmin.add_acmpolicy_to_system( + xml, flags, + overwrite) + if rc != 0: + polstate.update( { 'xserr' : rc, + 'errors': base64.b64encode(errors) } ) + else: + ref = xspol.get_ref() + polstate = { + 'xs_ref' : ref, + 'flags' : poladmin.get_policy_flags(xspol), + 'type' : xstype, + 'repr' : "", + 'version': xspol.get_version(), + 'errors' : base64.b64encode(errors), + 'xserr' : rc, + } + except Exception, e: + raise XenSecurityError(e) + else: + raise XenSecurityError(xsconstants.XSERR_POLICY_TYPE_UNSUPPORTED) + return polstate + + def activate_xspolicy(self, flags): + flags = int(flags) + rc = -xsconstants.XSERR_GENERAL_FAILURE + poladmin = XSPolicyAdminInstance() + xspol = xspolicy_from_ref(self.get_uuid()) + if xspol: + try: + rc = poladmin.activate_xspolicy(xspol, flags) + except Exception, e: + log.info("Activate_policy: %s" % str(e)) + return rc + + def get_xspolicy(self): + polstate = { 'xs_ref' : "", + 'repr' : "", + 'type' : 0, + 'flags' : 0, + 'version': "", + 'errors' : "", + 'xserr' : 0 } + poladmin = XSPolicyAdminInstance() + refs = poladmin.get_policies_refs() + # Will return one or no policy + if refs and len(refs) > 0: + xspol = None + try: + xspol = xspolicy_from_ref(refs[0]) + xspol.grab_lock() + + polstate = { + 'xs_ref' : refs[0], + 'repr' : xspol.toxml(), + 'type' : xspol.get_type(), + 'flags' : poladmin.get_policy_flags(xspol), + 'version': xspol.get_version(), + 'errors' : "", + 'xserr' : 0, + } + finally: + if xspol: + xspol.unlock() + return polstate + + def rm_xsbootpolicy(self): + return XSPolicyAdminInstance().rm_bootpolicy() + + def get_labeled_resources(self): + return security.get_labeled_resources_xapi() + + def set_resource_label(self, resource, sec_lab, old_lab): + rc = security.set_resource_label_xapi(resource, sec_lab, old_lab) + return rc + + def get_resource_label(self, resource): + res = security.get_resource_label_xapi(resource) + return res + + get_xstype = classmethod(get_xstype) + get_xspolicy = classmethod(get_xspolicy) + set_xspolicy = classmethod(set_xspolicy) + rm_xsbootpolicy = classmethod(rm_xsbootpolicy) + set_resource_label = classmethod(set_resource_label) + get_resource_label = classmethod(get_resource_label) + get_labeled_resources = classmethod(get_labeled_resources) + + +class XendACMPolicy(XendXSPolicy): + + def getClass(self): + return "ACMPolicy" + + def getAttrRO(self): + attrRO = [ 'xml', + 'map', + 'binary', + 'header' ] + return XendXSPolicy.getAttrRO() + attrRO + + getClass = classmethod(getClass) + getAttrRO = classmethod(getAttrRO) + + def __init__(self, record, uuid): + XendXSPolicy.__init__(self, record, uuid) + + def get_record(self): + xspol = self.xspolicy_from_ref(self.get_uuid()) + + polstate = { + 'uuid' : self.get_uuid(), + 'flags' : XSPolicyAdminInstance().get_policy_flags(xspol), + 'repr' : xspol.toxml(), + 'type' : xspol.get_type(), + } + return polstate + + def get_header(self): + header = { + 'policyname' : "", 'policyurl' : "", 'reference' : "", + 'date' : "", 'namespaceurl' : "", 'version' : "", + } + xspol = xspolicy_from_ref(self.get_uuid()) + if xspol: + header = xspol.get_header_fields_map() + return header + + def get_xml(self): + policyxml = "" + xspol = xspolicy_from_ref(self.get_uuid()) + if xspol: + policyxml = xspol.toxml() + return policyxml + + def get_map(self): + policymap = "" + xspol = xspolicy_from_ref(self.get_uuid()) + if xspol: + policymap = xspol.get_map() + return policymap + + def get_binary(self): + polb64 = "" + xspol = xspolicy_from_ref(self.get_uuid()) + if xspol: + polbin = xspol.get_bin() + polb64 = base64.b64encode(polbin) + return polb64