ia64/xen-unstable

view tools/python/xen/util/security.py @ 15805:8f1c807ace97

[ACM/XEND] Fix case where resource label file does not exist.

Fix the case where the resource label file does not exist but its
contents would be needed for access control evaluations.

Signed-off-by: Stefan Berger <stefanb@us.ibm.com>
author kfraser@localhost.localdomain
date Thu Aug 30 15:39:13 2007 +0100 (2007-08-30)
parents f0298301ba8b
children
line source
1 #===========================================================================
2 # This library is free software; you can redistribute it and/or
3 # modify it under the terms of version 2.1 of the GNU Lesser General Public
4 # License as published by the Free Software Foundation.
5 #
6 # This library is distributed in the hope that it will be useful,
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
9 # Lesser General Public License for more details.
10 #
11 # You should have received a copy of the GNU Lesser General Public
12 # License along with this library; if not, write to the Free Software
13 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
14 #============================================================================
15 # Copyright (C) 2006 International Business Machines Corp.
16 # Author: Reiner Sailer
17 # Author: Bryan D. Payne <bdpayne@us.ibm.com>
18 # Author: Stefan Berger <stefanb@us.ibm.com>
19 #============================================================================
21 import commands
22 import logging
23 import os, string, re
24 import threading
25 import struct
26 import stat
27 from xen.lowlevel import acm
28 from xen.xend import sxp
29 from xen.xend import XendConstants
30 from xen.xend.XendLogging import log
31 from xen.xend.XendError import VmError
32 from xen.util import dictio, xsconstants
33 from xen.xend.XendConstants import *
35 #global directories and tools for security management
36 policy_dir_prefix = "/etc/xen/acm-security/policies"
37 res_label_filename = policy_dir_prefix + "/resource_labels"
38 boot_filename = "/boot/grub/menu.lst"
39 altboot_filename = "/boot/grub/grub.conf"
40 xensec_xml2bin = "/usr/sbin/xensec_xml2bin"
41 xensec_tool = "/usr/sbin/xensec_tool"
43 #global patterns for map file
44 #police_reference_tagname = "POLICYREFERENCENAME"
45 primary_entry_re = re.compile("\s*PRIMARY\s+.*", re.IGNORECASE)
46 secondary_entry_re = re.compile("\s*SECONDARY\s+.*", re.IGNORECASE)
47 label_template_re = re.compile(".*security_label_template.xml", re.IGNORECASE)
48 mapping_filename_re = re.compile(".*\.map", re.IGNORECASE)
49 policy_reference_entry_re = re.compile("\s*POLICYREFERENCENAME\s+.*", re.IGNORECASE)
50 vm_label_re = re.compile("\s*LABEL->SSID\s+VM\s+.*", re.IGNORECASE)
51 res_label_re = re.compile("\s*LABEL->SSID\s+RES\s+.*", re.IGNORECASE)
52 all_label_re = re.compile("\s*LABEL->SSID\s+.*", re.IGNORECASE)
53 access_control_re = re.compile("\s*access_control\s*=", re.IGNORECASE)
55 #global patterns for boot configuration file
56 xen_title_re = re.compile("\s*title\s+XEN", re.IGNORECASE)
57 any_title_re = re.compile("\s*title\s", re.IGNORECASE)
58 xen_kernel_re = re.compile("\s*kernel.*xen.*\.gz", re.IGNORECASE)
59 kernel_ver_re = re.compile("\s*module.*vmlinuz", re.IGNORECASE)
60 any_module_re = re.compile("\s*module\s", re.IGNORECASE)
61 empty_line_re = re.compile("^\s*$")
62 binary_name_re = re.compile(".*[chwall|ste|chwall_ste].*\.bin", re.IGNORECASE)
63 policy_name_re = re.compile(".*[chwall|ste|chwall_ste].*", re.IGNORECASE)
65 #decision hooks known to the hypervisor
66 ACMHOOK_sharing = 1
67 ACMHOOK_authorization = 2
69 #other global variables
70 NULL_SSIDREF = 0
72 #general Rlock for map files; only one lock for all mapfiles
73 __mapfile_lock = threading.RLock()
74 __resfile_lock = threading.RLock()
76 log = logging.getLogger("xend.util.security")
78 # Our own exception definition. It is masked (pass) if raised and
79 # whoever raises this exception must provide error information.
80 class ACMError(Exception):
81 def __init__(self,value):
82 self.value = value
83 def __str__(self):
84 return repr(self.value)
88 def err(msg):
89 """Raise ACM exception.
90 """
91 raise ACMError(msg)
95 active_policy = None
98 def mapfile_lock():
99 __mapfile_lock.acquire()
101 def mapfile_unlock():
102 __mapfile_lock.release()
105 def refresh_security_policy():
106 """
107 retrieves security policy
108 """
109 global active_policy
111 try:
112 active_policy = acm.policy()
113 except:
114 active_policy = "INACTIVE"
116 # now set active_policy
117 refresh_security_policy()
119 def on():
120 """
121 returns none if security policy is off (not compiled),
122 any string otherwise, use it: if not security.on() ...
123 """
124 refresh_security_policy()
125 return (active_policy not in ['INACTIVE', 'NULL'])
128 def calc_dom_ssidref_from_info(info):
129 """
130 Calculate a domain's ssidref from the security_label in its
131 info.
132 This function is called before the domain is started and
133 makes sure that:
134 - the type of the policy is the same as indicated in the label
135 - the name of the policy is the same as indicated in the label
136 - calculates an up-to-date ssidref for the domain
137 The latter is necessary since the domain's ssidref could have
138 changed due to changes to the policy.
139 """
140 import xen.xend.XendConfig
141 if isinstance(info, xen.xend.XendConfig.XendConfig):
142 if info.has_key('security_label'):
143 seclab = info['security_label']
144 tmp = seclab.split(":")
145 if len(tmp) != 3:
146 raise VmError("VM label '%s' in wrong format." % seclab)
147 typ, policyname, vmlabel = seclab.split(":")
148 if typ != xsconstants.ACM_POLICY_ID:
149 raise VmError("Policy type '%s' must be changed." % typ)
150 refresh_security_policy()
151 if active_policy != policyname:
152 raise VmError("Active policy '%s' different than "
153 "what in VM's label ('%s')." %
154 (active_policy, policyname))
155 ssidref = label2ssidref(vmlabel, policyname, "dom")
156 return ssidref
157 else:
158 return 0x0
159 raise VmError("security.calc_dom_ssidref_from_info: info of type '%s'"
160 "not supported." % type(info))
163 def getmapfile(policyname):
164 """
165 in: if policyname is None then the currently
166 active hypervisor policy is used
167 out: 1. primary policy, 2. secondary policy,
168 3. open file descriptor for mapping file, and
169 4. True if policy file is available, False otherwise
170 """
171 if not policyname:
172 policyname = active_policy
173 map_file_ok = False
174 primary = None
175 secondary = None
176 #strip last part of policy as file name part
177 policy_dir_list = string.split(policyname, ".")
178 policy_file = policy_dir_list.pop()
179 if len(policy_dir_list) > 0:
180 policy_dir = string.join(policy_dir_list, "/") + "/"
181 else:
182 policy_dir = ""
184 map_filename = policy_dir_prefix + "/" + policy_dir + policy_file + ".map"
185 # check if it is there, if not check if policy file is there
186 if not os.path.isfile(map_filename):
187 policy_filename = policy_dir_prefix + "/" + policy_dir + policy_file + "-security_policy.xml"
188 if not os.path.isfile(policy_filename):
189 err("Policy file \'" + policy_filename + "\' not found.")
190 else:
191 err("Mapping file \'" + map_filename + "\' not found." +
192 " Use xm makepolicy to create it.")
194 f = open(map_filename)
195 for line in f:
196 if policy_reference_entry_re.match(line):
197 l = line.split()
198 if (len(l) == 2) and (l[1] == policyname):
199 map_file_ok = True
200 elif primary_entry_re.match(line):
201 l = line.split()
202 if len(l) == 2:
203 primary = l[1]
204 elif secondary_entry_re.match(line):
205 l = line.split()
206 if len(l) == 2:
207 secondary = l[1]
208 f.close()
209 f = open(map_filename)
210 if map_file_ok and primary and secondary:
211 return (primary, secondary, f, True)
212 else:
213 err("Mapping file inconsistencies found. Try makepolicy to create a new one.")
217 def ssidref2label(ssidref_var):
218 """
219 returns labelname corresponding to ssidref;
220 maps current policy to default directory
221 to find mapping file
222 """
223 #1. translated permitted input formats
224 if isinstance(ssidref_var, str):
225 ssidref_var.strip()
226 if ssidref_var[0:2] == "0x":
227 ssidref = int(ssidref_var[2:], 16)
228 else:
229 ssidref = int(ssidref_var)
230 elif isinstance(ssidref_var, int):
231 ssidref = ssidref_var
232 else:
233 err("Instance type of ssidref not supported (must be of type 'str' or 'int')")
235 if ssidref == 0:
236 from xen.util.acmpolicy import ACM_LABEL_UNLABELED
237 return ACM_LABEL_UNLABELED
239 try:
240 mapfile_lock()
242 (primary, secondary, f, pol_exists) = getmapfile(None)
243 if not f:
244 if (pol_exists):
245 err("Mapping file for policy not found.\n" +
246 "Please use makepolicy command to create mapping file!")
247 else:
248 err("Policy file for \'" + active_policy + "\' not found.")
250 #2. get labelnames for both ssidref parts
251 pri_ssid = ssidref & 0xffff
252 sec_ssid = ssidref >> 16
253 pri_null_ssid = NULL_SSIDREF & 0xffff
254 sec_null_ssid = NULL_SSIDREF >> 16
255 pri_labels = []
256 sec_labels = []
257 labels = []
259 for line in f:
260 l = line.split()
261 if (len(l) < 5) or (l[0] != "LABEL->SSID"):
262 continue
263 if primary and (l[2] == primary) and (int(l[4], 16) == pri_ssid):
264 pri_labels.append(l[3])
265 if secondary and (l[2] == secondary) and (int(l[4], 16) == sec_ssid):
266 sec_labels.append(l[3])
267 f.close()
268 finally:
269 mapfile_unlock()
271 #3. get the label that is in both lists (combination must be a single label)
272 if (primary == "CHWALL") and (pri_ssid == pri_null_ssid) and (sec_ssid != sec_null_ssid):
273 labels = sec_labels
274 elif (secondary == "CHWALL") and (pri_ssid != pri_null_ssid) and (sec_ssid == sec_null_ssid):
275 labels = pri_labels
276 elif secondary == "NULL":
277 labels = pri_labels
278 else:
279 for i in pri_labels:
280 for j in sec_labels:
281 if (i==j):
282 labels.append(i)
283 if len(labels) != 1:
284 err("Label for ssidref \'" + str(ssidref) +
285 "\' unknown or not unique in policy \'" + active_policy + "\'")
287 return labels[0]
291 def label2ssidref(labelname, policyname, typ):
292 """
293 returns ssidref corresponding to labelname;
294 maps current policy to default directory
295 to find mapping file """
297 if policyname in ['NULL', 'INACTIVE', 'DEFAULT']:
298 err("Cannot translate labels for \'" + policyname + "\' policy.")
300 allowed_types = ['ANY']
301 if typ == 'dom':
302 allowed_types.append('VM')
303 elif typ == 'res':
304 allowed_types.append('RES')
305 else:
306 err("Invalid type. Must specify 'dom' or 'res'.")
308 try:
309 mapfile_lock()
310 (primary, secondary, f, pol_exists) = getmapfile(policyname)
312 #2. get labelnames for ssidref parts and find a common label
313 pri_ssid = []
314 sec_ssid = []
315 for line in f:
316 l = line.split()
317 if (len(l) < 5) or (l[0] != "LABEL->SSID"):
318 continue
319 if primary and (l[1] in allowed_types) and \
320 (l[2] == primary) and \
321 (l[3] == labelname):
322 pri_ssid.append(int(l[4], 16))
323 if secondary and (l[1] in allowed_types) and \
324 (l[2] == secondary) and \
325 (l[3] == labelname):
326 sec_ssid.append(int(l[4], 16))
327 f.close()
328 if (typ == 'res') and (primary == "CHWALL") and (len(pri_ssid) == 0):
329 pri_ssid.append(NULL_SSIDREF)
330 elif (typ == 'res') and (secondary == "CHWALL") and \
331 (len(sec_ssid) == 0):
332 sec_ssid.append(NULL_SSIDREF)
334 #3. sanity check and composition of ssidref
335 if (len(pri_ssid) == 0) or ((len(sec_ssid) == 0) and \
336 (secondary != "NULL")):
337 err("Label \'" + labelname + "\' not found.")
338 elif (len(pri_ssid) > 1) or (len(sec_ssid) > 1):
339 err("Label \'" + labelname + "\' not unique in policy (policy error)")
340 if secondary == "NULL":
341 return pri_ssid[0]
342 else:
343 return (sec_ssid[0] << 16) | pri_ssid[0]
344 finally:
345 mapfile_unlock()
348 def refresh_ssidref(config):
349 """
350 looks up ssidref from security field
351 and refreshes the value if label exists
352 """
353 #called by dom0, policy could have changed after xen.utils.security was initialized
354 refresh_security_policy()
356 security = None
357 if isinstance(config, dict):
358 security = config['security']
359 elif isinstance(config, list):
360 security = sxp.child_value(config, 'security')
361 else:
362 err("Instance type of config parameter not supported.")
363 if not security:
364 #nothing to do (no security label attached)
365 return config
367 policyname = None
368 labelname = None
369 # compose new security field
370 for idx in range(0, len(security)):
371 if security[idx][0] == 'ssidref':
372 security.pop(idx)
373 break
374 elif security[idx][0] == 'access_control':
375 for jdx in [1, 2]:
376 if security[idx][jdx][0] == 'label':
377 labelname = security[idx][jdx][1]
378 elif security[idx][jdx][0] == 'policy':
379 policyname = security[idx][jdx][1]
380 else:
381 err("Illegal field in access_control")
382 #verify policy is correct
383 if active_policy != policyname:
384 err("Policy \'" + str(policyname) +
385 "\' in label does not match active policy \'"
386 + str(active_policy) +"\'!")
388 new_ssidref = label2ssidref(labelname, policyname, 'dom')
389 if not new_ssidref:
390 err("SSIDREF refresh failed!")
392 security.append([ 'ssidref',str(new_ssidref)])
393 security = ['security', security ]
395 for idx in range(0,len(config)):
396 if config[idx][0] == 'security':
397 config.pop(idx)
398 break
399 config.append(security)
403 def get_ssid(domain):
404 """
405 enables domains to retrieve the label / ssidref of a running domain
406 """
407 if not on():
408 err("No policy active.")
410 if isinstance(domain, str):
411 domain_int = int(domain)
412 elif isinstance(domain, int):
413 domain_int = domain
414 else:
415 err("Illegal parameter type.")
416 try:
417 ssid_info = acm.getssid(int(domain_int))
418 except:
419 err("Cannot determine security information.")
421 if active_policy in ["DEFAULT"]:
422 label = "DEFAULT"
423 else:
424 label = ssidref2label(ssid_info["ssidref"])
425 return(ssid_info["policyreference"],
426 label,
427 ssid_info["policytype"],
428 ssid_info["ssidref"])
432 def get_decision(arg1, arg2):
433 """
434 enables domains to retrieve access control decisions from
435 the hypervisor Access Control Module.
436 IN: args format = ['domid', id] or ['ssidref', ssidref]
437 or ['access_control', ['policy', policy], ['label', label], ['type', type]]
438 """
440 if not on():
441 err("No policy active.")
443 #translate labels before calling low-level function
444 if arg1[0] == 'access_control':
445 if (arg1[1][0] != 'policy') or (arg1[2][0] != 'label') or (arg1[3][0] != 'type'):
446 err("Argument type not supported.")
447 ssidref = label2ssidref(arg1[2][1], arg1[1][1], arg1[3][1])
448 arg1 = ['ssidref', str(ssidref)]
449 if arg2[0] == 'access_control':
450 if (arg2[1][0] != 'policy') or (arg2[2][0] != 'label') or (arg2[3][0] != 'type'):
451 err("Argument type not supported.")
452 ssidref = label2ssidref(arg2[2][1], arg2[1][1], arg2[3][1])
453 arg2 = ['ssidref', str(ssidref)]
455 # accept only int or string types for domid and ssidref
456 if isinstance(arg1[1], int):
457 arg1[1] = str(arg1[1])
458 if isinstance(arg2[1], int):
459 arg2[1] = str(arg2[1])
460 if not isinstance(arg1[1], str) or not isinstance(arg2[1], str):
461 err("Invalid id or ssidref type, string or int required")
463 try:
464 decision = acm.getdecision(arg1[0], arg1[1], arg2[0], arg2[1],
465 ACMHOOK_sharing)
466 except:
467 err("Cannot determine decision.")
469 if decision:
470 return decision
471 else:
472 err("Cannot determine decision (Invalid parameter).")
475 def has_authorization(ssidref):
476 """ Check if the domain with the given ssidref has authorization to
477 run on this system. To have authoriztion dom0's STE types must
478 be a superset of that of the domain's given through its ssidref.
479 """
480 rc = True
481 dom0_ssidref = int(acm.getssid(0)['ssidref'])
482 decision = acm.getdecision('ssidref', str(dom0_ssidref),
483 'ssidref', str(ssidref),
484 ACMHOOK_authorization)
485 if decision == "DENIED":
486 rc = False
487 return rc
490 def hv_chg_policy(bin_pol, del_array, chg_array):
491 """
492 Change the binary policy in the hypervisor
493 The 'del_array' and 'chg_array' give hints about deleted ssidrefs
494 and changed ssidrefs which can be due to deleted VM labels
495 or reordered VM labels
496 """
497 rc = -xsconstants.XSERR_GENERAL_FAILURE
498 errors = ""
499 if not on():
500 err("No policy active.")
501 try:
502 rc, errors = acm.chgpolicy(bin_pol, del_array, chg_array)
503 except Exception, e:
504 pass
505 if len(errors) > 0:
506 rc = -xsconstants.XSERR_HV_OP_FAILED
507 return rc, errors
510 def make_policy(policy_name):
511 policy_file = string.join(string.split(policy_name, "."), "/")
512 if not os.path.isfile(policy_dir_prefix + "/" + policy_file + "-security_policy.xml"):
513 err("Unknown policy \'" + policy_name + "\'")
515 (ret, output) = commands.getstatusoutput(xensec_xml2bin + " -d " + policy_dir_prefix + " " + policy_file)
516 if ret:
517 err("Creating policy failed:\n" + output)
519 def load_policy(policy_name):
520 global active_policy
521 policy_file = policy_dir_prefix + "/" + string.join(string.split(policy_name, "."), "/")
522 if not os.path.isfile(policy_file + ".bin"):
523 if os.path.isfile(policy_file + "-security_policy.xml"):
524 err("Binary file does not exist." +
525 "Please use makepolicy to build the policy binary.")
526 else:
527 err("Unknown Policy " + policy_name)
529 #require this policy to be the first or the same as installed
530 if active_policy not in ['DEFAULT', policy_name]:
531 err("Active policy \'" + active_policy +
532 "\' incompatible with new policy \'" + policy_name + "\'")
533 (ret, output) = commands.getstatusoutput(xensec_tool + " loadpolicy " + policy_file + ".bin")
534 if ret:
535 err("Loading policy failed:\n" + output)
536 else:
537 # refresh active policy
538 refresh_security_policy()
542 def dump_policy():
543 if active_policy in ['NULL', 'INACTIVE']:
544 err("\'" + active_policy + "\' policy. Nothing to dump.")
546 (ret, output) = commands.getstatusoutput(xensec_tool + " getpolicy")
547 if ret:
548 err("Dumping hypervisor policy failed:\n" + output)
549 print output
553 def list_labels(policy_name, condition):
554 if (not policy_name) and (active_policy) in ["NULL", "INACTIVE", "DEFAULT"]:
555 err("Current policy \'" + active_policy + "\' has no labels defined.\n")
557 (primary, secondary, f, pol_exists) = getmapfile(policy_name)
558 if not f:
559 if pol_exists:
560 err("Cannot find mapfile for policy \'" + policy_name +
561 "\'.\nPlease use makepolicy to create mapping file.")
562 else:
563 err("Unknown policy \'" + policy_name + "\'")
565 labels = []
566 for line in f:
567 if condition.match(line):
568 label = line.split()[3]
569 if label not in labels:
570 labels.append(label)
571 return labels
574 def get_res_label(resource):
575 """Returns resource label information (policytype, label, policy) if
576 it exists. Otherwise returns null label and policy.
577 """
578 def default_res_label():
579 ssidref = NULL_SSIDREF
580 if on():
581 label = ssidref2label(ssidref)
582 else:
583 label = None
584 return (xsconstants.ACM_POLICY_ID, 'NULL', label)
587 tmp = get_resource_label(resource)
588 if len(tmp) == 2:
589 policytype = xsconstants.ACM_POLICY_ID
590 policy, label = tmp
591 elif len(tmp) == 3:
592 policytype, policy, label = tmp
593 else:
594 policytype, policy, label = default_res_label()
596 return (policytype, label, policy)
599 def get_res_security_details(resource):
600 """Returns the (label, ssidref, policy) associated with a given
601 resource from the global resource label file.
602 """
603 def default_security_details():
604 ssidref = NULL_SSIDREF
605 if on():
606 label = ssidref2label(ssidref)
607 else:
608 label = None
609 policy = active_policy
610 return (label, ssidref, policy)
612 (label, ssidref, policy) = default_security_details()
614 # find the entry associated with this resource
615 (policytype, label, policy) = get_res_label(resource)
616 if policy == 'NULL':
617 log.info("Resource label for "+resource+" not in file, using DEFAULT.")
618 return default_security_details()
620 # is this resource label for the running policy?
621 if policy == active_policy:
622 ssidref = label2ssidref(label, policy, 'res')
623 else:
624 log.info("Resource label not for active policy, using DEFAULT.")
625 return default_security_details()
627 return (label, ssidref, policy)
629 def security_label_to_details(seclab):
630 """ Convert a Xen-API type of security label into details """
631 def default_security_details():
632 ssidref = NULL_SSIDREF
633 if on():
634 label = ssidref2label(ssidref)
635 else:
636 label = None
637 policy = active_policy
638 return (label, ssidref, policy)
640 (policytype, policy, label) = seclab.split(":")
642 # is this resource label for the running policy?
643 if policy == active_policy:
644 ssidref = label2ssidref(label, policy, 'res')
645 else:
646 log.info("Resource label not for active policy, using DEFAULT.")
647 return default_security_details()
649 return (label, ssidref, policy)
651 def unify_resname(resource, mustexist=True):
652 """Makes all resource locations absolute. In case of physical
653 resources, '/dev/' is added to local file names"""
655 if not resource:
656 return resource
658 # sanity check on resource name
659 try:
660 (typ, resfile) = resource.split(":", 1)
661 except:
662 err("Resource spec '%s' contains no ':' delimiter" % resource)
664 if typ == "tap":
665 try:
666 (subtype, resfile) = resfile.split(":")
667 except:
668 err("Resource spec '%s' contains no tap subtype" % resource)
670 import os
671 if typ in ["phy", "tap"]:
672 if not resfile.startswith("/"):
673 resfile = "/dev/" + resfile
674 if mustexist:
675 stats = os.lstat(resfile)
676 if stat.S_ISLNK(stats[stat.ST_MODE]):
677 resolved = os.readlink(resfile)
678 if resolved[0] != "/":
679 resfile = os.path.join(os.path.dirname(resfile), resolved)
680 resfile = os.path.abspath(resfile)
681 else:
682 resfile = resolved
683 stats = os.lstat(resfile)
684 if not (stat.S_ISBLK(stats[stat.ST_MODE])):
685 err("Invalid resource")
687 if typ in [ "file", "tap" ]:
688 if mustexist:
689 stats = os.lstat(resfile)
690 if stat.S_ISLNK(stats[stat.ST_MODE]):
691 resfile = os.readlink(resfile)
692 stats = os.lstat(resfile)
693 if not stat.S_ISREG(stats[stat.ST_MODE]):
694 err("Invalid resource")
696 #file: resources must specified with absolute path
697 #vlan resources don't start with '/'
698 if typ != "vlan":
699 if (not resfile.startswith("/")) or \
700 (mustexist and not os.path.exists(resfile)):
701 err("Invalid resource.")
703 # from here on absolute file names with resources
704 if typ == "tap":
705 typ = typ + ":" + subtype
706 resource = typ + ":" + resfile
707 return resource
710 def res_security_check(resource, domain_label):
711 """Checks if the given resource can be used by the given domain
712 label. Returns 1 if the resource can be used, otherwise 0.
713 """
714 rtnval = 1
716 # if security is on, ask the hypervisor for a decision
717 if on():
718 #build canonical resource name
719 resource = unify_resname(resource)
721 (label, ssidref, policy) = get_res_security_details(resource)
722 domac = ['access_control']
723 domac.append(['policy', active_policy])
724 domac.append(['label', domain_label])
725 domac.append(['type', 'dom'])
726 decision = get_decision(domac, ['ssidref', str(ssidref)])
728 # provide descriptive error messages
729 if decision == 'DENIED':
730 if label == ssidref2label(NULL_SSIDREF):
731 raise ACMError("Resource '"+resource+"' is not labeled")
732 rtnval = 0
733 else:
734 raise ACMError("Permission denied for resource '"+resource+"' because label '"+label+"' is not allowed")
735 rtnval = 0
737 # security is off, make sure resource isn't labeled
738 else:
739 # Note, we can't canonicalise the resource here, because people using
740 # xm without ACM are free to use relative paths.
741 (policytype, label, policy) = get_res_label(resource)
742 if policy != 'NULL':
743 raise ACMError("Security is off, but '"+resource+"' is labeled")
744 rtnval = 0
746 return rtnval
748 def res_security_check_xapi(rlabel, rssidref, rpolicy, xapi_dom_label):
749 """Checks if the given resource can be used by the given domain
750 label. Returns 1 if the resource can be used, otherwise 0.
751 """
752 rtnval = 1
753 # if security is on, ask the hypervisor for a decision
754 if on():
755 typ, dpolicy, domain_label = xapi_dom_label.split(":")
756 if not dpolicy or not domain_label:
757 raise VmError("VM security label in wrong format.")
758 if active_policy != rpolicy:
759 raise VmError("Resource's policy '%s' != active policy '%s'" %
760 (rpolicy, active_policy))
761 domac = ['access_control']
762 domac.append(['policy', active_policy])
763 domac.append(['label', domain_label])
764 domac.append(['type', 'dom'])
765 decision = get_decision(domac, ['ssidref', str(rssidref)])
767 log.info("Access Control Decision : %s" % decision)
768 # provide descriptive error messages
769 if decision == 'DENIED':
770 if rlabel == ssidref2label(NULL_SSIDREF):
771 #raise ACMError("Resource is not labeled")
772 rtnval = 0
773 else:
774 #raise ACMError("Permission denied for resource because label '"+rlabel+"' is not allowed")
775 rtnval = 0
777 # security is off, make sure resource isn't labeled
778 else:
779 # Note, we can't canonicalise the resource here, because people using
780 # xm without ACM are free to use relative paths.
781 if rpolicy != 'NULL':
782 #raise ACMError("Security is off, but resource is labeled")
783 rtnval = 0
785 return rtnval
788 def validate_label(label, policyref):
789 """
790 Make sure that this label is part of the currently enforced policy
791 and that it reference the current policy.
792 """
793 rc = xsconstants.XSERR_SUCCESS
794 from xen.xend.XendXSPolicyAdmin import XSPolicyAdminInstance
795 curpol = XSPolicyAdminInstance().get_loaded_policy()
796 if not curpol or curpol.get_name() != policyref:
797 rc = -xsconstants.XSERR_BAD_LABEL
798 else:
799 try:
800 label2ssidref(label, curpol.get_name() , 'res')
801 except:
802 rc = -xsconstants.XSERR_BAD_LABEL
803 return rc
806 def set_resource_label_xapi(resource, reslabel_xapi, oldlabel_xapi):
807 """Assign a resource label to a resource
808 @param resource: The name of a resource, i.e., "phy:/dev/hda", or
809 "tap:qcow:/path/to/file.qcow"
811 @param reslabel_xapi: A resource label foramtted as in all other parts of
812 the Xen-API, i.e., ACM:xm-test:blue"
813 @rtype: int
814 @return Success (0) or failure value (< 0)
815 """
816 olabel = ""
817 if reslabel_xapi == "":
818 return rm_resource_label(resource, oldlabel_xapi)
819 typ, policyref, label = reslabel_xapi.split(":")
820 if typ != xsconstants.ACM_POLICY_ID:
821 return -xsconstants.XSERR_WRONG_POLICY_TYPE
822 if not policyref or not label:
823 return -xsconstants.XSERR_BAD_LABEL_FORMAT
824 if oldlabel_xapi not in [ "" ]:
825 tmp = oldlabel_xapi.split(":")
826 if len(tmp) != 3:
827 return -xsconstants.XSERR_BAD_LABEL_FORMAT
828 otyp, opolicyref, olabel = tmp
829 # Only ACM is supported
830 if otyp != xsconstants.ACM_POLICY_ID and \
831 otyp != xsconstants.INVALID_POLICY_PREFIX + \
832 xsconstants.ACM_POLICY_ID:
833 return -xsconstants.XSERR_WRONG_POLICY_TYPE
834 rc = validate_label(label, policyref)
835 if rc != xsconstants.XSERR_SUCCESS:
836 return rc
837 return set_resource_label(resource, typ, policyref, label, olabel)
840 def is_resource_in_use(resource):
841 """
842 Domain-0 'owns' resources of type 'VLAN', the rest are owned by
843 the guests.
844 """
845 from xen.xend import XendDomain
846 lst = []
847 if resource.startswith('vlan'):
848 from xen.xend.XendXSPolicyAdmin import XSPolicyAdminInstance
849 curpol = XSPolicyAdminInstance().get_loaded_policy()
850 policytype, label, policy = get_res_label(resource)
851 if curpol and \
852 policytype == xsconstants.ACM_POLICY_ID and \
853 policy == curpol.get_name() and \
854 label in curpol.policy_get_resourcelabel_names():
855 # VLAN is in use.
856 lst.append(XendDomain.instance().
857 get_vm_by_uuid(XendDomain.DOM0_UUID))
858 else:
859 dominfos = XendDomain.instance().list('all')
860 for dominfo in dominfos:
861 if is_resource_in_use_by_dom(dominfo, resource):
862 lst.append(dominfo)
863 return lst
865 def devices_equal(res1, res2, mustexist=True):
866 """ Determine whether two devices are equal """
867 return (unify_resname(res1, mustexist) ==
868 unify_resname(res2, mustexist))
870 def is_resource_in_use_by_dom(dominfo, resource):
871 """ Determine whether a resources is in use by a given domain
872 @return True or False
873 """
874 if not dominfo.domid:
875 return False
876 if dominfo._stateGet() not in [ DOM_STATE_RUNNING ]:
877 return False
878 devs = dominfo.info['devices']
879 uuids = devs.keys()
880 for uuid in uuids:
881 dev = devs[uuid]
882 if len(dev) >= 2 and dev[1].has_key('uname'):
883 # dev[0] is type, i.e. 'vbd'
884 if devices_equal(dev[1]['uname'], resource, mustexist=False):
885 log.info("RESOURCE IN USE: Domain %d uses %s." %
886 (dominfo.domid, resource))
887 return True
888 return False
891 def get_domain_resources(dominfo):
892 """ Collect all resources of a domain in a map where each entry of
893 the map is a list.
894 Entries are strored in the following formats:
895 tap:qcow:/path/xyz.qcow
896 """
897 resources = { 'vbd' : [], 'tap' : [], 'vif' : []}
898 devs = dominfo.info['devices']
899 uuids = devs.keys()
900 for uuid in uuids:
901 dev = devs[uuid]
902 typ = dev[0]
903 if typ in [ 'vbd', 'tap' ]:
904 resources[typ].append(dev[1]['uname'])
905 if typ in [ 'vif' ]:
906 sec_lab = dev[1].get('security_label')
907 if sec_lab:
908 resources[typ].append(sec_lab)
909 else:
910 # !!! This should really get the label of the domain
911 # or at least a resource label that has the same STE type
912 # as the domain has
913 from xen.util.acmpolicy import ACM_LABEL_UNLABELED
914 resources[typ].append("%s:%s:%s" %
915 (xsconstants.ACM_POLICY_ID,
916 active_policy,
917 ACM_LABEL_UNLABELED))
919 return resources
922 def resources_compatible_with_vmlabel(xspol, dominfo, vmlabel):
923 """
924 Check whether the resources' labels are compatible with the
925 given VM label. This is a function to be used when for example
926 a running domain is to get the new label 'vmlabel'
927 """
928 if not xspol:
929 return False
931 try:
932 __resfile_lock.acquire()
933 try:
934 access_control = dictio.dict_read("resources",
935 res_label_filename)
936 except:
937 # No labeled resources -> must be compatible
938 return True
939 return __resources_compatible_with_vmlabel(xspol, dominfo, vmlabel,
940 access_control)
941 finally:
942 __resfile_lock.release()
943 return False
946 def __resources_compatible_with_vmlabel(xspol, dominfo, vmlabel,
947 access_control,
948 is_policy_update=False):
949 """
950 Check whether the resources' labels are compatible with the
951 given VM label. The access_control parameter provides a
952 dictionary of the resource name to resource label mappings
953 under which the evaluation should be done.
954 Call this only for a paused or running domain.
955 """
956 def collect_labels(reslabels, s_label, polname):
957 if len(s_label) != 3 or polname != s_label[1]:
958 return False
959 label = s_label[2]
960 if not label in reslabels:
961 reslabels.append(label)
962 return True
964 resources = get_domain_resources(dominfo)
965 reslabels = [] # all resource labels
967 polname = xspol.get_name()
968 for key, value in resources.items():
969 if key in [ 'vbd', 'tap' ]:
970 for res in resources[key]:
971 try:
972 label = access_control[res]
973 if not collect_labels(reslabels, label, polname):
974 return False
975 except:
976 return False
977 elif key in [ 'vif' ]:
978 for xapi_label in value:
979 label = xapi_label.split(":")
980 from xen.util.acmpolicy import ACM_LABEL_UNLABELED
981 if not (is_policy_update and \
982 label[2] == ACM_LABEL_UNLABELED):
983 if not collect_labels(reslabels, label, polname):
984 return False
985 else:
986 log.error("Unhandled device type: %s" % key)
987 return False
989 # Check that all resource labes have a common STE type with the
990 # vmlabel
991 if len(reslabels) > 0:
992 rc = xspol.policy_check_vmlabel_against_reslabels(vmlabel, reslabels)
993 else:
994 rc = True
995 log.info("vmlabel=%s, reslabels=%s, rc=%s" %
996 (vmlabel, reslabels, str(rc)))
997 return rc;
999 def set_resource_label(resource, policytype, policyref, reslabel, \
1000 oreslabel = None):
1001 """Assign a label to a resource
1002 If the old label (oreslabel) is given, then the resource must have
1003 that old label.
1004 A resource label may be changed if
1005 - the resource is not in use
1006 @param resource : The name of a resource, i.e., "phy:/dev/hda"
1007 @param policyref : The name of the policy
1008 @param reslabel : the resource label within the policy
1009 @param oreslabel : optional current resource label
1011 @rtype: int
1012 @return Success (0) or failure value (< 0)
1013 """
1014 try:
1015 resource = unify_resname(resource, mustexist=False)
1016 except Exception:
1017 return -xsconstants.XSERR_BAD_RESOURCE_FORMAT
1019 domains = is_resource_in_use(resource)
1020 if len(domains) > 0:
1021 return -xsconstants.XSERR_RESOURCE_IN_USE
1023 try:
1024 __resfile_lock.acquire()
1025 access_control = {}
1026 try:
1027 access_control = dictio.dict_read("resources", res_label_filename)
1028 except:
1029 pass
1030 if oreslabel:
1031 if not access_control.has_key(resource):
1032 return -xsconstants.XSERR_BAD_LABEL
1033 tmp = access_control[resource]
1034 if len(tmp) != 3:
1035 return -xsconstants.XSERR_BAD_LABEL
1036 if tmp[2] != oreslabel:
1037 return -xsconstants.XSERR_BAD_LABEL
1038 if reslabel != "":
1039 new_entry = { resource : tuple([policytype, policyref, reslabel])}
1040 access_control.update(new_entry)
1041 else:
1042 if access_control.has_key(resource):
1043 del access_control[resource]
1044 dictio.dict_write(access_control, "resources", res_label_filename)
1045 finally:
1046 __resfile_lock.release()
1047 return xsconstants.XSERR_SUCCESS
1049 def rm_resource_label(resource, oldlabel_xapi):
1050 """Remove a resource label from a physical resource
1051 @param resource: The name of a resource, i.e., "phy:/dev/hda"
1053 @rtype: int
1054 @return Success (0) or failure value (< 0)
1055 """
1056 tmp = oldlabel_xapi.split(":")
1057 if len(tmp) != 3:
1058 return -xsconstants.XSERR_BAD_LABEL_FORMAT
1059 otyp, opolicyref, olabel = tmp
1060 # Only ACM is supported
1061 if otyp != xsconstants.ACM_POLICY_ID and \
1062 otyp != xsconstants.INVALID_POLICY_PREFIX + xsconstants.ACM_POLICY_ID:
1063 return -xsconstants.XSERR_WRONG_POLICY_TYPE
1064 return set_resource_label(resource, "", "", "", olabel)
1066 def get_resource_label_xapi(resource):
1067 """Get the assigned resource label of a physical resource
1068 in the format used by then Xen-API, i.e., "ACM:xm-test:blue"
1070 @rtype: string
1071 @return the string representing policy type, policy name and label of
1072 the resource
1073 """
1074 res = get_resource_label(resource)
1075 return format_resource_label(res)
1077 def format_resource_label(res):
1078 if res:
1079 if len(res) == 2:
1080 return xsconstants.ACM_POLICY_ID + ":" + res[0] + ":" + res[1]
1081 if len(res) == 3:
1082 return ":".join(res)
1083 return ""
1085 def get_resource_label(resource):
1086 """Get the assigned resource label of a given resource
1087 @param resource: The name of a resource, i.e., "phy:/dev/hda"
1089 @rtype: list
1090 @return tuple of (policy name, resource label), i.e., (xm-test, blue)
1091 """
1092 try:
1093 resource = unify_resname(resource, mustexist=False)
1094 except Exception:
1095 return []
1097 reslabel_map = get_labeled_resources()
1099 if reslabel_map.has_key(resource):
1100 return list(reslabel_map[resource])
1101 else:
1102 #Try to resolve each label entry
1103 for key, value in reslabel_map.items():
1104 try:
1105 if resource == unify_resname(key):
1106 return list(value)
1107 except:
1108 pass
1110 return []
1113 def get_labeled_resources_xapi():
1114 """ Get a map of all labeled resource with the labels formatted in the
1115 xen-api resource label format.
1116 """
1117 reslabel_map = get_labeled_resources()
1118 for key, labeldata in reslabel_map.items():
1119 reslabel_map[key] = format_resource_label(labeldata)
1120 return reslabel_map
1123 def get_labeled_resources():
1124 """Get a map of all labeled resources
1125 @rtype: list
1126 @return list of labeled resources
1127 """
1128 try:
1129 __resfile_lock.acquire()
1130 try:
1131 access_control = dictio.dict_read("resources", res_label_filename)
1132 except:
1133 return {}
1134 finally:
1135 __resfile_lock.release()
1136 return access_control
1139 def relabel_domains(relabel_list):
1140 """
1141 Relabel the given domains to have a new ssidref.
1142 @param relabel_list: a list containing tuples of domid, ssidref
1143 example: [ [0, 0x00020002] ]
1144 """
1145 rel_rules = ""
1146 for r in relabel_list:
1147 log.info("Relabeling domain with domid %d to new ssidref 0x%08x",
1148 r[0], r[1])
1149 rel_rules += struct.pack("ii", r[0], r[1])
1150 try:
1151 rc, errors = acm.relabel_domains(rel_rules)
1152 except Exception, e:
1153 log.info("Error after relabel_domains: %s" % str(e))
1154 rc = -xsconstants.XSERR_GENERAL_FAILURE
1155 errors = ""
1156 if (len(errors) > 0):
1157 rc = -xsconstants.XSERR_HV_OP_FAILED
1158 return rc, errors
1161 def change_acm_policy(bin_pol, del_array, chg_array,
1162 vmlabel_map, reslabel_map, cur_acmpol, new_acmpol):
1163 """
1164 Change the ACM policy of the system by relabeling
1165 domains and resources first and doing some access checks.
1166 Then update the policy in the hypervisor. If this is all successful,
1167 relabel the domains permanently and commit the relabed resources.
1169 Need to do / check the following:
1170 - relabel all resources where there is a 'from' field in
1171 the policy. [ NOT DOING THIS: and mark those as unlabeled where the label
1172 does not appear in the new policy anymore (deletion) ]
1173 - relabel all VMs where there is a 'from' field in the
1174 policy and mark those as unlabeled where the label
1175 does not appear in the new policy anymore; no running
1176 or paused VM may be unlabeled through this
1177 - check that under the new labeling conditions the VMs
1178 still have access to their resources as before. Unlabeled
1179 resources are inaccessible. If this check fails, the
1180 update failed.
1181 - Attempt changes in the hypervisor; if this step fails,
1182 roll back the relabeling of resources and VMs
1183 - Make the relabeling of resources and VMs permanent
1184 """
1185 rc = xsconstants.XSERR_SUCCESS
1187 domain_label_map = {}
1188 new_policyname = new_acmpol.get_name()
1189 new_policytype = new_acmpol.get_type_name()
1190 cur_policyname = cur_acmpol.get_name()
1191 cur_policytype = cur_acmpol.get_type_name()
1192 polnew_reslabels = new_acmpol.policy_get_resourcelabel_names()
1193 errors=""
1195 try:
1196 __resfile_lock.acquire()
1197 mapfile_lock()
1199 # Get all domains' dominfo.
1200 from xen.xend import XendDomain
1201 dominfos = XendDomain.instance().list('all')
1203 log.info("----------------------------------------------")
1204 # relabel resources
1206 access_control = {}
1207 try:
1208 access_control = dictio.dict_read("resources", res_label_filename)
1209 except:
1210 pass
1211 for key, labeldata in access_control.items():
1212 if len(labeldata) == 2:
1213 policy, label = labeldata
1214 policytype = xsconstants.ACM_POLICY_ID
1215 elif len(labeldata) == 3:
1216 policytype, policy, label = labeldata
1217 else:
1218 return -xsconstants.XSERR_BAD_LABEL_FORMAT, ""
1220 if policytype != cur_policytype or \
1221 policy != cur_policyname:
1222 continue
1224 # label been renamed or deleted?
1225 if reslabel_map.has_key(label) and cur_policyname == policy:
1226 label = reslabel_map[label]
1227 elif label not in polnew_reslabels:
1228 policytype = xsconstants.INVALID_POLICY_PREFIX + policytype
1229 # Update entry
1230 access_control[key] = \
1231 tuple([ policytype, new_policyname, label ])
1233 # All resources have new labels in the access_control map
1234 # There may still be labels in there that are invalid now.
1236 # Do this in memory without writing to disk:
1237 # - Relabel all domains independent of whether they are running
1238 # or not
1239 # - later write back to config files
1240 polnew_vmlabels = new_acmpol.policy_get_virtualmachinelabel_names()
1242 for dominfo in dominfos:
1243 sec_lab = dominfo.get_security_label()
1244 if not sec_lab:
1245 continue
1246 policytype, policy, vmlabel = sec_lab.split(":")
1247 name = dominfo.getName()
1249 if policytype != cur_policytype or \
1250 policy != cur_policyname:
1251 continue
1253 new_vmlabel = vmlabel
1254 if vmlabel_map.has_key(vmlabel):
1255 new_vmlabel = vmlabel_map[vmlabel]
1256 if new_vmlabel not in polnew_vmlabels:
1257 policytype = xsconstants.INVALID_POLICY_PREFIX + policytype
1258 new_seclab = "%s:%s:%s" % \
1259 (policytype, new_policyname, new_vmlabel)
1261 domain_label_map[dominfo] = [ sec_lab, new_seclab ]
1263 if dominfo._stateGet() in (DOM_STATE_PAUSED, DOM_STATE_RUNNING):
1264 compatible = __resources_compatible_with_vmlabel(new_acmpol,
1265 dominfo,
1266 new_vmlabel,
1267 access_control,
1268 is_policy_update=True)
1269 log.info("Domain %s with new label '%s' can access its "
1270 "resources? : %s" %
1271 (name, new_vmlabel, str(compatible)))
1272 log.info("VM labels in new policy: %s" %
1273 new_acmpol.policy_get_virtualmachinelabel_names())
1274 if not compatible:
1275 return (-xsconstants.XSERR_RESOURCE_ACCESS, "")
1277 rc, errors = hv_chg_policy(bin_pol, del_array, chg_array)
1278 if rc == 0:
1279 # Write the relabeled resources back into the file
1280 dictio.dict_write(access_control, "resources", res_label_filename)
1281 # Properly update all VMs to their new labels
1282 for dominfo, labels in domain_label_map.items():
1283 sec_lab, new_seclab = labels
1284 if sec_lab != new_seclab:
1285 log.info("Updating domain %s to new label '%s'." % \
1286 (dominfo.getName(), new_seclab))
1287 # This better be working!
1288 res = dominfo.set_security_label(new_seclab,
1289 sec_lab,
1290 new_acmpol,
1291 cur_acmpol)
1292 if res[0] != xsconstants.XSERR_SUCCESS:
1293 log.info("ERROR: Could not chg label on domain %s: %s" %
1294 (dominfo.getName(),
1295 xsconstants.xserr2string(-int(res[0]))))
1296 finally:
1297 log.info("----------------------------------------------")
1298 mapfile_unlock()
1299 __resfile_lock.release()
1301 return rc, errors