ia64/xen-unstable

view tools/python/xen/util/security.py @ 9835:cf20dbbf5c2b

This patch adds new python access control management scripts, which
integrate into Xen Management and which support the new access control
labels (labels replace the ssidref numbers at the management user
interface).

Signed-off by: Reiner Sailer <sailer@us.ibm.com>
author smh22@firebug.cl.cam.ac.uk
date Mon Apr 24 10:58:25 2006 +0100 (2006-04-24)
parents
children bcfbbca9cf08
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)
57 log = logging.getLogger("xend.util.security")
59 # Our own exception definition. It is masked (pass) if raised and
60 # whoever raises this exception must provide error information.
61 class ACMError(Exception):
62 def __init__(self,value):
63 self.value = value
64 def __str__(self):
65 return repr(self.value)
69 def err(msg):
70 """Raise ACM exception.
71 """
72 sys.stderr.write("ACMError: " + msg + "\n")
73 raise ACMError(msg)
77 active_policy = None
80 def refresh_security_policy():
81 """
82 retrieves security policy
83 """
84 global active_policy
86 try:
87 active_policy = acm.policy()
88 except:
89 active_policy = "INACTIVE"
91 # now set active_policy
92 refresh_security_policy()
94 def on():
95 """
96 returns none if security policy is off (not compiled),
97 any string otherwise, use it: if not security.on() ...
98 """
99 refresh_security_policy()
100 return (active_policy not in ['INACTIVE', 'NULL'])
104 # Assumes a 'security' info [security access_control ...] [ssidref ...]
105 def get_security_info(info, field):
106 """retrieves security field from self.info['security'])
107 allowed search fields: ssidref, label, policy
108 """
109 if isinstance(info, dict):
110 security = info['security']
111 elif isinstance(info, list):
112 security = sxp.child_value(info, 'security', )
113 if not security:
114 if field == 'ssidref':
115 #return default ssid
116 return 0
117 else:
118 err("Security information not found in info struct.")
120 if field == 'ssidref':
121 search = 'ssidref'
122 elif field in ['policy', 'label']:
123 search = 'access_control'
124 else:
125 err("Illegal field in get_security_info.")
127 for idx in range(0, len(security)):
128 if search != security[idx][0]:
129 continue
130 if search == 'ssidref':
131 return int(security[idx][1])
132 else:
133 for aidx in range(0, len(security[idx])):
134 if security[idx][aidx][0] == field:
135 return str(security[idx][aidx][1])
137 if search == 'ssidref':
138 return 0
139 else:
140 return None
144 def get_security_printlabel(info):
145 """retrieves printable security label from self.info['security']),
146 preferably the label name and otherwise (if label is not specified
147 in config and cannot be found in mapping file) a hex string of the
148 ssidref or none if both not available
149 """
150 try:
151 if not on():
152 return "INACTIVE"
153 if active_policy in ["DEFAULT"]:
154 return "DEFAULT"
156 printlabel = get_security_info(info, 'label')
157 if printlabel:
158 return printlabel
159 ssidref = get_security_info(info, 'ssidref')
160 if not ssidref:
161 return None
162 #try to translate ssidref to a label
163 result = ssidref2label(ssidref)
164 if not result:
165 printlabel = "0x%08x" % ssidref
166 else:
167 printlabel = result
168 return printlabel
169 except ACMError:
170 #don't throw an exception in xm list
171 return "ERROR"
175 def getmapfile(policyname):
176 """
177 in: if policyname is None then the currently
178 active hypervisor policy is used
179 out: 1. primary policy, 2. secondary policy,
180 3. open file descriptor for mapping file, and
181 4. True if policy file is available, False otherwise
182 """
183 if not policyname:
184 policyname = active_policy
185 map_file_ok = False
186 primary = None
187 secondary = None
188 #strip last part of policy as file name part
189 policy_dir_list = string.split(policyname, ".")
190 policy_file = policy_dir_list.pop()
191 if len(policy_dir_list) > 0:
192 policy_dir = string.join(policy_dir_list, "/") + "/"
193 else:
194 policy_dir = ""
196 map_filename = policy_dir_prefix + "/" + policy_dir + policy_file + ".map"
197 # check if it is there, if not check if policy file is there
198 if not os.path.isfile(map_filename):
199 policy_filename = policy_dir_prefix + "/" + policy_dir + policy_file + "-security_policy.xml"
200 if not os.path.isfile(policy_filename):
201 err("Policy file \'" + policy_filename + "\' not found.")
202 else:
203 err("Mapping file \'" + map_filename + "\' not found." +
204 " Use xm makepolicy to create it.")
206 f = open(map_filename)
207 for line in f:
208 if policy_reference_entry_re.match(line):
209 l = line.split()
210 if (len(l) == 2) and (l[1] == policyname):
211 map_file_ok = True
212 elif primary_entry_re.match(line):
213 l = line.split()
214 if len(l) == 2:
215 primary = l[1]
216 elif secondary_entry_re.match(line):
217 l = line.split()
218 if len(l) == 2:
219 secondary = l[1]
220 f.close()
221 f = open(map_filename)
222 if map_file_ok and primary and secondary:
223 return (primary, secondary, f, True)
224 else:
225 err("Mapping file inconsistencies found. Try makepolicy to create a new one.")
229 def ssidref2label(ssidref_var):
230 """
231 returns labelname corresponding to ssidref;
232 maps current policy to default directory
233 to find mapping file
234 """
235 #1. translated permitted input formats
236 if isinstance(ssidref_var, str):
237 ssidref_var.strip()
238 if ssidref_var[0:2] == "0x":
239 ssidref = int(ssidref_var[2:], 16)
240 else:
241 ssidref = int(ssidref_var)
242 elif isinstance(ssidref_var, int):
243 ssidref = ssidref_var
244 else:
245 err("Instance type of ssidref not supported (must be of type 'str' or 'int')")
247 (primary, secondary, f, pol_exists) = getmapfile(None)
248 if not f:
249 if (pol_exists):
250 err("Mapping file for policy \'" + policyname + "\' not found.\n" +
251 "Please use makepolicy command to create mapping file!")
252 else:
253 err("Policy file for \'" + active_policy + "\' not found.")
255 #2. get labelnames for both ssidref parts
256 pri_ssid = ssidref & 0xffff
257 sec_ssid = ssidref >> 16
258 pri_labels = []
259 sec_labels = []
260 labels = []
262 for line in f:
263 l = line.split()
264 if (len(l) < 5) or (l[0] != "LABEL->SSID"):
265 continue
266 if primary and (l[2] == primary) and (int(l[4], 16) == pri_ssid):
267 pri_labels.append(l[3])
268 if secondary and (l[2] == secondary) and (int(l[4], 16) == sec_ssid):
269 sec_labels.append(l[3])
270 f.close()
272 #3. get the label that is in both lists (combination must be a single label)
273 if secondary == "NULL":
274 labels = pri_labels
275 else:
276 for i in pri_labels:
277 for j in sec_labels:
278 if (i==j):
279 labels.append(i)
280 if len(labels) != 1:
281 err("Label for ssidref \'" + str(ssidref) +
282 "\' unknown or not unique in policy \'" + active_policy + "\'")
284 return labels[0]
288 def label2ssidref(labelname, policyname):
289 """
290 returns ssidref corresponding to labelname;
291 maps current policy to default directory
292 to find mapping file """
294 if policyname in ['NULL', 'INACTIVE', 'DEFAULT']:
295 err("Cannot translate labels for \'" + policyname + "\' policy.")
297 (primary, secondary, f, pol_exists) = getmapfile(policyname)
299 #2. get labelnames for ssidref parts and find a common label
300 pri_ssid = []
301 sec_ssid = []
302 for line in f:
303 l = line.split()
304 if (len(l) < 5) or (l[0] != "LABEL->SSID"):
305 continue
306 if primary and (l[2] == primary) and (l[3] == labelname):
307 pri_ssid.append(int(l[4], 16))
308 if secondary and (l[2] == secondary) and (l[3] == labelname):
309 sec_ssid.append(int(l[4], 16))
310 f.close()
312 #3. sanity check and composition of ssidref
313 if (len(pri_ssid) == 0) or ((len(sec_ssid) == 0) and (secondary != "NULL")):
314 err("Label \'" + labelname + "\' not found.")
315 elif (len(pri_ssid) > 1) or (len(sec_ssid) > 1):
316 err("Label \'" + labelname + "\' not unique in policy (policy error)")
317 if secondary == "NULL":
318 return pri_ssid[0]
319 else:
320 return (sec_ssid[0] << 16) | pri_ssid[0]
324 def refresh_ssidref(config):
325 """
326 looks up ssidref from security field
327 and refreshes the value if label exists
328 """
329 #called by dom0, policy could have changed after xen.utils.security was initialized
330 refresh_security_policy()
332 security = None
333 if isinstance(config, dict):
334 security = config['security']
335 elif isinstance(config, list):
336 security = sxp.child_value(config, 'security',)
337 else:
338 err("Instance type of config parameter not supported.")
339 if not security:
340 #nothing to do (no security label attached)
341 return config
343 policyname = None
344 labelname = None
345 # compose new security field
346 for idx in range(0, len(security)):
347 if security[idx][0] == 'ssidref':
348 security.pop(idx)
349 break
350 elif security[idx][0] == 'access_control':
351 for jdx in [1, 2]:
352 if security[idx][jdx][0] == 'label':
353 labelname = security[idx][jdx][1]
354 elif security[idx][jdx][0] == 'policy':
355 policyname = security[idx][jdx][1]
356 else:
357 err("Illegal field in access_control")
358 #verify policy is correct
359 if active_policy != policyname:
360 err("Policy \'" + policyname + "\' in label does not match active policy \'"
361 + active_policy +"\'!")
363 new_ssidref = label2ssidref(labelname, policyname)
364 if not new_ssidref:
365 err("SSIDREF refresh failed!")
367 security.append([ 'ssidref',str(new_ssidref)])
368 security = ['security', security ]
370 for idx in range(0,len(config)):
371 if config[idx][0] == 'security':
372 config.pop(idx)
373 break
374 config.append(security)
378 def get_ssid(domain):
379 """
380 enables domains to retrieve the label / ssidref of a running domain
381 """
382 if not on():
383 err("No policy active.")
385 if isinstance(domain, str):
386 domain_int = int(domain)
387 elif isinstance(domain, int):
388 domain_int = domain
389 else:
390 err("Illegal parameter type.")
391 try:
392 ssid_info = acm.getssid(int(domain_int))
393 except:
394 err("Cannot determine security information.")
396 if active_policy in ["DEFAULT"]:
397 label = "DEFAULT"
398 else:
399 label = ssidref2label(ssid_info["ssidref"])
400 return(ssid_info["policyreference"],
401 label,
402 ssid_info["policytype"],
403 ssid_info["ssidref"])
407 def get_decision(arg1, arg2):
408 """
409 enables domains to retrieve access control decisions from
410 the hypervisor Access Control Module.
411 IN: args format = ['domid', id] or ['ssidref', ssidref]
412 or ['access_control', ['policy', policy], ['label', label]]
413 """
415 if not on():
416 err("No policy active.")
418 #translate labels before calling low-level function
419 if arg1[0] == 'access_control':
420 if (arg1[1][0] != 'policy') or (arg1[2][0] != 'label') :
421 err("Argument type not supported.")
422 ssidref = label2ssidref(arg1[2][1], arg1[1][1])
423 arg1 = ['ssidref', str(ssidref)]
424 if arg2[0] == 'access_control':
425 if (arg2[1][0] != 'policy') or (arg2[2][0] != 'label') :
426 err("Argument type not supported.")
427 ssidref = label2ssidref(arg2[2][1], arg2[1][1])
428 arg2 = ['ssidref', str(ssidref)]
429 try:
430 decision = acm.getdecision(arg1[0], arg1[1], arg2[0], arg2[1])
431 except:
432 err("Cannot determine decision.")
434 if decision:
435 return decision
436 else:
437 err("Cannot determine decision (Invalid parameter).")
441 def make_policy(policy_name):
442 policy_file = string.join(string.split(policy_name, "."), "/")
443 if not os.path.isfile(policy_dir_prefix + "/" + policy_file + "-security_policy.xml"):
444 err("Unknown policy \'" + policy_name + "\'")
446 (ret, output) = commands.getstatusoutput(xensec_xml2bin + " -d " + policy_dir_prefix + " " + policy_file)
447 if ret:
448 err("Creating policy failed:\n" + output)
452 def load_policy(policy_name):
453 global active_policy
454 policy_file = policy_dir_prefix + "/" + string.join(string.split(policy_name, "."), "/")
455 if not os.path.isfile(policy_file + ".bin"):
456 if os.path.isfile(policy_file + "-security_policy.xml"):
457 err("Binary file does not exist." +
458 "Please use makepolicy to build the policy binary.")
459 else:
460 err("Unknown Policy " + policy_name)
462 #require this policy to be the first or the same as installed
463 if active_policy not in ['DEFAULT', policy_name]:
464 err("Active policy \'" + active_policy +
465 "\' incompatible with new policy \'" + policy_name + "\'")
466 (ret, output) = commands.getstatusoutput(xensec_tool + " loadpolicy " + policy_file + ".bin")
467 if ret:
468 err("Loading policy failed:\n" + output)
469 else:
470 # refresh active policy
471 refresh_security_policy()
475 def dump_policy():
476 if active_policy in ['NULL', 'INACTIVE']:
477 err("\'" + active_policy + "\' policy. Nothing to dump.")
479 (ret, output) = commands.getstatusoutput(xensec_tool + " getpolicy")
480 if ret:
481 err("Dumping hypervisor policy failed:\n" + output)
482 print output
486 def list_labels(policy_name, condition):
487 if (not policy_name) and (active_policy) in ["NULL", "INACTIVE", "DEFAULT"]:
488 err("Current policy \'" + active_policy + "\' has no labels defined.\n")
490 (primary, secondary, f, pol_exists) = getmapfile(policy_name)
491 if not f:
492 if pol_exists:
493 err("Cannot find mapfile for policy \'" + policy_name +
494 "\'.\nPlease use makepolicy to create mapping file.")
495 else:
496 err("Unknown policy \'" + policy_name + "\'")
498 labels = []
499 for line in f:
500 if condition.match(line):
501 label = line.split()[3]
502 if label not in labels:
503 labels.append(label)
504 return labels