ia64/xen-unstable

view tools/python/xen/util/security.py @ 10720:8922c1fbe684

[XM][ACM] Add xm subcommands to work with security resource labels.

This patch adds new xm subcommands to support working with resource
labels. The new subcommands are 'xm resources', 'xm rmlabel', 'xm
getlabel' and 'xm dry-run'. In addition, the 'xm addlabel' subcommand
now uses an updated syntax to support labeling both domains and
resources. See the xm man page for details on each subcommand.

Beyond the new subcommands, this patch allows users to immediately see
when security checks will fail by pushing some basic security checking
into the beginning of 'xm create' and 'xm block-attach'. ACM security
attributes for block devices are added to XenStore in order to support
the final security enforcement, which will be performed in the kernel
and included in a separate patch.

Signed-off-by: Bryan D. Payne <bdpayne@us.ibm.com>
Signed-off-by: Reiner Sailer <sailer@us.ibm.com>
author kfraser@localhost.localdomain
date Mon Jul 10 17:18:07 2006 +0100 (2006-07-10)
parents 0de8a4a023d0
children 58144f4b102c
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 xensec_xml2bin = "/usr/sbin/xensec_xml2bin"
35 xensec_tool = "/usr/sbin/xensec_tool"
37 #global patterns for map file
38 #police_reference_tagname = "POLICYREFERENCENAME"
39 primary_entry_re = re.compile("\s*PRIMARY\s+.*", re.IGNORECASE)
40 secondary_entry_re = re.compile("\s*SECONDARY\s+.*", re.IGNORECASE)
41 label_template_re = re.compile(".*security_label_template.xml", re.IGNORECASE)
42 mapping_filename_re = re.compile(".*\.map", re.IGNORECASE)
43 policy_reference_entry_re = re.compile("\s*POLICYREFERENCENAME\s+.*", re.IGNORECASE)
44 vm_label_re = re.compile("\s*LABEL->SSID\s+VM\s+.*", re.IGNORECASE)
45 res_label_re = re.compile("\s*LABEL->SSID\s+RES\s+.*", re.IGNORECASE)
46 all_label_re = re.compile("\s*LABEL->SSID\s+.*", re.IGNORECASE)
47 access_control_re = re.compile("\s*access_control\s*=", re.IGNORECASE)
49 #global patterns for boot configuration file
50 xen_title_re = re.compile("\s*title\s+XEN", re.IGNORECASE)
51 any_title_re = re.compile("\s*title\s", re.IGNORECASE)
52 xen_kernel_re = re.compile("\s*kernel.*xen.*\.gz", re.IGNORECASE)
53 kernel_ver_re = re.compile("\s*module.*vmlinuz", re.IGNORECASE)
54 any_module_re = re.compile("\s*module\s", re.IGNORECASE)
55 empty_line_re = re.compile("^\s*$")
56 binary_name_re = re.compile(".*[chwall|ste|chwall_ste].*\.bin", re.IGNORECASE)
57 policy_name_re = re.compile(".*[chwall|ste|chwall_ste].*", re.IGNORECASE)
59 #other global variables
60 NULL_SSIDREF = 0
62 log = logging.getLogger("xend.util.security")
64 # Our own exception definition. It is masked (pass) if raised and
65 # whoever raises this exception must provide error information.
66 class ACMError(Exception):
67 def __init__(self,value):
68 self.value = value
69 def __str__(self):
70 return repr(self.value)
74 def err(msg):
75 """Raise ACM exception.
76 """
77 sys.stderr.write("ACMError: " + msg + "\n")
78 raise ACMError(msg)
82 active_policy = None
85 def refresh_security_policy():
86 """
87 retrieves security policy
88 """
89 global active_policy
91 try:
92 active_policy = acm.policy()
93 except:
94 active_policy = "INACTIVE"
96 # now set active_policy
97 refresh_security_policy()
99 def on():
100 """
101 returns none if security policy is off (not compiled),
102 any string otherwise, use it: if not security.on() ...
103 """
104 refresh_security_policy()
105 return (active_policy not in ['INACTIVE', 'NULL'])
109 # Assumes a 'security' info [security access_control ...] [ssidref ...]
110 def get_security_info(info, field):
111 """retrieves security field from self.info['security'])
112 allowed search fields: ssidref, label, policy
113 """
114 if isinstance(info, dict):
115 security = info['security']
116 elif isinstance(info, list):
117 security = sxp.child_value(info, 'security', )
118 if not security:
119 if field == 'ssidref':
120 #return default ssid
121 return 0
122 else:
123 err("Security information not found in info struct.")
125 if field == 'ssidref':
126 search = 'ssidref'
127 elif field in ['policy', 'label']:
128 search = 'access_control'
129 else:
130 err("Illegal field in get_security_info.")
132 for idx in range(0, len(security)):
133 if search != security[idx][0]:
134 continue
135 if search == 'ssidref':
136 return int(security[idx][1])
137 else:
138 for aidx in range(0, len(security[idx])):
139 if security[idx][aidx][0] == field:
140 return str(security[idx][aidx][1])
142 if search == 'ssidref':
143 return 0
144 else:
145 return None
149 def get_security_printlabel(info):
150 """retrieves printable security label from self.info['security']),
151 preferably the label name and otherwise (if label is not specified
152 in config and cannot be found in mapping file) a hex string of the
153 ssidref or none if both not available
154 """
155 try:
156 if not on():
157 return "INACTIVE"
158 if active_policy in ["DEFAULT"]:
159 return "DEFAULT"
161 printlabel = get_security_info(info, 'label')
162 if printlabel:
163 return printlabel
164 ssidref = get_security_info(info, 'ssidref')
165 if not ssidref:
166 return None
167 #try to translate ssidref to a label
168 result = ssidref2label(ssidref)
169 if not result:
170 printlabel = "0x%08x" % ssidref
171 else:
172 printlabel = result
173 return printlabel
174 except ACMError:
175 #don't throw an exception in xm list
176 return "ERROR"
180 def getmapfile(policyname):
181 """
182 in: if policyname is None then the currently
183 active hypervisor policy is used
184 out: 1. primary policy, 2. secondary policy,
185 3. open file descriptor for mapping file, and
186 4. True if policy file is available, False otherwise
187 """
188 if not policyname:
189 policyname = active_policy
190 map_file_ok = False
191 primary = None
192 secondary = None
193 #strip last part of policy as file name part
194 policy_dir_list = string.split(policyname, ".")
195 policy_file = policy_dir_list.pop()
196 if len(policy_dir_list) > 0:
197 policy_dir = string.join(policy_dir_list, "/") + "/"
198 else:
199 policy_dir = ""
201 map_filename = policy_dir_prefix + "/" + policy_dir + policy_file + ".map"
202 # check if it is there, if not check if policy file is there
203 if not os.path.isfile(map_filename):
204 policy_filename = policy_dir_prefix + "/" + policy_dir + policy_file + "-security_policy.xml"
205 if not os.path.isfile(policy_filename):
206 err("Policy file \'" + policy_filename + "\' not found.")
207 else:
208 err("Mapping file \'" + map_filename + "\' not found." +
209 " Use xm makepolicy to create it.")
211 f = open(map_filename)
212 for line in f:
213 if policy_reference_entry_re.match(line):
214 l = line.split()
215 if (len(l) == 2) and (l[1] == policyname):
216 map_file_ok = True
217 elif primary_entry_re.match(line):
218 l = line.split()
219 if len(l) == 2:
220 primary = l[1]
221 elif secondary_entry_re.match(line):
222 l = line.split()
223 if len(l) == 2:
224 secondary = l[1]
225 f.close()
226 f = open(map_filename)
227 if map_file_ok and primary and secondary:
228 return (primary, secondary, f, True)
229 else:
230 err("Mapping file inconsistencies found. Try makepolicy to create a new one.")
234 def ssidref2label(ssidref_var):
235 """
236 returns labelname corresponding to ssidref;
237 maps current policy to default directory
238 to find mapping file
239 """
240 #1. translated permitted input formats
241 if isinstance(ssidref_var, str):
242 ssidref_var.strip()
243 if ssidref_var[0:2] == "0x":
244 ssidref = int(ssidref_var[2:], 16)
245 else:
246 ssidref = int(ssidref_var)
247 elif isinstance(ssidref_var, int):
248 ssidref = ssidref_var
249 else:
250 err("Instance type of ssidref not supported (must be of type 'str' or 'int')")
252 (primary, secondary, f, pol_exists) = getmapfile(None)
253 if not f:
254 if (pol_exists):
255 err("Mapping file for policy \'" + policyname + "\' not found.\n" +
256 "Please use makepolicy command to create mapping file!")
257 else:
258 err("Policy file for \'" + active_policy + "\' not found.")
260 #2. get labelnames for both ssidref parts
261 pri_ssid = ssidref & 0xffff
262 sec_ssid = ssidref >> 16
263 pri_null_ssid = NULL_SSIDREF & 0xffff
264 sec_null_ssid = NULL_SSIDREF >> 16
265 pri_labels = []
266 sec_labels = []
267 labels = []
269 for line in f:
270 l = line.split()
271 if (len(l) < 5) or (l[0] != "LABEL->SSID"):
272 continue
273 if primary and (l[2] == primary) and (int(l[4], 16) == pri_ssid):
274 pri_labels.append(l[3])
275 if secondary and (l[2] == secondary) and (int(l[4], 16) == sec_ssid):
276 sec_labels.append(l[3])
277 f.close()
279 #3. get the label that is in both lists (combination must be a single label)
280 if (primary == "CHWALL") and (pri_ssid == pri_null_ssid) and (sec_ssid != sec_null_ssid):
281 labels = sec_labels
282 elif (secondary == "CHWALL") and (pri_ssid != pri_null_ssid) and (sec_ssid == sec_null_ssid):
283 labels = pri_labels
284 elif secondary == "NULL":
285 labels = pri_labels
286 else:
287 for i in pri_labels:
288 for j in sec_labels:
289 if (i==j):
290 labels.append(i)
291 if len(labels) != 1:
292 err("Label for ssidref \'" + str(ssidref) +
293 "\' unknown or not unique in policy \'" + active_policy + "\'")
295 return labels[0]
299 def label2ssidref(labelname, policyname, type):
300 """
301 returns ssidref corresponding to labelname;
302 maps current policy to default directory
303 to find mapping file """
305 if policyname in ['NULL', 'INACTIVE', 'DEFAULT']:
306 err("Cannot translate labels for \'" + policyname + "\' policy.")
308 allowed_types = ['ANY']
309 if type == 'dom':
310 allowed_types.append('VM')
311 elif type == 'res':
312 allowed_types.append('RES')
313 else:
314 err("Invalid type. Must specify 'dom' or 'res'.")
316 (primary, secondary, f, pol_exists) = getmapfile(policyname)
318 #2. get labelnames for ssidref parts and find a common label
319 pri_ssid = []
320 sec_ssid = []
321 for line in f:
322 l = line.split()
323 if (len(l) < 5) or (l[0] != "LABEL->SSID"):
324 continue
325 if primary and (l[1] in allowed_types) and (l[2] == primary) and (l[3] == labelname):
326 pri_ssid.append(int(l[4], 16))
327 if secondary and (l[1] in allowed_types) and (l[2] == secondary) and (l[3] == labelname):
328 sec_ssid.append(int(l[4], 16))
329 f.close()
330 if (type == 'res') and (primary == "CHWALL") and (len(pri_ssid) == 0):
331 pri_ssid.append(NULL_SSIDREF)
332 elif (type == 'res') and (secondary == "CHWALL") and (len(sec_ssid) == 0):
333 sec_ssid.append(NULL_SSIDREF)
335 #3. sanity check and composition of ssidref
336 if (len(pri_ssid) == 0) or ((len(sec_ssid) == 0) and (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]
347 def refresh_ssidref(config):
348 """
349 looks up ssidref from security field
350 and refreshes the value if label exists
351 """
352 #called by dom0, policy could have changed after xen.utils.security was initialized
353 refresh_security_policy()
355 security = None
356 if isinstance(config, dict):
357 security = config['security']
358 elif isinstance(config, list):
359 security = sxp.child_value(config, 'security',)
360 else:
361 err("Instance type of config parameter not supported.")
362 if not security:
363 #nothing to do (no security label attached)
364 return config
366 policyname = None
367 labelname = None
368 # compose new security field
369 for idx in range(0, len(security)):
370 if security[idx][0] == 'ssidref':
371 security.pop(idx)
372 break
373 elif security[idx][0] == 'access_control':
374 for jdx in [1, 2]:
375 if security[idx][jdx][0] == 'label':
376 labelname = security[idx][jdx][1]
377 elif security[idx][jdx][0] == 'policy':
378 policyname = security[idx][jdx][1]
379 else:
380 err("Illegal field in access_control")
381 #verify policy is correct
382 if active_policy != policyname:
383 err("Policy \'" + policyname + "\' in label does not match active policy \'"
384 + active_policy +"\'!")
386 new_ssidref = label2ssidref(labelname, policyname, 'dom')
387 if not new_ssidref:
388 err("SSIDREF refresh failed!")
390 security.append([ 'ssidref',str(new_ssidref)])
391 security = ['security', security ]
393 for idx in range(0,len(config)):
394 if config[idx][0] == 'security':
395 config.pop(idx)
396 break
397 config.append(security)
401 def get_ssid(domain):
402 """
403 enables domains to retrieve the label / ssidref of a running domain
404 """
405 if not on():
406 err("No policy active.")
408 if isinstance(domain, str):
409 domain_int = int(domain)
410 elif isinstance(domain, int):
411 domain_int = domain
412 else:
413 err("Illegal parameter type.")
414 try:
415 ssid_info = acm.getssid(int(domain_int))
416 except:
417 err("Cannot determine security information.")
419 if active_policy in ["DEFAULT"]:
420 label = "DEFAULT"
421 else:
422 label = ssidref2label(ssid_info["ssidref"])
423 return(ssid_info["policyreference"],
424 label,
425 ssid_info["policytype"],
426 ssid_info["ssidref"])
430 def get_decision(arg1, arg2):
431 """
432 enables domains to retrieve access control decisions from
433 the hypervisor Access Control Module.
434 IN: args format = ['domid', id] or ['ssidref', ssidref]
435 or ['access_control', ['policy', policy], ['label', label], ['type', type]]
436 """
438 if not on():
439 err("No policy active.")
441 #translate labels before calling low-level function
442 if arg1[0] == 'access_control':
443 if (arg1[1][0] != 'policy') or (arg1[2][0] != 'label') or (arg1[3][0] != 'type'):
444 err("Argument type not supported.")
445 ssidref = label2ssidref(arg1[2][1], arg1[1][1], arg1[3][1])
446 arg1 = ['ssidref', str(ssidref)]
447 if arg2[0] == 'access_control':
448 if (arg2[1][0] != 'policy') or (arg2[2][0] != 'label') or (arg2[3][0] != 'type'):
449 err("Argument type not supported.")
450 ssidref = label2ssidref(arg2[2][1], arg2[1][1], arg2[3][1])
451 arg2 = ['ssidref', str(ssidref)]
453 # accept only int or string types for domid and ssidref
454 if isinstance(arg1[1], int):
455 arg1[1] = str(arg1[1])
456 if isinstance(arg2[1], int):
457 arg2[1] = str(arg2[1])
458 if not isinstance(arg1[1], str) or not isinstance(arg2[1], str):
459 err("Invalid id or ssidref type, string or int required")
461 try:
462 decision = acm.getdecision(arg1[0], arg1[1], arg2[0], arg2[1])
463 except:
464 err("Cannot determine decision.")
466 if decision:
467 return decision
468 else:
469 err("Cannot determine decision (Invalid parameter).")
473 def make_policy(policy_name):
474 policy_file = string.join(string.split(policy_name, "."), "/")
475 if not os.path.isfile(policy_dir_prefix + "/" + policy_file + "-security_policy.xml"):
476 err("Unknown policy \'" + policy_name + "\'")
478 (ret, output) = commands.getstatusoutput(xensec_xml2bin + " -d " + policy_dir_prefix + " " + policy_file)
479 if ret:
480 err("Creating policy failed:\n" + output)
484 def load_policy(policy_name):
485 global active_policy
486 policy_file = policy_dir_prefix + "/" + string.join(string.split(policy_name, "."), "/")
487 if not os.path.isfile(policy_file + ".bin"):
488 if os.path.isfile(policy_file + "-security_policy.xml"):
489 err("Binary file does not exist." +
490 "Please use makepolicy to build the policy binary.")
491 else:
492 err("Unknown Policy " + policy_name)
494 #require this policy to be the first or the same as installed
495 if active_policy not in ['DEFAULT', policy_name]:
496 err("Active policy \'" + active_policy +
497 "\' incompatible with new policy \'" + policy_name + "\'")
498 (ret, output) = commands.getstatusoutput(xensec_tool + " loadpolicy " + policy_file + ".bin")
499 if ret:
500 err("Loading policy failed:\n" + output)
501 else:
502 # refresh active policy
503 refresh_security_policy()
507 def dump_policy():
508 if active_policy in ['NULL', 'INACTIVE']:
509 err("\'" + active_policy + "\' policy. Nothing to dump.")
511 (ret, output) = commands.getstatusoutput(xensec_tool + " getpolicy")
512 if ret:
513 err("Dumping hypervisor policy failed:\n" + output)
514 print output
518 def list_labels(policy_name, condition):
519 if (not policy_name) and (active_policy) in ["NULL", "INACTIVE", "DEFAULT"]:
520 err("Current policy \'" + active_policy + "\' has no labels defined.\n")
522 (primary, secondary, f, pol_exists) = getmapfile(policy_name)
523 if not f:
524 if pol_exists:
525 err("Cannot find mapfile for policy \'" + policy_name +
526 "\'.\nPlease use makepolicy to create mapping file.")
527 else:
528 err("Unknown policy \'" + policy_name + "\'")
530 labels = []
531 for line in f:
532 if condition.match(line):
533 label = line.split()[3]
534 if label not in labels:
535 labels.append(label)
536 return labels
539 def get_res_label(resource):
540 """Returns resource label information (label, policy) if it exists.
541 Otherwise returns null label and policy.
542 """
543 def default_res_label():
544 ssidref = NULL_SSIDREF
545 if on():
546 label = ssidref2label(ssidref)
547 else:
548 label = None
549 return (label, 'NULL')
551 (label, policy) = default_res_label()
553 # load the resource label file
554 res_label_cache = {}
555 try:
556 res_label_cache = dictio.dict_read("resources", res_label_filename)
557 except:
558 log.info("Resource label file not found.")
559 return default_res_label()
561 # find the resource information
562 if res_label_cache.has_key(resource):
563 (policy, label) = res_label_cache[resource]
565 return (label, policy)
568 def get_res_security_details(resource):
569 """Returns the (label, ssidref, policy) associated with a given
570 resource from the global resource label file.
571 """
572 def default_security_details():
573 ssidref = NULL_SSIDREF
574 if on():
575 label = ssidref2label(ssidref)
576 else:
577 label = None
578 policy = active_policy
579 return (label, ssidref, policy)
581 (label, ssidref, policy) = default_security_details()
583 # find the entry associated with this resource
584 (label, policy) = get_res_label(resource)
585 if policy == 'NULL':
586 log.info("Resource label for "+resource+" not in file, using DEFAULT.")
587 return default_security_details()
589 # is this resource label for the running policy?
590 if policy == active_policy:
591 ssidref = label2ssidref(label, policy, 'res')
592 else:
593 log.info("Resource label not for active policy, using DEFAULT.")
594 return default_security_details()
596 return (label, ssidref, policy)
599 def res_security_check(resource, domain_label):
600 """Checks if the given resource can be used by the given domain
601 label. Returns 1 if the resource can be used, otherwise 0.
602 """
603 rtnval = 1
605 # if security is on, ask the hypervisor for a decision
606 if on():
607 (label, ssidref, policy) = get_res_security_details(resource)
608 domac = ['access_control']
609 domac.append(['policy', active_policy])
610 domac.append(['label', domain_label])
611 domac.append(['type', 'dom'])
612 decision = get_decision(domac, ['ssidref', str(ssidref)])
614 # provide descriptive error messages
615 if decision == 'DENIED':
616 if label == ssidref2label(NULL_SSIDREF):
617 raise ACMError("Resource '"+resource+"' is not labeled")
618 rtnval = 0
619 else:
620 raise ACMError("Permission denied for resource '"+resource+"' because label '"+label+"' is not allowed")
621 rtnval = 0
623 # security is off, make sure resource isn't labeled
624 else:
625 (label, policy) = get_res_label(resource)
626 if policy != 'NULL':
627 raise ACMError("Security is off, but '"+resource+"' is labeled")
628 rtnval = 0
630 return rtnval