ia64/xen-unstable

view tools/python/xen/util/security.py @ 11912:328606e0705f

Use the name of the title of the system to boot into (instead of the
kernel version) to determine where to make the entry into the grub
configuration file.

Signed-off-by: Reiner Sailer <sailer@us.ibm.com>
Signed-off-by: Stefan Berger <stefanb@us.ibm.com>
author kfraser@localhost.localdomain
date Fri Oct 20 09:32:16 2006 +0100 (2006-10-20)
parents 58144f4b102c
children 763339844784
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 #============================================================================
20 import commands
21 import logging
22 import sys, os, string, re
23 import traceback
24 import shutil
25 from xen.lowlevel import acm
26 from xen.xend import sxp
27 from xen.xend.XendLogging import log
28 from xen.util import dictio
30 #global directories and tools for security management
31 policy_dir_prefix = "/etc/xen/acm-security/policies"
32 res_label_filename = policy_dir_prefix + "/resource_labels"
33 boot_filename = "/boot/grub/menu.lst"
34 altboot_filename = "/boot/grub/grub.conf"
35 xensec_xml2bin = "/usr/sbin/xensec_xml2bin"
36 xensec_tool = "/usr/sbin/xensec_tool"
38 #global patterns for map file
39 #police_reference_tagname = "POLICYREFERENCENAME"
40 primary_entry_re = re.compile("\s*PRIMARY\s+.*", re.IGNORECASE)
41 secondary_entry_re = re.compile("\s*SECONDARY\s+.*", re.IGNORECASE)
42 label_template_re = re.compile(".*security_label_template.xml", re.IGNORECASE)
43 mapping_filename_re = re.compile(".*\.map", re.IGNORECASE)
44 policy_reference_entry_re = re.compile("\s*POLICYREFERENCENAME\s+.*", re.IGNORECASE)
45 vm_label_re = re.compile("\s*LABEL->SSID\s+VM\s+.*", re.IGNORECASE)
46 res_label_re = re.compile("\s*LABEL->SSID\s+RES\s+.*", re.IGNORECASE)
47 all_label_re = re.compile("\s*LABEL->SSID\s+.*", re.IGNORECASE)
48 access_control_re = re.compile("\s*access_control\s*=", re.IGNORECASE)
50 #global patterns for boot configuration file
51 xen_title_re = re.compile("\s*title\s+XEN", re.IGNORECASE)
52 any_title_re = re.compile("\s*title\s", re.IGNORECASE)
53 xen_kernel_re = re.compile("\s*kernel.*xen.*\.gz", re.IGNORECASE)
54 kernel_ver_re = re.compile("\s*module.*vmlinuz", re.IGNORECASE)
55 any_module_re = re.compile("\s*module\s", re.IGNORECASE)
56 empty_line_re = re.compile("^\s*$")
57 binary_name_re = re.compile(".*[chwall|ste|chwall_ste].*\.bin", re.IGNORECASE)
58 policy_name_re = re.compile(".*[chwall|ste|chwall_ste].*", re.IGNORECASE)
60 #other global variables
61 NULL_SSIDREF = 0
63 log = logging.getLogger("xend.util.security")
65 # Our own exception definition. It is masked (pass) if raised and
66 # whoever raises this exception must provide error information.
67 class ACMError(Exception):
68 def __init__(self,value):
69 self.value = value
70 def __str__(self):
71 return repr(self.value)
75 def err(msg):
76 """Raise ACM exception.
77 """
78 sys.stderr.write("ACMError: " + msg + "\n")
79 raise ACMError(msg)
83 active_policy = None
86 def refresh_security_policy():
87 """
88 retrieves security policy
89 """
90 global active_policy
92 try:
93 active_policy = acm.policy()
94 except:
95 active_policy = "INACTIVE"
97 # now set active_policy
98 refresh_security_policy()
100 def on():
101 """
102 returns none if security policy is off (not compiled),
103 any string otherwise, use it: if not security.on() ...
104 """
105 refresh_security_policy()
106 return (active_policy not in ['INACTIVE', 'NULL'])
110 # Assumes a 'security' info [security access_control ...] [ssidref ...]
111 def get_security_info(info, field):
112 """retrieves security field from self.info['security'])
113 allowed search fields: ssidref, label, policy
114 """
115 if isinstance(info, dict):
116 security = info['security']
117 elif isinstance(info, list):
118 security = sxp.child_value(info, 'security', )
119 if not security:
120 if field == 'ssidref':
121 #return default ssid
122 return 0
123 else:
124 err("Security information not found in info struct.")
126 if field == 'ssidref':
127 search = 'ssidref'
128 elif field in ['policy', 'label']:
129 search = 'access_control'
130 else:
131 err("Illegal field in get_security_info.")
133 for idx in range(0, len(security)):
134 if search != security[idx][0]:
135 continue
136 if search == 'ssidref':
137 return int(security[idx][1])
138 else:
139 for aidx in range(0, len(security[idx])):
140 if security[idx][aidx][0] == field:
141 return str(security[idx][aidx][1])
143 if search == 'ssidref':
144 return 0
145 else:
146 return None
150 def get_security_printlabel(info):
151 """retrieves printable security label from self.info['security']),
152 preferably the label name and otherwise (if label is not specified
153 in config and cannot be found in mapping file) a hex string of the
154 ssidref or none if both not available
155 """
156 try:
157 if not on():
158 return "INACTIVE"
159 if active_policy in ["DEFAULT"]:
160 return "DEFAULT"
162 printlabel = get_security_info(info, 'label')
163 if printlabel:
164 return printlabel
165 ssidref = get_security_info(info, 'ssidref')
166 if not ssidref:
167 return None
168 #try to translate ssidref to a label
169 result = ssidref2label(ssidref)
170 if not result:
171 printlabel = "0x%08x" % ssidref
172 else:
173 printlabel = result
174 return printlabel
175 except ACMError:
176 #don't throw an exception in xm list
177 return "ERROR"
181 def getmapfile(policyname):
182 """
183 in: if policyname is None then the currently
184 active hypervisor policy is used
185 out: 1. primary policy, 2. secondary policy,
186 3. open file descriptor for mapping file, and
187 4. True if policy file is available, False otherwise
188 """
189 if not policyname:
190 policyname = active_policy
191 map_file_ok = False
192 primary = None
193 secondary = None
194 #strip last part of policy as file name part
195 policy_dir_list = string.split(policyname, ".")
196 policy_file = policy_dir_list.pop()
197 if len(policy_dir_list) > 0:
198 policy_dir = string.join(policy_dir_list, "/") + "/"
199 else:
200 policy_dir = ""
202 map_filename = policy_dir_prefix + "/" + policy_dir + policy_file + ".map"
203 # check if it is there, if not check if policy file is there
204 if not os.path.isfile(map_filename):
205 policy_filename = policy_dir_prefix + "/" + policy_dir + policy_file + "-security_policy.xml"
206 if not os.path.isfile(policy_filename):
207 err("Policy file \'" + policy_filename + "\' not found.")
208 else:
209 err("Mapping file \'" + map_filename + "\' not found." +
210 " Use xm makepolicy to create it.")
212 f = open(map_filename)
213 for line in f:
214 if policy_reference_entry_re.match(line):
215 l = line.split()
216 if (len(l) == 2) and (l[1] == policyname):
217 map_file_ok = True
218 elif primary_entry_re.match(line):
219 l = line.split()
220 if len(l) == 2:
221 primary = l[1]
222 elif secondary_entry_re.match(line):
223 l = line.split()
224 if len(l) == 2:
225 secondary = l[1]
226 f.close()
227 f = open(map_filename)
228 if map_file_ok and primary and secondary:
229 return (primary, secondary, f, True)
230 else:
231 err("Mapping file inconsistencies found. Try makepolicy to create a new one.")
235 def ssidref2label(ssidref_var):
236 """
237 returns labelname corresponding to ssidref;
238 maps current policy to default directory
239 to find mapping file
240 """
241 #1. translated permitted input formats
242 if isinstance(ssidref_var, str):
243 ssidref_var.strip()
244 if ssidref_var[0:2] == "0x":
245 ssidref = int(ssidref_var[2:], 16)
246 else:
247 ssidref = int(ssidref_var)
248 elif isinstance(ssidref_var, int):
249 ssidref = ssidref_var
250 else:
251 err("Instance type of ssidref not supported (must be of type 'str' or 'int')")
253 (primary, secondary, f, pol_exists) = getmapfile(None)
254 if not f:
255 if (pol_exists):
256 err("Mapping file for policy \'" + policyname + "\' not found.\n" +
257 "Please use makepolicy command to create mapping file!")
258 else:
259 err("Policy file for \'" + active_policy + "\' not found.")
261 #2. get labelnames for both ssidref parts
262 pri_ssid = ssidref & 0xffff
263 sec_ssid = ssidref >> 16
264 pri_null_ssid = NULL_SSIDREF & 0xffff
265 sec_null_ssid = NULL_SSIDREF >> 16
266 pri_labels = []
267 sec_labels = []
268 labels = []
270 for line in f:
271 l = line.split()
272 if (len(l) < 5) or (l[0] != "LABEL->SSID"):
273 continue
274 if primary and (l[2] == primary) and (int(l[4], 16) == pri_ssid):
275 pri_labels.append(l[3])
276 if secondary and (l[2] == secondary) and (int(l[4], 16) == sec_ssid):
277 sec_labels.append(l[3])
278 f.close()
280 #3. get the label that is in both lists (combination must be a single label)
281 if (primary == "CHWALL") and (pri_ssid == pri_null_ssid) and (sec_ssid != sec_null_ssid):
282 labels = sec_labels
283 elif (secondary == "CHWALL") and (pri_ssid != pri_null_ssid) and (sec_ssid == sec_null_ssid):
284 labels = pri_labels
285 elif secondary == "NULL":
286 labels = pri_labels
287 else:
288 for i in pri_labels:
289 for j in sec_labels:
290 if (i==j):
291 labels.append(i)
292 if len(labels) != 1:
293 err("Label for ssidref \'" + str(ssidref) +
294 "\' unknown or not unique in policy \'" + active_policy + "\'")
296 return labels[0]
300 def label2ssidref(labelname, policyname, type):
301 """
302 returns ssidref corresponding to labelname;
303 maps current policy to default directory
304 to find mapping file """
306 if policyname in ['NULL', 'INACTIVE', 'DEFAULT']:
307 err("Cannot translate labels for \'" + policyname + "\' policy.")
309 allowed_types = ['ANY']
310 if type == 'dom':
311 allowed_types.append('VM')
312 elif type == 'res':
313 allowed_types.append('RES')
314 else:
315 err("Invalid type. Must specify 'dom' or 'res'.")
317 (primary, secondary, f, pol_exists) = getmapfile(policyname)
319 #2. get labelnames for ssidref parts and find a common label
320 pri_ssid = []
321 sec_ssid = []
322 for line in f:
323 l = line.split()
324 if (len(l) < 5) or (l[0] != "LABEL->SSID"):
325 continue
326 if primary and (l[1] in allowed_types) and (l[2] == primary) and (l[3] == labelname):
327 pri_ssid.append(int(l[4], 16))
328 if secondary and (l[1] in allowed_types) and (l[2] == secondary) and (l[3] == labelname):
329 sec_ssid.append(int(l[4], 16))
330 f.close()
331 if (type == 'res') and (primary == "CHWALL") and (len(pri_ssid) == 0):
332 pri_ssid.append(NULL_SSIDREF)
333 elif (type == 'res') and (secondary == "CHWALL") and (len(sec_ssid) == 0):
334 sec_ssid.append(NULL_SSIDREF)
336 #3. sanity check and composition of ssidref
337 if (len(pri_ssid) == 0) or ((len(sec_ssid) == 0) and (secondary != "NULL")):
338 err("Label \'" + labelname + "\' not found.")
339 elif (len(pri_ssid) > 1) or (len(sec_ssid) > 1):
340 err("Label \'" + labelname + "\' not unique in policy (policy error)")
341 if secondary == "NULL":
342 return pri_ssid[0]
343 else:
344 return (sec_ssid[0] << 16) | pri_ssid[0]
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 \'" + policyname + "\' in label does not match active policy \'"
385 + active_policy +"\'!")
387 new_ssidref = label2ssidref(labelname, policyname, 'dom')
388 if not new_ssidref:
389 err("SSIDREF refresh failed!")
391 security.append([ 'ssidref',str(new_ssidref)])
392 security = ['security', security ]
394 for idx in range(0,len(config)):
395 if config[idx][0] == 'security':
396 config.pop(idx)
397 break
398 config.append(security)
402 def get_ssid(domain):
403 """
404 enables domains to retrieve the label / ssidref of a running domain
405 """
406 if not on():
407 err("No policy active.")
409 if isinstance(domain, str):
410 domain_int = int(domain)
411 elif isinstance(domain, int):
412 domain_int = domain
413 else:
414 err("Illegal parameter type.")
415 try:
416 ssid_info = acm.getssid(int(domain_int))
417 except:
418 err("Cannot determine security information.")
420 if active_policy in ["DEFAULT"]:
421 label = "DEFAULT"
422 else:
423 label = ssidref2label(ssid_info["ssidref"])
424 return(ssid_info["policyreference"],
425 label,
426 ssid_info["policytype"],
427 ssid_info["ssidref"])
431 def get_decision(arg1, arg2):
432 """
433 enables domains to retrieve access control decisions from
434 the hypervisor Access Control Module.
435 IN: args format = ['domid', id] or ['ssidref', ssidref]
436 or ['access_control', ['policy', policy], ['label', label], ['type', type]]
437 """
439 if not on():
440 err("No policy active.")
442 #translate labels before calling low-level function
443 if arg1[0] == 'access_control':
444 if (arg1[1][0] != 'policy') or (arg1[2][0] != 'label') or (arg1[3][0] != 'type'):
445 err("Argument type not supported.")
446 ssidref = label2ssidref(arg1[2][1], arg1[1][1], arg1[3][1])
447 arg1 = ['ssidref', str(ssidref)]
448 if arg2[0] == 'access_control':
449 if (arg2[1][0] != 'policy') or (arg2[2][0] != 'label') or (arg2[3][0] != 'type'):
450 err("Argument type not supported.")
451 ssidref = label2ssidref(arg2[2][1], arg2[1][1], arg2[3][1])
452 arg2 = ['ssidref', str(ssidref)]
454 # accept only int or string types for domid and ssidref
455 if isinstance(arg1[1], int):
456 arg1[1] = str(arg1[1])
457 if isinstance(arg2[1], int):
458 arg2[1] = str(arg2[1])
459 if not isinstance(arg1[1], str) or not isinstance(arg2[1], str):
460 err("Invalid id or ssidref type, string or int required")
462 try:
463 decision = acm.getdecision(arg1[0], arg1[1], arg2[0], arg2[1])
464 except:
465 err("Cannot determine decision.")
467 if decision:
468 return decision
469 else:
470 err("Cannot determine decision (Invalid parameter).")
474 def make_policy(policy_name):
475 policy_file = string.join(string.split(policy_name, "."), "/")
476 if not os.path.isfile(policy_dir_prefix + "/" + policy_file + "-security_policy.xml"):
477 err("Unknown policy \'" + policy_name + "\'")
479 (ret, output) = commands.getstatusoutput(xensec_xml2bin + " -d " + policy_dir_prefix + " " + policy_file)
480 if ret:
481 err("Creating policy failed:\n" + output)
485 def load_policy(policy_name):
486 global active_policy
487 policy_file = policy_dir_prefix + "/" + string.join(string.split(policy_name, "."), "/")
488 if not os.path.isfile(policy_file + ".bin"):
489 if os.path.isfile(policy_file + "-security_policy.xml"):
490 err("Binary file does not exist." +
491 "Please use makepolicy to build the policy binary.")
492 else:
493 err("Unknown Policy " + policy_name)
495 #require this policy to be the first or the same as installed
496 if active_policy not in ['DEFAULT', policy_name]:
497 err("Active policy \'" + active_policy +
498 "\' incompatible with new policy \'" + policy_name + "\'")
499 (ret, output) = commands.getstatusoutput(xensec_tool + " loadpolicy " + policy_file + ".bin")
500 if ret:
501 err("Loading policy failed:\n" + output)
502 else:
503 # refresh active policy
504 refresh_security_policy()
508 def dump_policy():
509 if active_policy in ['NULL', 'INACTIVE']:
510 err("\'" + active_policy + "\' policy. Nothing to dump.")
512 (ret, output) = commands.getstatusoutput(xensec_tool + " getpolicy")
513 if ret:
514 err("Dumping hypervisor policy failed:\n" + output)
515 print output
519 def list_labels(policy_name, condition):
520 if (not policy_name) and (active_policy) in ["NULL", "INACTIVE", "DEFAULT"]:
521 err("Current policy \'" + active_policy + "\' has no labels defined.\n")
523 (primary, secondary, f, pol_exists) = getmapfile(policy_name)
524 if not f:
525 if pol_exists:
526 err("Cannot find mapfile for policy \'" + policy_name +
527 "\'.\nPlease use makepolicy to create mapping file.")
528 else:
529 err("Unknown policy \'" + policy_name + "\'")
531 labels = []
532 for line in f:
533 if condition.match(line):
534 label = line.split()[3]
535 if label not in labels:
536 labels.append(label)
537 return labels
540 def get_res_label(resource):
541 """Returns resource label information (label, policy) if it exists.
542 Otherwise returns null label and policy.
543 """
544 def default_res_label():
545 ssidref = NULL_SSIDREF
546 if on():
547 label = ssidref2label(ssidref)
548 else:
549 label = None
550 return (label, 'NULL')
552 (label, policy) = default_res_label()
554 # load the resource label file
555 res_label_cache = {}
556 try:
557 res_label_cache = dictio.dict_read("resources", res_label_filename)
558 except:
559 log.info("Resource label file not found.")
560 return default_res_label()
562 # find the resource information
563 if res_label_cache.has_key(resource):
564 (policy, label) = res_label_cache[resource]
566 return (label, policy)
569 def get_res_security_details(resource):
570 """Returns the (label, ssidref, policy) associated with a given
571 resource from the global resource label file.
572 """
573 def default_security_details():
574 ssidref = NULL_SSIDREF
575 if on():
576 label = ssidref2label(ssidref)
577 else:
578 label = None
579 policy = active_policy
580 return (label, ssidref, policy)
582 (label, ssidref, policy) = default_security_details()
584 # find the entry associated with this resource
585 (label, policy) = get_res_label(resource)
586 if policy == 'NULL':
587 log.info("Resource label for "+resource+" not in file, using DEFAULT.")
588 return default_security_details()
590 # is this resource label for the running policy?
591 if policy == active_policy:
592 ssidref = label2ssidref(label, policy, 'res')
593 else:
594 log.info("Resource label not for active policy, using DEFAULT.")
595 return default_security_details()
597 return (label, ssidref, policy)
600 def unify_resname(resource):
601 """Makes all resource locations absolute. In case of physical
602 resources, '/dev/' is added to local file names"""
604 # sanity check on resource name
605 (type, resfile) = resource.split(":")
606 if type == "phy":
607 if not resfile.startswith("/"):
608 resfile = "/dev/" + resfile
610 #file: resources must specified with absolute path
611 if (not resfile.startswith("/")) or (not os.path.exists(resfile)):
612 err("Invalid resource.")
614 # from here on absolute file names with resources
615 resource = type + ":" + resfile
616 return resource
619 def res_security_check(resource, domain_label):
620 """Checks if the given resource can be used by the given domain
621 label. Returns 1 if the resource can be used, otherwise 0.
622 """
623 rtnval = 1
625 #build canonical resource name
626 resource = unify_resname(resource)
628 # if security is on, ask the hypervisor for a decision
629 if on():
630 (label, ssidref, policy) = get_res_security_details(resource)
631 domac = ['access_control']
632 domac.append(['policy', active_policy])
633 domac.append(['label', domain_label])
634 domac.append(['type', 'dom'])
635 decision = get_decision(domac, ['ssidref', str(ssidref)])
637 # provide descriptive error messages
638 if decision == 'DENIED':
639 if label == ssidref2label(NULL_SSIDREF):
640 raise ACMError("Resource '"+resource+"' is not labeled")
641 rtnval = 0
642 else:
643 raise ACMError("Permission denied for resource '"+resource+"' because label '"+label+"' is not allowed")
644 rtnval = 0
646 # security is off, make sure resource isn't labeled
647 else:
648 (label, policy) = get_res_label(resource)
649 if policy != 'NULL':
650 raise ACMError("Security is off, but '"+resource+"' is labeled")
651 rtnval = 0
653 return rtnval