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,1312 @@ +#============================================================================ +# 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 sys +import os, stat +import string +import re +import commands +import struct as struct +import tempfile +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 dictio +from xen.util import xserror + +# +# 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 = "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 0 and 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): + err("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 = [] + boot_file = get_bootfile() + 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()) + 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 + boot_file = get_bootfile() + 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 + 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 + boot_file = get_bootfile() + 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 + 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 + boot_file = get_bootfile() + grub_fd = open(boot_file) + (tmp_fd, tmp_grub) = tempfile.mkstemp() + for line in grub_fd: + if title_re.match(line): + if module_line != "": + os.write(tmp_fd, module_line) + module_line = "" + + if ctr == index: + within_title = 1 + else: + within_title = 0 + ctr = ctr + 1 + if 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 = "" + module_line = "\tmodule %s%s\n" % (prefix,binpolname) + + os.write(tmp_fd, line) + + if module_line != "": + os.write(tmp_fd, module_line) + module_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 + + +def rm_policy_from_boottitle(index, namelist): + """ 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 + boot_file = get_bootfile() + 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[0] == '/': + pol = pol[1:len(pol)] + 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 + return found + + +# Some internal variables used by the Xen-API +SHYPE_LABEL_VM = 1 +SHYPE_LABEL_RES = 2 + +COMPONENT_SHYPE_STE = 1 +COMPONENT_SHYPE_CHWALL = 2 + +INST_NONE = 0 +INST_BOOT = (1 << 0) +INST_LOAD = (1 << 1) + +POLICY_NONE = 0 +POLICY_SHYPE = (1 << 0) + +ACM_POLICIES_DIR = security.policy_dir_prefix + "/" + +# Constants needed for generating a binary policy from its XML +# representation +ACM_POLICY_VERSION = 2 +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_MANAGED_POLICIES_FILE = "/etc/xen/acm-security/policies/managed_policies" + +# +# Utility functions for DOM operations +# + +def policy_dom_get(parent, key): + for node in parent.childNodes: + if node.nodeType == Node.ELEMENT_NODE: + if node.nodeName == key: + return node + +def listtoxml(type, list): + pre = "<"+type+">" + post = "" + i = 0 + xml = "" + while i < len(list): + xml += pre + list[i] + post + i += 1 + return xml + +# Create a node with the given name and link it to the given node as child +def createnode(node, newname): + xml = "<"+newname+">" + frag = minidom.parseString(xml) + frag.childNodes[0].nodeType = Node.DOCUMENT_FRAGMENT_NODE + node.appendChild(frag.childNodes[0]) + return frag.childNodes[0] + +# +# General policy handling functions +# + +def policy_path(name, prefix = ACM_POLICIES_DIR ): + return prefix + name.replace('.','/') + +def path_from_policy_name(name): + return policy_path(name) + "-security_policy.xml" + +#Compare two lists whether they contain equal elements +def compare_lists(list1, list2): + if len(list1) != len(list2): + return 1 + for l in list1: + if not l in list2: + return 1 + return 0 + + + +class ACMPolicyAdmin: + + def __init__(self, maxpolicies, name=None, dom=None): + self.maxpolicies = maxpolicies + try: + self.policies = dictio.dict_read("managed_policies", + ACM_MANAGED_POLICIES_FILE) + except: + self.policies = {} + #FIXME: Need to determine which one of the policies is the loaded + # one. + + + def isACMEnabled(self): + return security.on() + + + def add_xmlpolicy_to_system(self, xmltext, flags, overwrite): + """ Add a policy's xml representation to the system. The + policy will automatically be compiled + flags: + 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 + 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 + """ + rc = -1 + try: + dom = minidom.parseString(xmltext) + except: + return -xserror.XSERR_BAD_XML + + acmpol = ACMPolicy(dom=dom) + + #First some basic tests that do not modify anything: + title = get_default_title() + bootpolicies = get_boot_policies() + + if int(flags) & INST_BOOT: + if int(title) != -1 and \ + bootpolicies.has_key(title) and \ + bootpolicies[title] != acmpol.get_filename(".bin",""): + return -xserror.XSERR_BOOTPOLICY_INSTALLED + + if not overwrite and len(self.policies) >= self.maxpolicies: + return -xserror.XSERR_BOOTPOLICY_INSTALLED + + rc = acmpol.compile() + if rc != 0: + return rc + + if int(flags) & INST_LOAD: + rc = acmpol.loadintohv() + if rc != 0: + return rc + + if overwrite: + #FIXME: Remove existing policy from the system + keys = self.policies.keys() + #for k in keys: + # self.rm_policy_from_system(k) + + if int(flags) & INST_BOOT: + if not bootpolicies.has_key(title): + add_boot_policy(title, acmpol.get_filename(".bin","")) + + if dom: + self.policies[uuid.createString()] = acmpol + dictio.dict_write(self.policies, + "managed_policies", + ACM_MANAGED_POLICIES_FILE) + return rc + + def rm_policy_from_system(self, ref): + if self.policies.has_key(ref): + acmpol = self.policies[ref] + path = path_from_policy_name(acmpol.get_name()) + os.unlink(path) + del self.policies[ref] + dictio.dict_write(self.policies, + "managed_policies", + ACM_MANAGED_POLICIES_FILE) + + def rm_shypebootpolicy(self): + """ Remove any shype boot policy from the grub configuration file + """ + rc = 0 + title = get_default_title() + if int(title) != -1: + rm_policy_from_boottitle(title, + [v.get_filename(".bin","") for v in self.policies.values()]) + else: + rc = -xserror.XSERR_NO_DEFAULT_BOOT_TITLE + return rc + + def get_policy_flags(self, ref): + """ 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. + """ + #FIXME: flags not properly supported, yet + flags = 0 + + acmpol = self.policy_from_ref(ref) + filename = acmpol.get_filename(".bin","") + title = get_default_title() + policies = get_boot_policies() + + if policies.has_key(title): + polfile = policies[title] + if polfile == filename or \ + "/"+polfile == filename: + flags |= INST_BOOT + return flags + + def get_policies(self): + return self.policies.values() + + def get_policies_refs(self): + return self.policies.keys() + + def has_ref(self, ref): + return self.policies.has_key(ref) + + def policy_from_ref(self, ref): + if ref in self.policies.keys(): + return self.policies[ref] + + def ref_from_polname(self, polname): + ref = None + for (k, v) in self.policies.items(): + if v.get_name() == polname: + ref = k + break + return ref + + + +def ACMPolicyAdminInstance(maxpolicies=1): + global poladmin + try: + poladmin + except: + poladmin = ACMPolicyAdmin(maxpolicies) + return poladmin + +# +# ACMPolicy class. Implements methods for modification of the +# XML representation of the policy +# +class ACMPolicy: + + def __init__(self, name=None, dom=None): + if name: + self.name = name + self.dom = minidom.parse(path_from_policy_name(name)) + elif dom: + self.dom = dom + self.name = self.get_name() + self.isloaded = False #loaded into HV + + def __str__(self): + return self.get_name() + + # + # Header-related functions + # + def get_dom(self): + return self.dom + + def get_name(self): + return self.policy_dom_get_name() + + def get_filename(self, postfix, prefix = ACM_POLICIES_DIR): + name = self.get_name() + if name: + p = name.split(".") + path = "" + if len(p) > 1: + path = "".join(p[0:len(p)-1]) + return prefix + path + p[-1] + postfix + + def get_map(self): + cont = "" + filename = self.get_filename(".map") + f = open(filename, "r") + if f: + cont = f.read() + f.close() + return cont + + def get_bin(self): + cont = "" + filename = self.get_filename(".bin") + f = open(filename, "r") + if f: + cont = f.read() + f.close() + return cont + + # + # DOM-related functions + # + + def policy_dom_get_policyheader_node(self): + doc = self.get_dom() + return policy_dom_get(doc.documentElement, "PolicyHeader") + + def policy_dom_get_policyname_node(self, mustadd=False): + node = self.policy_dom_get_policyheader_node() + if node: + _node = policy_dom_get(node, "PolicyName") + if not _node: + _node = createnode(node, "PolicyName") + return _node + return None + + def policy_dom_get_policyurl_node(self, mustadd=False): + node = self.policy_dom_get_policyheader_node() + if node: + _node = policy_dom_get(node, "PolicyUrl") + if not _node and mustadd == True: + _node = createnode(node, "PolicyUrl") + return _node + return None + + def policy_dom_get_date_node(self, mustadd=False): + node = self.policy_dom_get_policyheader_node() + if node: + _node = policy_dom_get(node, "Date") + if not _node and mustadd == True: + _node = createnode(node, "Date") + return _node + return None + + def policy_dom_get_version_node(self, mustadd=False): + node = self.policy_dom_get_policyheader_node() + if node: + _node = policy_dom_get(node, "Version") + if not _node and mustadd == True: + _node = createnode(node, "Version") + return _node + return None + + def policy_dom_get_name(self): + node = self.policy_dom_get_policyname_node() + if node and len(node.childNodes) > 0: + return node.childNodes[0].nodeValue + return None + + def policy_dom_set_name(sefl, name): + rc = -1 + node = self.policy_dom_get_policyname_node(True) + if node and len(node.childNodes) > 0: + node.childNodes[0].nodeValue = name + rc = 0 + return rc + + def policy_dom_get_url(self): + node = self.policy_dom_get_policyurl_node() + if node and len(node.childNodes) > 0: + return node.childNodes[0].nodeValue + return None + + def policy_dom_set_url(self, url): + rc = -1 + node = self.policy_dom_get_policyurl_node(True) + if node and len(node.childNodes) > 0: + node.childNodes[0].nodeValue = url + rc = 0 + return rc + + def policy_dom_get_date(self): + node = self.policy_dom_get_date_node() + if node and len(node.childNodes) > 0: + return node.childNodes[0].nodeValue + return None + + def policy_dom_set_date(self, date): + rc = -1 + node = self.policy_dom_get_date_node(True) + if node and len(node.childNodes) > 0: + node.childNodes[0].nodeValue = date + rc = 0 + return rc + + def policy_dom_get_version(self): + node = self.policy_dom_get_version_node() + if node and len(node.childNodes) > 0: + return node.childNodes[0].nodeValue + return None + + def policy_dom_set_version(self, date): + rc = -1 + node = self.policy_dom_get_version_node(True) + if node and len(node.childNodes) > 0: + node.childNodes[0].nodeValue = date + rc = 0 + return rc + + # 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 + + # Remove a specific type + def policy_rm_type(self, node, value): + rc = -1 + i = 0 + while i < len(node.childNodes): + if node.childNodes[i].nodeName == "Type": + if node.childNodes[i].childNodes[0].nodeValue == value: + node.removeChild(node.childNodes[i]) + rc = 0 + break + i += 1 + return rc + + # + # Simple Type Enforcement-related functions + # + + def policy_get_ste_node(self, mustcreate=False): + doc = self.get_dom() + node = policy_dom_get(doc.documentElement, "SimpleTypeEnforcement") + if not node and mustcreate == True: + node = createnode(doc.documentElement, "SimpleTypeEnforcement"); + return node + + def policy_get_stetypes_node(self, mustcreate=False): + node = self.policy_get_ste_node(mustcreate) + if node: + _node = policy_dom_get(node, "SimpleTypeEnforcementTypes") + if not _node and mustcreate == True: + _node = createnode(node, "SimpleTypeEnforcementTypes") + return _node + return None + + 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_node(self, mustcreate=False): + doc = self.get_dom() + node = policy_dom_get(doc.documentElement, "ChineseWall") + if not node and mustcreate == True: + node = createnode(doc.documentElement, "ChineseWall") + return node + + def policy_get_chwalltypes_node(self, mustcreate=False): + node = self.policy_get_chwall_node(mustcreate) + if node: + _node = policy_dom_get(node, "ChineseWallTypes") + if not _node and mustcreate == True: + _node = createnode(node, "ChineseWallTypes") + return _node + return None + + def policy_get_chwall_types(self): + strings = [] + node = self.policy_get_chwalltypes_node() + if node: + strings = self.policy_get_types(node) + return strings + + def policy_get_chwall_cfs_node(self): + node = self.policy_get_chwall_node() + if node: + return policy_dom_get(node, "ConflictSets") + return None + + def policy_get_chwall_cfses(self): + cfs = [] + node = self.policy_get_chwall_cfs_node() + if node: + i = 0 + while i < len(node.childNodes): + _cfs = {} + if node.childNodes[i].nodeName == "Conflict": + _cfs['name'] = node.childNodes[i].getAttribute('name') + _cfs['types'] = self.policy_get_types(node.childNodes[i]) + cfs.append(_cfs) + i += 1 + return cfs + + def policy_rm_chwall_cfs(self, name, types): + rc = -1 + node = self.policy_get_chwall_cfs_node() + if node: + i = 0 + while i < len(node.childNodes): + if node.childNodes[i].nodeName == "Conflict": + if name and \ + name != node.childNodes[i].getAttribute('name'): + i += 1 + continue + else: + _types = self.policy_get_types(node.childNodes[i]) + if compare_lists(types, _types) == 0: + node.removeChild(node.childNodes[i]) + rc = 0 + break + i += 1 + return rc + + # + # Subject Label-related functions + # + + def policy_get_securitylabeltemplate(self): + doc = self.get_dom() + return policy_dom_get(doc.documentElement, "SecurityLabelTemplate") + + def policy_get_subjectlabels(self, mustcreate=False): + node = self.policy_get_securitylabeltemplate() + if node: + _node = policy_dom_get(node, "SubjectLabels") + if not _node and mustcreate == True: + _node = createnode(node, "SubjectLabels") + return _node + return None + + def policy_get_bootstrap_vmlabel(self): + vmlabel = None + node = self.policy_get_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.policy_get_subjectlabels() + if node: + i = 0 + while i < len(node.childNodes): + if node.childNodes[i].nodeName == "VirtualMachineLabel": + name = policy_dom_get(node.childNodes[i], "Name") + strings.append(name.childNodes[0].nodeValue) + i += 1 + return strings + + def policy_get_virtualmachinelabel_names_sorted(self): + bootstrap = self.policy_get_bootstrap_vmlabel() + vmnames = self.policy_get_virtualmachinelabel_names() + if bootstrap not in vmnames: + return -xserror.XSERR_POLICY_INCONSISTENT + vmnames.remove(bootstrap) + vmnames.sort() + vmnames.insert(0, bootstrap) + return vmnames + + def policy_get_virtualmachinelabels(self): + res = [] + node = self.policy_get_subjectlabels() + if node: + i = 0 + while i < len(node.childNodes): + if node.childNodes[i].nodeName == "VirtualMachineLabel": + _res = {} + _res['type'] = SHYPE_LABEL_VM + name = policy_dom_get(node.childNodes[i], "Name") + _res['name'] = name.childNodes[0].nodeValue + stes = policy_dom_get(node.childNodes[i], "SimpleTypeEnforcementTypes") + if stes: + _res['stes'] = self.policy_get_types(stes) + chws = policy_dom_get(node.childNodes[i], "ChineseWallTypes") + if chws: + _res['chws'] = self.policy_get_types(chws) + res.append(_res) + i += 1 + return res + + def policy_get_virtualmachinelabel_node(self, vmname): + rc = -1 + node = self.policy_get_subjectlabels() + if node: + i = 0 + while i < len(node.childNodes): + if node.childNodes[i].nodeName == "VirtualMachineLabel": + name = policy_dom_get(node.childNodes[i], "Name") + if name.childNodes[0].nodeValue == vmname: + return (node,i) + i += 1 + return None + + def policy_add_virtualmachinelabel(self, name, stes, chws): + rc = -1 + node = self.policy_get_subjectlabels() + if node: + xml = "\n" + \ + " " + name + "\n" + \ + " \n" + \ + convtoxml("Type",stes) + \ + " \n" + \ + " \n" + \ + convtoxml("Type",chws) + \ + " \n" + \ + "" + frag = minidom.parseString(xml) + frag.childNodes[0].nodeType = Node.DOCUMENT_FRAGMENT_NODE + node.appendChild(frag.childNodes[0]) + rc = 0 + return rc + + def policy_rm_stetype_from_vm(self, vmname, type): + rc = -1 + res = self.policy_get_virtualmachinelabel_node(vmname) + if res: + node, i = res + ste_node = policy_dom_get(node.childNodes[i], + "SimpleTypeEnforcementTypes") + if ste_node: + rc = self.policy_rm_type(ste_node, type) + return rc + + def policy_rm_chwalltype_from_vm(self, vmname, type): + rc = -1 + res = self.policy_get_virtualmachinelabel_node(vmname) + if res: + node, i = res + ste_node = policy_dom_get(node.childNodes[i], + "ChineseWallTypes") + if ste_node: + rc = self.policy_rm_type(ste_node, type) + return rc + + # + # Object Label-related functions + # + + def policy_get_objectlabels(self, mustcreate=False): + node = self.policy_get_securitylabeltemplate() + if node: + _node = policy_dom_get(node, "ObjectLabels") + if not _node and mustcreate == True: + _node = createnode(node, "ObjectLables") + return _node + return None + + + # Get the names of all resource labels in an array + def policy_get_resourcelabel_node(self, resname): + rc = -1 + node = self.policy_get_objectlabels() + if node: + i = 0 + while i < len(node.childNodes): + if node.childNodes[i].nodeName == "ResourceLabel": + name = policy_dom_get(node.childNodes[i], "Name") + if name.childNodes[0].nodeValue == resname: + return (node,i) + i += 1 + return None + + def policy_get_resourcelabel_names(self): + strings = [] + node = self.policy_get_objectlabels() + if node: + i = 0 + while i < len(node.childNodes): + if node.childNodes[i].nodeName == "ResourceLabel": + name = policy_dom_get(node.childNodes[i], "Name") + strings.append(name.childNodes[0].nodeValue) + i += 1 + return strings + + def policy_get_resourcelabels(self): + res = [] + node = self.policy_get_objectlabels() + if node: + i = 0 + while i < len(node.childNodes): + if node.childNodes[i].nodeName == "ResourceLabel": + _res = {} + _res['type'] = SHYPE_LABEL_RES + name = policy_dom_get(node.childNodes[i], "Name") + _res['name'] = name.childNodes[0].nodeValue + stes = policy_dom_get(node.childNodes[i], "SimpleTypeEnforcementTypes") + if stes: + _res['stes'] = self.policy_get_types(stes) + res.append(_res) + i += 1 + return res + + def policy_add_resourcelabel(self, name, stes): + rc = -1 + node = self.policy_get_objectlabels() + if node: + xml = "\n" + \ + " " + name + "\n" + \ + " \n" + \ + convtoxml("Type", stes) + \ + " \n" + \ + "\n" + frag = minidom.parseString(xml) + frag.childNodes[0].nodeType = Node.DOCUMENT_FRAGMENT_NODE + node.appendChild(frag.childNodes[0]) + rc = 0 + return rc + + def policy_rm_stetype_from_resource(self, resname, type): + rc = -1 + res = self.policy_get_resourcelabel_node(resname) + if res: + node, i = res + ste_node = policy_dom_get(node.childNodes[i], + "SimpleTypeEnforcementTypes") + if ste_node: + rc = self.policy_rm_type(ste_node, type) + return rc + + + + + def toxml(self): + dom = self.get_dom() + if dom: + return dom.toxml() + return None + + def save(self): + rc = -1 + if self.name: + path = path_from_policy_name(self.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 + + # load the policy into the hypervisor + # if successful, set the policy's flag showing that this is + # the loaded policy + def loadintohv(self): + (ret, output) = commands.getstatusoutput( + security.xensec_tool + + " loadpolicy " + + self.get_filename(".bin")) + if ret != 0: + return -xserror.XSERR_POLICY_LOAD_FAILED + else: + self.isloaded = True + return 0 + + #Collect the STE types in use by VMs and resources + def collect_stetypes_in_use(self): + res = [] + node = self.policy_get_subjectlabels() + if node: + i = 0 + while i < len(node.childNodes): + if node.childNodes[i].nodeName == "VirtualMachineLabel": + stes = policy_dom_get(node.childNodes[i], "SimpleTypeEnforcementTypes") + if stes: + used_stes = self.policy_get_types(stes) + for u in used_stes: + if u not in res: + res.append(u) + i += 1 + + node = self.policy_get_objectlabels() + if node: + i = 0 + while i < len(node.childNodes): + if node.childNodes[i].nodeName == "ResourceLabel": + stes = policy_dom_get(node.childNodes[i], "SimpleTypeEnforcementTypes") + if stes: + used_stes = self.policy_get_types(stes) + for u in used_stes: + if u not in res: + res.append(u) + i += 1 + return res + + def collect_chwtypes_in_use(self): + res = [] + node = self.policy_get_subjectlabels() + if node: + i = 0 + while i < len(node.childNodes): + if node.childNodes[i].nodeName == "VirtualMachineLabel": + chws = policy_dom_get(node.childNodes[i], "ChineseWallTypes") + if chws: + used_chws = self.policy_get_types(chws) + for u in used_chws: + if u not in res: + res.append(u) + i += 1 + return res + + + def policy_write_map_and_bin(self, dontwrite=False): + """ Write the policie's map and binary files -- compile the policy. + This function is also suitable for validating the policy + by not writing the files (dontwrite=True). + """ + rc = 0 + mapfile = "" + bin = "" + primpolcode = ACM_POLICY_UNDEFINED + secpolcode = ACM_POLICY_UNDEFINED + + 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() + vmnames = self.policy_get_virtualmachinelabel_names_sorted() + 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" % 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 = 4 + len(self.get_name()) + 1 + length = (length + 7) & ~7 + 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 = 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 + for _c in cfses: + conf = _c['types'] + for c in chws: + if c in conf: + chw_bin_body += struct.pack("!h",1) + else: + chw_bin_body += struct.pack("!h",0) + + chw_bin += chw_bin_body + + # 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; + + #Write binary header: + headerformat="!iiiiiiii" + 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 + + hdr_bin = struct.pack(headerformat, + ACM_POLICY_VERSION, + ACM_MAGIC, + totallen_bin, + polref_offset, + primpolcode, + primpoloffset, + secpolcode, + secpoloffset) + + if not dontwrite: + #write the map file + f = open(self.get_filename(".map"),"w") + if f: + f.write(mapfile) + f.close() + else: + return -xserror.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 -xserror.XSERR_FILE_ERROR + return rc 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 @@ -34,6 +34,10 @@ from xen.xend.XendTask import XendTask from xen.xend.XendAPIConstants import * from xen.util.xmlrpclib2 import stringify +from xen.util import acmpolicy, security, dictio +from xen.util.acmpolicy import ACMPolicy, ACMPolicyAdminInstance +import base64 + AUTH_NONE = 'none' AUTH_PAM = 'pam' @@ -283,6 +287,28 @@ def valid_debug(func): _check_ref(lambda r: r in XendAPI._debug, 'TASK_HANDLE_INVALID', func, *args, **kwargs) +def valid_xspolicy(func): + """Decorator to verify if acmpolicy_ref is valid before calling + method. + + @param func: function with params: (self, session, acmpolicy_ref) + @rtype: callable object + """ + def check_acmpolicy_ref(self, session, acmpolicy_ref, *args, **kwargs): + xennode = XendNode.instance() + acmadmin = ACMPolicyAdminInstance() + if type(acmpolicy_ref) == type(str()) and \ + acmadmin.has_ref(acmpolicy_ref): + return func(self, session, acmpolicy_ref, *args, **kwargs) + else: + return {'Status': 'Failure', + 'ErrorDescription': XEND_ERROR_XSPOLICY_INVALID} + + if hasattr(func, 'api'): + check_acmpolicy_ref.api = func.api + + return check_acmpolicy_ref + # ----------------------------- # Bridge to Legacy XM API calls # ----------------------------- @@ -362,6 +388,7 @@ class XendAPI(object): 'SR' : valid_sr, 'PIF' : valid_pif, 'task' : valid_task, + 'XSPolicy': valid_xspolicy, 'debug' : valid_debug, } @@ -884,7 +911,8 @@ class XendAPI(object): 'platform_clock_offset', 'platform_enable_audio', 'platform_keymap', - 'otherConfig'] + 'otherConfig', + 'security_label'] VM_methods = [('clone', 'VM'), ('start', None), @@ -900,7 +928,8 @@ class XendAPI(object): ('remove_from_otherConfig', None)] VM_funcs = [('create', 'VM'), - ('get_by_name_label', 'Set(VM)')] + ('get_by_name_label', 'Set(VM)'), + ('chg_security_label', 'str')] # parameters required for _create() VM_attr_inst = [ @@ -936,7 +965,8 @@ class XendAPI(object): 'platform_keymap', 'grub_cmdline', 'PCI_bus', - 'otherConfig'] + 'otherConfig', + 'security_label'] def VM_get(self, name, session, vm_ref): return xen_api_success( @@ -1235,7 +1265,33 @@ 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): + xendom = XendDomain.instance() + xeninfo = xendom.get_vm_by_uuid(vm_ref) + if not xeninfo: + return xen_api_error(['VM_HANDLE_INVALID', vm_ref]) + return xen_api_success(xeninfo.get_security_label()) + + def VM_set_security_label(self, session, vm_ref, sec_label): + xendom = XendDomain.instance() + xeninfo = xendom.get_vm_by_uuid(vm_ref) + if not xeninfo: + return xen_api_error(['VM_HANDLE_INVALID', vm_ref]) + rc = xeninfo.set_security_label(sec_label) + return xen_api_success(rc) + + def VM_chg_security_label(self, session, vm_ref, sec_label): + xendom = XendDomain.instance() + xeninfo = xendom.get_vm_by_uuid(vm_ref) + if not xeninfo: + return xen_api_error(['VM_HANDLE_INVALID', vm_ref]) + cur = xeninfo.get_security_label() + rc = xeninfo.set_security_label(sec_label) + if rc != 0: + return xen_api_error(rc) + return xen_api_success(rc) + def VM_create(self, session, vm_struct): xendom = XendDomain.instance() domuuid = XendTask.log_progress(0, 100, @@ -1943,6 +1999,130 @@ class XendAPI(object): XendNode.instance().save() return xen_api_success_void() + # Xen API: Class XSPolicy + # ---------------------------------------------------------------- + + XSPolicy_attr_ro = [ 'xml', + 'map', + 'binary' ] + + XSPolicy_attr_rw = [ 'shypeheader' ] + + XSPolicy_attr_inst = [ ] + + XSPolicy_funcs = [ ('get_xstype', 'int'), + ('set_xspolicy', 'int'), + ('get_xspolicy', 'Struct'), + ('get_resource_label', 'str'), + ('set_resource_label', 'int'), + ('rm_resource_label', 'int'), + ('get_labeled_resources', 'list') ] + + #Helper function - not exported + def xspolicy_from_ref(self, acm_ref): + poladmin = ACMPolicyAdminInstance() + return poladmin.policy_from_ref(acm_ref) + + def XSPolicy_get_xstype(self, session): + rc = 0 + poladmin = ACMPolicyAdminInstance() + if poladmin.isACMEnabled() == True: + rc |= acmpolicy.POLICY_SHYPE + return xen_api_success(rc) + + def XSPolicy_set_xspolicy(self, session, acmtype, xml, flags, overwrite): + rc = 0 + if acmtype == acmpolicy.POLICY_SHYPE: + poladmin = ACMPolicyAdminInstance() + rc = poladmin.add_xmlpolicy_to_system(xml, flags, overwrite) + return xen_api_success(rc) + + def XSPolicy_get_xspolicy(self, session, acmtype): + polstate = {} + poladmin = ACMPolicyAdminInstance() + refs = poladmin.get_policies_refs() + if refs and len(refs) > 0: + polstate["acmpolicy_ref"] = refs[0] + polstate["flags"] = poladmin.get_policy_flags(refs[0]) + return xen_api_success(polstate) + + def XSPolicy_rm_acmbootpolicy(self, session, acmtype): + rc = 0 + if acmtype == acmpolicy.POLICY_SHYPE: + poladmin = ACMPolicyAdminInstance() + rc = poladmin.rm_shypebootpolicy() + return xen_api_success(rc) + + def XSPolicy_get_labeled_resources(self, session): + return xen_api_success(security.get_labeled_resources()) + + def XSPolicy_set_resource_label(self, session, resource, xapilabel): + rc = security.set_resource_label_xapi(resource, xapilabel) + return xen_api_success(rc) + + def XSPolicy_get_resource_label(self, session, resource): + res = security.get_resource_label_xapi(resource) + return xen_api_success(res) + + def XSPolicy_rm_resource_label(self, session, resource): + rc = security.rm_resource_label(resource) + return xen_api_success(rc) + + def XSPolicy_get_shypeheader(self, session, acm_ref): + header = {} + acmpol = self.xspolicy_from_ref(acm_ref) + if acmpol: + v = acmpol.policy_dom_get_name() + if v: + header["name"] = v + v = acmpol.policy_dom_get_url() + if v: + header["url" ] = v + v = acmpol.policy_dom_get_date() + if v: + header["date"] = v + v = acmpol.policy_dom_get_version() + if v: + header["version"] = v + return xen_api_success(header) + + def XSPolicy_set_shypeheader(self, session, acm_ref, header): + rc = -1 + acmpol = self.xspolicy_from_ref(acm_ref) + if acmpol: + #NOT the name; name stays constant. + if header.has_key("url"): + rc = acmpol.policy_dom_set_url (header["url" ]) + if header.has_key("date"): + rc = acmpol.policy_dom_set_date(header["date"]) + if header.has_key("version"): + rc = acmpol.policy_dom_set_version(header["version"]) + if rc == 0: + # No need to compile policy, just save it + acmpol.save() + return xen_api_success(rc) + + def XSPolicy_get_xml(self, session, acm_ref): + policyxml = "" + acmpol = self.xspolicy_from_ref(acm_ref) + if acmpol: + policyxml = acmpol.toxml() + return xen_api_success(policyxml) + + def XSPolicy_get_map(self, session, acm_ref): + policymap = "" + acmpol = self.xspolicy_from_ref(acm_ref) + if acmpol: + policymap = acmpol.get_map() + return xen_api_success(policymap) + + def XSPolicy_get_binary(self, session, acm_ref): + polb64 = "" + acmpol = self.xspolicy_from_ref(acm_ref) + if acmpol: + polbin = acmpol.get_bin() + polb64 = base64.b64encode(polbin) + return xen_api_success(polb64) # Xen API: Class debug # ---------------------------------------------------------------- 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 @@ -61,4 +61,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/xm-test/tests/security-acm/07_security-acm_xapi.py =================================================================== --- /dev/null +++ root/xen-unstable.hg/tools/xm-test/tests/security-acm/07_security-acm_xapi.py @@ -0,0 +1,263 @@ +#!/usr/bin/python + +# Copyright (C) International Business Machines Corp., 2007 +# Author: Stefan Berger + +# VM creation test with labeled VM and labeled VBD + +from XmTestLib import xapi +from XmTestLib.XenAPIDomain import XmTestAPIDomain +from XmTestLib import * +from xen.xend import XendAPIConstants +from xen.util import acmpolicy, security +import commands +import os + +vm_sec_label = security.shype_policy_identifier + ":xm-test:red" +vm_sec_label_test = security.shype_policy_identifier + ":xm-test:green" +vbd_sec_label = security.shype_policy_identifier + ":xm-test:red" +vbd_sec_label_bad = security.shype_policy_identifier + ":xm-test:green" + +#Note: +# If during the suspend/resume operations 'red' instead of 'green' is +# used, the Chinese Wall policy goes into effect and disallows the +# suspended VM from being resumed... + +try: + # XmTestAPIDomain tries to establish a connection to XenD + domain = XmTestAPIDomain(extraConfig={ 'security_label' : vm_sec_label }) +except Exception, e: + SKIP("Skipping test. Error: %s" % str(e)) + +vm_uuid = domain.get_uuid() + +session = xapi.connect() +acmtype = session.xenapi.XSPolicy.get_xstype() +if int(acmtype) & acmpolicy.POLICY_SHYPE == 0: + SKIP("ACM not enabled/compiled in Xen") + +f = open("xm-test-security_policy.xml", 'r') +if f: + newpolicyxml = f.read() + f.close() +else: + FAIL("Could not read 'xm-test' policy") + +rc = session.xenapi.XSPolicy.set_xspolicy(acmpolicy.POLICY_SHYPE, + newpolicyxml, 3, 1) +if rc != "0": + FAIL("Could not upload the new policy") + +policystate = session.xenapi.XSPolicy.get_xspolicy(acmpolicy.POLICY_SHYPE) +acm_ref = policystate['acmpolicy_ref'] + +labels = session.xenapi.XSPolicy.get_labeled_resources() +print "labels = %s" % labels + +rc = session.xenapi.XSPolicy.rm_resource_label("phy:/dev/ram0") + +rc = session.xenapi.XSPolicy.set_resource_label("phy:/dev/ram0", + vbd_sec_label_bad) + +res = session.xenapi.XSPolicy.get_resource_label("phy:/dev/ram0") +if res != vbd_sec_label_bad: + FAIL("lookup_labeled_resource returned unexpected result %s" % res) + +res = session.xenapi.VM.get_security_label(vm_uuid) + +if res != vm_sec_label: + FAIL("VM.get_security_label returned wront security label '%s'." % res) + +res = session.xenapi.VM.set_security_label(vm_uuid, vm_sec_label_test) + +res = session.xenapi.VM.get_security_label(vm_uuid) +if res != vm_sec_label_test: + FAIL("VM does not show expected label '%s' but '%s'." % + vm_sec_label_test, res) + +res = session.xenapi.VM.set_security_label(vm_uuid, "") +print "res from rm_security_label: %s" % res + +res = session.xenapi.VM.get_security_label(vm_uuid) +if res != "": + FAIL("Unexpected VM security label after removal: %s" % res) + +res = session.xenapi.VM.chg_security_label(vm_uuid, vm_sec_label) +print "res from chg_security_label: %s" % res + +res = session.xenapi.VM.get_security_label(vm_uuid) +if res != vm_sec_label: + FAIL("VM has wrong label '%s', expected '%s'." % (res, vm_sec_label)) + +sr_uuid = session.xenapi.SR.get_by_name_label("Local") +if len(sr_uuid) == 0: + FAIL("Could not get a handle on SR 'Local'") + +vdi_rec = { 'name_label' : "My disk", + 'SR' : sr_uuid[0], + 'virtual_size': 1 << 10, + 'sector_size' : 512, + 'type' : 0, + 'shareable' : 0, + 'read-only' : 0 +} + +vdi_ref = session.xenapi.VDI.create(vdi_rec) + +res = session.xenapi.SR.get_VDIs(sr_uuid[0]) +if vdi_ref not in res: + session.xenapi.VDI.destroy(vdi_ref) + FAIL("SR_get_VDI does not show new VDI") + +res = session.xenapi.VDI.get_name_label(vdi_ref) +if res != vdi_rec['name_label']: + session.xenapi.VDI.destroy(vdi_ref) + FAIL("VDI_get_name_label return wrong information") + +# Attach a VBD to the VM + +vbd_rec = { 'VM' : vm_uuid, + 'VDI' : vdi_ref, + 'device': "xvda1", + 'mode' : 1, + 'driver': 1, + 'image' : "phy:/dev/ram0" +} + +vbd_ref = session.xenapi.VBD.create(vbd_rec) + +try: + domain.start(noConsole=True) + # Should not get here. + session.xenapi.VDI.destroy(vdi_ref) + FAIL("Could start VM with a VBD that it is not allowed to access.") +except: + pass + print "Could not create domain -- that's good" + + +rc = session.xenapi.XSPolicy.rm_resource_label("phy:/dev/ram0") + +rc = session.xenapi.XSPolicy.set_resource_label("phy:/dev/ram0", + vbd_sec_label) + +res = session.xenapi.XSPolicy.get_resource_label("phy:/dev/ram0") +if res != vbd_sec_label: + session.xenapi.VDI.destroy(vdi_ref) + FAIL("lookup_labeled_resource returned unexpected result %s" % res) + +print "Trying to start domain now with label changed" +domain.start(noConsole=True) + +console = domain.getConsole() + +domName = domain.getName() + +try: + run = console.runCmd("cat /proc/interrupts") +except ConsoleError, e: + saveLog(console.getHistory()) + FAIL("Could not access proc-filesystem") + +# Try to relabel while VM is running +try: + res = session.xenapi.VM.set_security_label(vm_uuid, vm_sec_label_test) + session.xenapi.VDI.destroy(vdi_ref) + FAIL("Could label VM while it's running.") +except: + pass + + +try: + status, output = traceCommand("xm suspend %s" % domName, + timeout=30) +except TimeoutError, e: + saveLog(consoleHistory) + session.xenapi.VDI.destroy(vdi_ref) + FAIL("Failure from suspending VM: %s." % str(e)) + +# Try to relabel while VM is suspended -- this should work +res = session.xenapi.VM.set_security_label(vm_uuid, vm_sec_label_test) + +res = session.xenapi.VM.get_security_label(vm_uuid) +if res != vm_sec_label_test: + session.xenapi.VDI.destroy(vdi_ref) + FAIL("VM (suspended) has label '%s', expected '%s'." % + (res, vm_sec_label_test)) + +status, output = traceCommand("xm list") + +#Try to resume now -- should fail due to denied access to block device +try: + status, output = traceCommand("xm resume %s" % domName, + timeout=30) + if status == 0: + session.xenapi.VDI.destroy(vdi_ref) + FAIL("Could resume re-labeled VM: %s" % output) +except Exception, e: + session.xenapi.VDI.destroy(vdi_ref) + FAIL("Error resuming the VM: %s." % str(e)) + +# Relabel VM so it would resume +res = session.xenapi.VM.set_security_label(vm_uuid, vm_sec_label) + +res = session.xenapi.VM.get_security_label(vm_uuid) +if res != vm_sec_label: + session.xenapi.VDI.destroy(vdi_ref) + FAIL("VM (suspended) has label '%s', expected '%s'." % + (res, vm_sec_label)) + +# Relabel the resource so VM should not resume +rc = session.xenapi.XSPolicy.set_resource_label("phy:/dev/ram0", + vbd_sec_label_bad) + +#Try to resume now -- should fail due to denied access to block device +try: + status, output = traceCommand("xm resume %s" % domName, + timeout=30) + if status == 0: + session.xenapi.VDI.destroy(vdi_ref) + FAIL("Could resume re-labeled VM: %s" % output) +except Exception, e: + session.xenapi.VDI.destroy(vdi_ref) + FAIL("Error resuming the VM: %s." % str(e)) + + +status, output = traceCommand("xm list") + +# Relabel the resource so VM can resume +rc = session.xenapi.XSPolicy.set_resource_label("phy:/dev/ram0", + vbd_sec_label) + +res = session.xenapi.XSPolicy.get_resource_label("phy:/dev/ram0") +if res != vbd_sec_label: + session.xenapi.VDI.destroy(vdi_ref) + FAIL("'phy:/dev/ram0' has label '%s', expected '%s'." % + (res, vbd_sec_label)) + +#Try to resume now -- should work +try: + status, output = traceCommand("xm resume %s" % domName, + timeout=30) + if status != 0: + session.xenapi.VDI.destroy(vdi_ref) + FAIL("Could not resume re-labeled VM: %s" % output) + print "Successfully resumed domain" +except Exception, e: + session.xenapi.VDI.destroy(vdi_ref) + FAIL("Error resuming the VM: %s." % str(e)) + + +status, output = traceCommand("xm list") + +console = domain.getConsole() + +try: + run = console.runCmd("cat /proc/interrupts") +except ConsoleError, e: + saveLog(console.getHistory()) + session.xenapi.VDI.destroy(vdi_ref) + FAIL("Could not access proc-filesystem") + +domain.stop() +domain.destroy() Index: root/xen-unstable.hg/tools/python/xen/util/xserror.py =================================================================== --- /dev/null +++ root/xen-unstable.hg/tools/python/xen/util/xserror.py @@ -0,0 +1,33 @@ +#============================================================================ +# 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 +#============================================================================ + +# ACM error codes as used by the Xen-API +XSERR_SUCCESS = 0 +XSERR_BAD_XML = 1 +XSERR_POLICY_INCONSISTENT = 2 +XSERR_FILE_ERROR = 3 +XSERR_BAD_RESOURCE_FORMAT = 4 # Badly formatted resource +XSERR_BAD_LABEL_FORMAT = 5 +XSERR_RESOURCE_NOT_LABELED = 6 +XSERR_RESOURCE_ALREADY_LABELED = 7 +XSERR_WRONG_POLICY_TYPE = 8 +XSERR_BOOTPOLICY_INSTALLED = 9 +XSERR_NO_DEFAULT_BOOT_TITLE = 10 +XSERR_POLICY_INCONSISTENT = 11 +XSERR_FILE_ERROR = 12 +XSERR_POLICY_LOAD_FAILED = 13 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 @@ -25,7 +25,8 @@ import shutil from xen.lowlevel import acm from xen.xend import sxp from xen.xend.XendLogging import log -from xen.util import dictio +from xen.xend.XendError import VmError +from xen.util import dictio, xserror #global directories and tools for security management policy_dir_prefix = "/etc/xen/acm-security/policies" @@ -60,6 +61,10 @@ policy_name_re = re.compile(".*[chwall|s #other global variables NULL_SSIDREF = 0 +# Policy identifier +shype_policy_identifier = "sHype" + + log = logging.getLogger("xend.util.security") # Our own exception definition. It is masked (pass) if raised and @@ -108,12 +113,16 @@ def on(): # Assumes a 'security' info [security access_control ...] [ssidref ...] -def get_security_info(info, field): +def get_security_info(info, field, dominfo=None): """retrieves security field from self.info['security']) allowed search fields: ssidref, label, policy """ if isinstance(info, dict): - security = info['security'] + import xen.xend.XendConfig + if isinstance(info, xen.xend.XendConfig.XendConfig): + security = sxp.child_value(info.to_sxp(dominfo), 'security') + else: + security = info['security'] elif isinstance(info, list): security = sxp.child_value(info, 'security') if not security: @@ -381,8 +390,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: @@ -668,3 +678,174 @@ def res_security_check(resource, domain_ 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 policy != 'NULL': + #raise ACMError("Security is off, but resource is labeled") + rtnval = 0 + + return rtnval + + +def set_resource_label_xapi(resource, reslabel_xapi): + """Assign a resource label to a physical resource + @param resource: The name of a resource, i.e., "phy:/dev/hda" + + @param reslabel_xapi: A resource label foramtted as in all other parts of + the Xen-API, i.e., "sHype:xm-test:blue" + @rtype: int + @return Success (0) or failure value (< 0) + """ + typ, policyref, label = reslabel_xapi.split(":") + if typ != shype_policy_identifier: + return -xserror.XSERR_WRONG_POLICY_TYPE + if not policyref or not label: + return -xserror.XSERR_BAD_LABEL_FORMAT + return set_resource_label(resource, policyref, label) + +def set_resource_label(resource, policyref, label): + """Assign a resource label to a physical resource + @param resource: The name of a resource, i.e., "phy:/dev/hda" + @param policyref : The name of the policy + @param label : the resource label within the policy + + @rtype: int + @return Success (0) or failure value (< 0) + """ + try: + resource = unify_resname(resource) + except: + return -xserror.XSERR_BAD_RESOURCE_FORMAT + access_control = {} + try: + access_control = dictio.dict_read("resources", res_label_filename) + except: + pass + if label != "": + new_entry = { resource : tuple([policyref, label]) } + access_control.update(new_entry) + else: + del access_control[resource] + dictio.dict_write(access_control, "resources", res_label_filename) + return xserror.XSERR_SUCCESS + +def rm_resource_label(resource): + """Assign a resource label to a physical resource + @param resource: The name of a resource, i.e., "phy:/dev/hda" + + @rtype: int + @return Success (0) or failure value (< 0) + """ + try: + resource = unify_resname(resource) + except Exception, e: + return -xserror.XSERR_BAD_RESOURCE_FORMAT + try: + access_control = dictio.dict_read("resources", res_label_filename) + except: + return -xserror.XSERR_RESOURCE_NOT_LABELED + if access_control.has_key(resource): + del access_control[resource] + dictio.dict_write(access_control, "resources", res_label_filename) + else: + return -xserror.XSERR_RESOURCE_NOT_LABELED + return xserror.XSERR_SUCCESS + +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., "sHype:xm-test:blue" + + @rtype: string + @return the string representing policy type, policy name and label of + the resource + """ + res = get_resource_label(resource) + if len(res) >= 2: + return shype_policy_identifier + ":" + res[0] + ":" + res[1] + return "" + +def get_resource_label(resource): + """Get the assigned resource label of a physical 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) + except Exception, e: + return [] + try: + access_control = dictio.dict_read("resources", res_label_filename) + except: + return [] + if access_control.has_key(resource): + return access_control[resource] + return [] + +def get_labeled_resources(): + """Get the names of all labeled resources + @rtype: list + @return list of labeled resources + """ + try: + access_control = dictio.dict_read("resources", res_label_filename) + except: + return [] + return access_control.keys() + + +def prep_seclabel(seclab): + """ rewrite the security label for xend to use + @param seclab: security label in the format used by then Xen-API, + i.e., "sHype:xm-test:blue" + @rtype: string + @return: netsted lists + """ + typ, policy, label = seclab.split(":") + if not policy or not label: + raise VmError("security label has wrong format") + config_access_control = ['access_control', + ['policy', policy], + ['label', label] ] + ssidref = label2ssidref(label, policy, 'dom') + if not ssidref: + log.error("ERROR calculating ssidref from access_control.") + raise VmError("Could not calculate ssidref") + security_label = ['security', + [ config_access_control, + ['ssidref' , ssidref ] ] ] + return security_label 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 @@ -156,8 +156,6 @@ XENAPI_CFG_TYPES = { 'actions_after_reboot': str, 'actions_after_suspend': str, 'actions_after_crash': str, - 'tpm_instance': int, - 'tpm_backend': int, 'PV_bootloader': str, 'PV_kernel': str, 'PV_ramdisk': str, @@ -171,6 +169,7 @@ XENAPI_CFG_TYPES = { 'platform_enable_audio': bool0, 'platform_keymap': str, 'pci_bus': str, + 'security_label': str, 'tools_version': dict, 'otherconfig': dict, } @@ -426,7 +425,6 @@ class XendConfig(dict): self['memory_dynamic_min'] = (dominfo['mem_kb'] + 1023)/1024 self['memory_dynamic_max'] = (dominfo['maxmem_kb'] + 1023)/1024 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'] @@ -825,6 +823,12 @@ class XendConfig(dict): """ sxpr = ['domain'] + if self.get("security_label"): + from xen.util import security + sec_label = security.prep_seclabel(self.get("security_label")) + if sec_label: + sxpr.append(sec_label) + # TODO: domid/dom is the same thing but called differently # depending if it is from xenstore or sxpr. @@ -1017,6 +1021,9 @@ class XendConfig(dict): else: dev_info['mode'] = 'r' + if cfg_xenapi.get('security_label'): + dev_info['security_label'] = cfg_xenapi.get('security_label') + dev_uuid = cfg_xenapi.get('uuid', uuid.createString()) dev_info['uuid'] = dev_uuid target['devices'][dev_uuid] = (dev_type, dev_info) 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 @@ -1355,11 +1355,15 @@ class XendDomainInfo: "supported by your CPU and enabled in your " "BIOS?") - self.domid = xc.domain_create( - domid = 0, - ssidref = security.get_security_info(self.info, 'ssidref'), - handle = uuid.fromString(self.info['uuid']), - hvm = int(hvm)) + try: + self.domid = xc.domain_create( + domid = 0, + ssidref = security.get_security_info(self.info, 'ssidref', self), + handle = uuid.fromString(self.info['uuid']), + hvm = int(hvm)) + except: + # may get here if due to ACM the operation is not permitted + pass if self.domid < 0: raise VmError('Creating domain failed: name=%s' % @@ -1983,6 +1987,22 @@ class XendDomainInfo: return self.info.get('pci_bus', '') def get_tools_version(self): return self.info.get('tools_version', {}) + + def get_security_label(self): + return self.info.get('security_label', '') + + def set_security_label(self, seclab): + rc = 0 + if self.state != DOM_STATE_HALTED: + raise VmError("VM must be halted to modify label") + if not seclab or seclab == "": + del self.info['security_label'] + else: + typ, policy, label = seclab.split(":") + if not policy or not label: + raise VmError("Security label has wrong format") + self.info['security_label'] = seclab + return rc def get_on_shutdown(self): after_shutdown = self.info.get('action_after_shutdown') 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}) + domain_label = self.vm.get_security_label() + if domain_label: + (label, ssidref, policy) = \ + security.get_res_security_details(uname) + domain_label = self.vm.get_security_label() + rc = security.res_security_check_xapi(label, ssidref, policy, + domain_label) + if rc == 0: + raise VmError("VM's access to block device denied.") + else: + (label, ssidref, policy) = security.get_res_security_details(uname) devid = blkif.blkdev_name_to_number(dev) if devid is None: Index: root/xen-unstable.hg/tools/xm-test/lib/XmTestLib/XenAPIDomain.py =================================================================== --- root.orig/xen-unstable.hg/tools/xm-test/lib/XmTestLib/XenAPIDomain.py +++ root/xen-unstable.hg/tools/xm-test/lib/XmTestLib/XenAPIDomain.py @@ -24,6 +24,7 @@ import sys from XmTestLib import * from xen.util.xmlrpclib2 import ServerProxy from types import DictType +from acm import * class XenAPIConfig: @@ -39,6 +40,9 @@ class XenAPIConfig: 'kernel' : 'PV_kernel', 'ramdisk': 'PV_ramdisk', 'root' : 'PV_args'} + if isACMEnabled(): + #A default so every VM can start with ACM enabled + self.opts["security_label"] = "sHype:xm-test:red" def setOpt(self, name, value): """Set an option in the config"""