ia64/xen-unstable

view tools/python/xen/util/security.py @ 10396:0de8a4a023d0

[ACM] Provide the framework needed for resource labeling.

Subsequent patches will follow in the coming weeks that will enable
Xen ACM to control assignment of resources (e.g., block devices and
networking) to virtual machines based on resource labels and the
active security policy.

Signed-off-by: Bryan D. Payne <bdpayne@us.ibm.com>
Signed-off-by: Reiner Sailer <sailer@us.ibm.com>
author kaf24@firebug.cl.cam.ac.uk
date Tue Jun 13 15:38:58 2006 +0100 (2006-06-13)
parents bcfbbca9cf08
children 25c6ea6d4024 8922c1fbe684
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 #============================================================================
19 import commands
20 import logging
21 import sys, os, string, re
22 import traceback
23 import shutil
24 from xen.lowlevel import acm
25 from xen.xend import sxp
27 #global directories and tools for security management
28 policy_dir_prefix = "/etc/xen/acm-security/policies"
29 boot_filename = "/boot/grub/menu.lst"
30 xensec_xml2bin = "/usr/sbin/xensec_xml2bin"
31 xensec_tool = "/usr/sbin/xensec_tool"
33 #global patterns for map file
34 #police_reference_tagname = "POLICYREFERENCENAME"
35 primary_entry_re = re.compile("\s*PRIMARY\s+.*", re.IGNORECASE)
36 secondary_entry_re = re.compile("\s*SECONDARY\s+.*", re.IGNORECASE)
37 label_template_re = re.compile(".*security_label_template.xml", re.IGNORECASE)
38 mapping_filename_re = re.compile(".*\.map", re.IGNORECASE)
39 policy_reference_entry_re = re.compile("\s*POLICYREFERENCENAME\s+.*", re.IGNORECASE)
40 vm_label_re = re.compile("\s*LABEL->SSID\s+VM\s+.*", re.IGNORECASE)
41 res_label_re = re.compile("\s*LABEL->SSID\s+RES\s+.*", re.IGNORECASE)
42 all_label_re = re.compile("\s*LABEL->SSID\s+.*", re.IGNORECASE)
43 access_control_re = re.compile("\s*access_control\s*=", re.IGNORECASE)
45 #global patterns for boot configuration file
46 xen_title_re = re.compile("\s*title\s+XEN", re.IGNORECASE)
47 any_title_re = re.compile("\s*title\s", re.IGNORECASE)
48 xen_kernel_re = re.compile("\s*kernel.*xen.*\.gz", re.IGNORECASE)
49 kernel_ver_re = re.compile("\s*module.*vmlinuz", re.IGNORECASE)
50 any_module_re = re.compile("\s*module\s", re.IGNORECASE)
51 empty_line_re = re.compile("^\s*$")
52 binary_name_re = re.compile(".*[chwall|ste|chwall_ste].*\.bin", re.IGNORECASE)
53 policy_name_re = re.compile(".*[chwall|ste|chwall_ste].*", re.IGNORECASE)
55 #other global variables
56 NULL_SSIDREF = 0
58 log = logging.getLogger("xend.util.security")
60 # Our own exception definition. It is masked (pass) if raised and
61 # whoever raises this exception must provide error information.
62 class ACMError(Exception):
63 def __init__(self,value):
64 self.value = value
65 def __str__(self):
66 return repr(self.value)
70 def err(msg):
71 """Raise ACM exception.
72 """
73 sys.stderr.write("ACMError: " + msg + "\n")
74 raise ACMError(msg)
78 active_policy = None
81 def refresh_security_policy():
82 """
83 retrieves security policy
84 """
85 global active_policy
87 try:
88 active_policy = acm.policy()
89 except:
90 active_policy = "INACTIVE"
92 # now set active_policy
93 refresh_security_policy()
95 def on():
96 """
97 returns none if security policy is off (not compiled),
98 any string otherwise, use it: if not security.on() ...
99 """
100 refresh_security_policy()
101 return (active_policy not in ['INACTIVE', 'NULL'])
105 # Assumes a 'security' info [security access_control ...] [ssidref ...]
106 def get_security_info(info, field):
107 """retrieves security field from self.info['security'])
108 allowed search fields: ssidref, label, policy
109 """
110 if isinstance(info, dict):
111 security = info['security']
112 elif isinstance(info, list):
113 security = sxp.child_value(info, 'security', )
114 if not security:
115 if field == 'ssidref':
116 #return default ssid
117 return 0
118 else:
119 err("Security information not found in info struct.")
121 if field == 'ssidref':
122 search = 'ssidref'
123 elif field in ['policy', 'label']:
124 search = 'access_control'
125 else:
126 err("Illegal field in get_security_info.")
128 for idx in range(0, len(security)):
129 if search != security[idx][0]:
130 continue
131 if search == 'ssidref':
132 return int(security[idx][1])
133 else:
134 for aidx in range(0, len(security[idx])):
135 if security[idx][aidx][0] == field:
136 return str(security[idx][aidx][1])
138 if search == 'ssidref':
139 return 0
140 else:
141 return None
145 def get_security_printlabel(info):
146 """retrieves printable security label from self.info['security']),
147 preferably the label name and otherwise (if label is not specified
148 in config and cannot be found in mapping file) a hex string of the
149 ssidref or none if both not available
150 """
151 try:
152 if not on():
153 return "INACTIVE"
154 if active_policy in ["DEFAULT"]:
155 return "DEFAULT"
157 printlabel = get_security_info(info, 'label')
158 if printlabel:
159 return printlabel
160 ssidref = get_security_info(info, 'ssidref')
161 if not ssidref:
162 return None
163 #try to translate ssidref to a label
164 result = ssidref2label(ssidref)
165 if not result:
166 printlabel = "0x%08x" % ssidref
167 else:
168 printlabel = result
169 return printlabel
170 except ACMError:
171 #don't throw an exception in xm list
172 return "ERROR"
176 def getmapfile(policyname):
177 """
178 in: if policyname is None then the currently
179 active hypervisor policy is used
180 out: 1. primary policy, 2. secondary policy,
181 3. open file descriptor for mapping file, and
182 4. True if policy file is available, False otherwise
183 """
184 if not policyname:
185 policyname = active_policy
186 map_file_ok = False
187 primary = None
188 secondary = None
189 #strip last part of policy as file name part
190 policy_dir_list = string.split(policyname, ".")
191 policy_file = policy_dir_list.pop()
192 if len(policy_dir_list) > 0:
193 policy_dir = string.join(policy_dir_list, "/") + "/"
194 else:
195 policy_dir = ""
197 map_filename = policy_dir_prefix + "/" + policy_dir + policy_file + ".map"
198 # check if it is there, if not check if policy file is there
199 if not os.path.isfile(map_filename):
200 policy_filename = policy_dir_prefix + "/" + policy_dir + policy_file + "-security_policy.xml"
201 if not os.path.isfile(policy_filename):
202 err("Policy file \'" + policy_filename + "\' not found.")
203 else:
204 err("Mapping file \'" + map_filename + "\' not found." +
205 " Use xm makepolicy to create it.")
207 f = open(map_filename)
208 for line in f:
209 if policy_reference_entry_re.match(line):
210 l = line.split()
211 if (len(l) == 2) and (l[1] == policyname):
212 map_file_ok = True
213 elif primary_entry_re.match(line):
214 l = line.split()
215 if len(l) == 2:
216 primary = l[1]
217 elif secondary_entry_re.match(line):
218 l = line.split()
219 if len(l) == 2:
220 secondary = l[1]
221 f.close()
222 f = open(map_filename)
223 if map_file_ok and primary and secondary:
224 return (primary, secondary, f, True)
225 else:
226 err("Mapping file inconsistencies found. Try makepolicy to create a new one.")
230 def ssidref2label(ssidref_var):
231 """
232 returns labelname corresponding to ssidref;
233 maps current policy to default directory
234 to find mapping file
235 """
236 #1. translated permitted input formats
237 if isinstance(ssidref_var, str):
238 ssidref_var.strip()
239 if ssidref_var[0:2] == "0x":
240 ssidref = int(ssidref_var[2:], 16)
241 else:
242 ssidref = int(ssidref_var)
243 elif isinstance(ssidref_var, int):
244 ssidref = ssidref_var
245 else:
246 err("Instance type of ssidref not supported (must be of type 'str' or 'int')")
248 (primary, secondary, f, pol_exists) = getmapfile(None)
249 if not f:
250 if (pol_exists):
251 err("Mapping file for policy \'" + policyname + "\' not found.\n" +
252 "Please use makepolicy command to create mapping file!")
253 else:
254 err("Policy file for \'" + active_policy + "\' not found.")
256 #2. get labelnames for both ssidref parts
257 pri_ssid = ssidref & 0xffff
258 sec_ssid = ssidref >> 16
259 pri_null_ssid = NULL_SSIDREF & 0xffff
260 sec_null_ssid = NULL_SSIDREF >> 16
261 pri_labels = []
262 sec_labels = []
263 labels = []
265 for line in f:
266 l = line.split()
267 if (len(l) < 5) or (l[0] != "LABEL->SSID"):
268 continue
269 if primary and (l[2] == primary) and (int(l[4], 16) == pri_ssid):
270 pri_labels.append(l[3])
271 if secondary and (l[2] == secondary) and (int(l[4], 16) == sec_ssid):
272 sec_labels.append(l[3])
273 f.close()
275 #3. get the label that is in both lists (combination must be a single label)
276 if (primary == "CHWALL") and (pri_ssid == pri_null_ssid) and (sec_ssid != sec_null_ssid):
277 labels = sec_labels
278 elif (secondary == "CHWALL") and (pri_ssid != pri_null_ssid) and (sec_ssid == sec_null_ssid):
279 labels = pri_labels
280 elif secondary == "NULL":
281 labels = pri_labels
282 else:
283 for i in pri_labels:
284 for j in sec_labels:
285 if (i==j):
286 labels.append(i)
287 if len(labels) != 1:
288 err("Label for ssidref \'" + str(ssidref) +
289 "\' unknown or not unique in policy \'" + active_policy + "\'")
291 return labels[0]
295 def label2ssidref(labelname, policyname, type):
296 """
297 returns ssidref corresponding to labelname;
298 maps current policy to default directory
299 to find mapping file """
301 if policyname in ['NULL', 'INACTIVE', 'DEFAULT']:
302 err("Cannot translate labels for \'" + policyname + "\' policy.")
304 allowed_types = ['ANY']
305 if type == 'dom':
306 allowed_types.append('VM')
307 elif type == 'res':
308 allowed_types.append('RES')
309 else:
310 err("Invalid type. Must specify 'dom' or 'res'.")
312 (primary, secondary, f, pol_exists) = getmapfile(policyname)
314 #2. get labelnames for ssidref parts and find a common label
315 pri_ssid = []
316 sec_ssid = []
317 for line in f:
318 l = line.split()
319 if (len(l) < 5) or (l[0] != "LABEL->SSID"):
320 continue
321 if primary and (l[1] in allowed_types) and (l[2] == primary) and (l[3] == labelname):
322 pri_ssid.append(int(l[4], 16))
323 if secondary and (l[1] in allowed_types) and (l[2] == secondary) and (l[3] == labelname):
324 sec_ssid.append(int(l[4], 16))
325 f.close()
326 if (type == 'res') and (primary == "CHWALL") and (len(pri_ssid) == 0):
327 pri_ssid.append(NULL_SSIDREF)
328 elif (type == 'res') and (secondary == "CHWALL") and (len(sec_ssid) == 0):
329 sec_ssid.append(NULL_SSIDREF)
331 #3. sanity check and composition of ssidref
332 if (len(pri_ssid) == 0) or ((len(sec_ssid) == 0) and (secondary != "NULL")):
333 err("Label \'" + labelname + "\' not found.")
334 elif (len(pri_ssid) > 1) or (len(sec_ssid) > 1):
335 err("Label \'" + labelname + "\' not unique in policy (policy error)")
336 if secondary == "NULL":
337 return pri_ssid[0]
338 else:
339 return (sec_ssid[0] << 16) | pri_ssid[0]
343 def refresh_ssidref(config):
344 """
345 looks up ssidref from security field
346 and refreshes the value if label exists
347 """
348 #called by dom0, policy could have changed after xen.utils.security was initialized
349 refresh_security_policy()
351 security = None
352 if isinstance(config, dict):
353 security = config['security']
354 elif isinstance(config, list):
355 security = sxp.child_value(config, 'security',)
356 else:
357 err("Instance type of config parameter not supported.")
358 if not security:
359 #nothing to do (no security label attached)
360 return config
362 policyname = None
363 labelname = None
364 # compose new security field
365 for idx in range(0, len(security)):
366 if security[idx][0] == 'ssidref':
367 security.pop(idx)
368 break
369 elif security[idx][0] == 'access_control':
370 for jdx in [1, 2]:
371 if security[idx][jdx][0] == 'label':
372 labelname = security[idx][jdx][1]
373 elif security[idx][jdx][0] == 'policy':
374 policyname = security[idx][jdx][1]
375 else:
376 err("Illegal field in access_control")
377 #verify policy is correct
378 if active_policy != policyname:
379 err("Policy \'" + policyname + "\' in label does not match active policy \'"
380 + active_policy +"\'!")
382 new_ssidref = label2ssidref(labelname, policyname, 'dom')
383 if not new_ssidref:
384 err("SSIDREF refresh failed!")
386 security.append([ 'ssidref',str(new_ssidref)])
387 security = ['security', security ]
389 for idx in range(0,len(config)):
390 if config[idx][0] == 'security':
391 config.pop(idx)
392 break
393 config.append(security)
397 def get_ssid(domain):
398 """
399 enables domains to retrieve the label / ssidref of a running domain
400 """
401 if not on():
402 err("No policy active.")
404 if isinstance(domain, str):
405 domain_int = int(domain)
406 elif isinstance(domain, int):
407 domain_int = domain
408 else:
409 err("Illegal parameter type.")
410 try:
411 ssid_info = acm.getssid(int(domain_int))
412 except:
413 err("Cannot determine security information.")
415 if active_policy in ["DEFAULT"]:
416 label = "DEFAULT"
417 else:
418 label = ssidref2label(ssid_info["ssidref"])
419 return(ssid_info["policyreference"],
420 label,
421 ssid_info["policytype"],
422 ssid_info["ssidref"])
426 def get_decision(arg1, arg2):
427 """
428 enables domains to retrieve access control decisions from
429 the hypervisor Access Control Module.
430 IN: args format = ['domid', id] or ['ssidref', ssidref]
431 or ['access_control', ['policy', policy], ['label', label], ['type', type]]
432 """
434 if not on():
435 err("No policy active.")
437 #translate labels before calling low-level function
438 if arg1[0] == 'access_control':
439 if (arg1[1][0] != 'policy') or (arg1[2][0] != 'label') or (arg1[3][0] != 'type'):
440 err("Argument type not supported.")
441 ssidref = label2ssidref(arg1[2][1], arg1[1][1], arg1[3][1])
442 arg1 = ['ssidref', str(ssidref)]
443 if arg2[0] == 'access_control':
444 if (arg2[1][0] != 'policy') or (arg2[2][0] != 'label') or (arg2[3][0] != 'type'):
445 err("Argument type not supported.")
446 ssidref = label2ssidref(arg2[2][1], arg2[1][1], arg2[3][1])
447 arg2 = ['ssidref', str(ssidref)]
449 # accept only int or string types for domid and ssidref
450 if isinstance(arg1[1], int):
451 arg1[1] = str(arg1[1])
452 if isinstance(arg2[1], int):
453 arg2[1] = str(arg2[1])
454 if not isinstance(arg1[1], str) or not isinstance(arg2[1], str):
455 err("Invalid id or ssidref type, string or int required")
457 try:
458 decision = acm.getdecision(arg1[0], arg1[1], arg2[0], arg2[1])
459 except:
460 err("Cannot determine decision.")
462 if decision:
463 return decision
464 else:
465 err("Cannot determine decision (Invalid parameter).")
469 def make_policy(policy_name):
470 policy_file = string.join(string.split(policy_name, "."), "/")
471 if not os.path.isfile(policy_dir_prefix + "/" + policy_file + "-security_policy.xml"):
472 err("Unknown policy \'" + policy_name + "\'")
474 (ret, output) = commands.getstatusoutput(xensec_xml2bin + " -d " + policy_dir_prefix + " " + policy_file)
475 if ret:
476 err("Creating policy failed:\n" + output)
480 def load_policy(policy_name):
481 global active_policy
482 policy_file = policy_dir_prefix + "/" + string.join(string.split(policy_name, "."), "/")
483 if not os.path.isfile(policy_file + ".bin"):
484 if os.path.isfile(policy_file + "-security_policy.xml"):
485 err("Binary file does not exist." +
486 "Please use makepolicy to build the policy binary.")
487 else:
488 err("Unknown Policy " + policy_name)
490 #require this policy to be the first or the same as installed
491 if active_policy not in ['DEFAULT', policy_name]:
492 err("Active policy \'" + active_policy +
493 "\' incompatible with new policy \'" + policy_name + "\'")
494 (ret, output) = commands.getstatusoutput(xensec_tool + " loadpolicy " + policy_file + ".bin")
495 if ret:
496 err("Loading policy failed:\n" + output)
497 else:
498 # refresh active policy
499 refresh_security_policy()
503 def dump_policy():
504 if active_policy in ['NULL', 'INACTIVE']:
505 err("\'" + active_policy + "\' policy. Nothing to dump.")
507 (ret, output) = commands.getstatusoutput(xensec_tool + " getpolicy")
508 if ret:
509 err("Dumping hypervisor policy failed:\n" + output)
510 print output
514 def list_labels(policy_name, condition):
515 if (not policy_name) and (active_policy) in ["NULL", "INACTIVE", "DEFAULT"]:
516 err("Current policy \'" + active_policy + "\' has no labels defined.\n")
518 (primary, secondary, f, pol_exists) = getmapfile(policy_name)
519 if not f:
520 if pol_exists:
521 err("Cannot find mapfile for policy \'" + policy_name +
522 "\'.\nPlease use makepolicy to create mapping file.")
523 else:
524 err("Unknown policy \'" + policy_name + "\'")
526 labels = []
527 for line in f:
528 if condition.match(line):
529 label = line.split()[3]
530 if label not in labels:
531 labels.append(label)
532 return labels