| 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 | #============================================================================ |
|---|
| 19 | |
|---|
| 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 |
|---|
| 29 | |
|---|
| 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" |
|---|
| 37 | |
|---|
| 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) |
|---|
| 49 | |
|---|
| 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) |
|---|
| 59 | |
|---|
| 60 | #other global variables |
|---|
| 61 | NULL_SSIDREF = 0 |
|---|
| 62 | |
|---|
| 63 | log = logging.getLogger("xend.util.security") |
|---|
| 64 | |
|---|
| 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) |
|---|
| 72 | |
|---|
| 73 | |
|---|
| 74 | |
|---|
| 75 | def err(msg): |
|---|
| 76 | """Raise ACM exception. |
|---|
| 77 | """ |
|---|
| 78 | sys.stderr.write("ACMError: " + msg + "\n") |
|---|
| 79 | raise ACMError(msg) |
|---|
| 80 | |
|---|
| 81 | |
|---|
| 82 | |
|---|
| 83 | active_policy = None |
|---|
| 84 | |
|---|
| 85 | |
|---|
| 86 | def refresh_security_policy(): |
|---|
| 87 | """ |
|---|
| 88 | retrieves security policy |
|---|
| 89 | """ |
|---|
| 90 | global active_policy |
|---|
| 91 | |
|---|
| 92 | try: |
|---|
| 93 | active_policy = acm.policy() |
|---|
| 94 | except: |
|---|
| 95 | active_policy = "INACTIVE" |
|---|
| 96 | |
|---|
| 97 | # now set active_policy |
|---|
| 98 | refresh_security_policy() |
|---|
| 99 | |
|---|
| 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']) |
|---|
| 107 | |
|---|
| 108 | |
|---|
| 109 | |
|---|
| 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.") |
|---|
| 125 | |
|---|
| 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.") |
|---|
| 132 | |
|---|
| 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]) |
|---|
| 142 | |
|---|
| 143 | if search == 'ssidref': |
|---|
| 144 | return 0 |
|---|
| 145 | else: |
|---|
| 146 | return None |
|---|
| 147 | |
|---|
| 148 | |
|---|
| 149 | |
|---|
| 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" |
|---|
| 161 | |
|---|
| 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" |
|---|
| 178 | |
|---|
| 179 | |
|---|
| 180 | |
|---|
| 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 = "" |
|---|
| 201 | |
|---|
| 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.") |
|---|
| 211 | |
|---|
| 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.") |
|---|
| 232 | |
|---|
| 233 | |
|---|
| 234 | |
|---|
| 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')") |
|---|
| 252 | |
|---|
| 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.") |
|---|
| 260 | |
|---|
| 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 = [] |
|---|
| 269 | |
|---|
| 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() |
|---|
| 279 | |
|---|
| 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 + "\'") |
|---|
| 295 | |
|---|
| 296 | return labels[0] |
|---|
| 297 | |
|---|
| 298 | |
|---|
| 299 | |
|---|
| 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 """ |
|---|
| 305 | |
|---|
| 306 | if policyname in ['NULL', 'INACTIVE', 'DEFAULT']: |
|---|
| 307 | err("Cannot translate labels for \'" + policyname + "\' policy.") |
|---|
| 308 | |
|---|
| 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'.") |
|---|
| 316 | |
|---|
| 317 | (primary, secondary, f, pol_exists) = getmapfile(policyname) |
|---|
| 318 | |
|---|
| 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) |
|---|
| 335 | |
|---|
| 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] |
|---|
| 345 | |
|---|
| 346 | |
|---|
| 347 | |
|---|
| 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() |
|---|
| 355 | |
|---|
| 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 |
|---|
| 366 | |
|---|
| 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 +"\'!") |
|---|
| 386 | |
|---|
| 387 | new_ssidref = label2ssidref(labelname, policyname, 'dom') |
|---|
| 388 | if not new_ssidref: |
|---|
| 389 | err("SSIDREF refresh failed!") |
|---|
| 390 | |
|---|
| 391 | security.append([ 'ssidref',str(new_ssidref)]) |
|---|
| 392 | security = ['security', security ] |
|---|
| 393 | |
|---|
| 394 | for idx in range(0,len(config)): |
|---|
| 395 | if config[idx][0] == 'security': |
|---|
| 396 | config.pop(idx) |
|---|
| 397 | break |
|---|
| 398 | config.append(security) |
|---|
| 399 | |
|---|
| 400 | |
|---|
| 401 | |
|---|
| 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.") |
|---|
| 408 | |
|---|
| 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.") |
|---|
| 419 | |
|---|
| 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"]) |
|---|
| 428 | |
|---|
| 429 | |
|---|
| 430 | |
|---|
| 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 | """ |
|---|
| 438 | |
|---|
| 439 | if not on(): |
|---|
| 440 | err("No policy active.") |
|---|
| 441 | |
|---|
| 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)] |
|---|
| 453 | |
|---|
| 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") |
|---|
| 461 | |
|---|
| 462 | try: |
|---|
| 463 | decision = acm.getdecision(arg1[0], arg1[1], arg2[0], arg2[1]) |
|---|
| 464 | except: |
|---|
| 465 | err("Cannot determine decision.") |
|---|
| 466 | |
|---|
| 467 | if decision: |
|---|
| 468 | return decision |
|---|
| 469 | else: |
|---|
| 470 | err("Cannot determine decision (Invalid parameter).") |
|---|
| 471 | |
|---|
| 472 | |
|---|
| 473 | |
|---|
| 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 + "\'") |
|---|
| 478 | |
|---|
| 479 | (ret, output) = commands.getstatusoutput(xensec_xml2bin + " -d " + policy_dir_prefix + " " + policy_file) |
|---|
| 480 | if ret: |
|---|
| 481 | err("Creating policy failed:\n" + output) |
|---|
| 482 | |
|---|
| 483 | |
|---|
| 484 | |
|---|
| 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) |
|---|
| 494 | |
|---|
| 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() |
|---|
| 505 | |
|---|
| 506 | |
|---|
| 507 | |
|---|
| 508 | def dump_policy(): |
|---|
| 509 | if active_policy in ['NULL', 'INACTIVE']: |
|---|
| 510 | err("\'" + active_policy + "\' policy. Nothing to dump.") |
|---|
| 511 | |
|---|
| 512 | (ret, output) = commands.getstatusoutput(xensec_tool + " getpolicy") |
|---|
| 513 | if ret: |
|---|
| 514 | err("Dumping hypervisor policy failed:\n" + output) |
|---|
| 515 | print output |
|---|
| 516 | |
|---|
| 517 | |
|---|
| 518 | |
|---|
| 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") |
|---|
| 522 | |
|---|
| 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 + "\'") |
|---|
| 530 | |
|---|
| 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 |
|---|
| 538 | |
|---|
| 539 | |
|---|
| 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') |
|---|
| 551 | |
|---|
| 552 | (label, policy) = default_res_label() |
|---|
| 553 | |
|---|
| 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() |
|---|
| 561 | |
|---|
| 562 | # find the resource information |
|---|
| 563 | if res_label_cache.has_key(resource): |
|---|
| 564 | (policy, label) = res_label_cache[resource] |
|---|
| 565 | |
|---|
| 566 | return (label, policy) |
|---|
| 567 | |
|---|
| 568 | |
|---|
| 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) |
|---|
| 581 | |
|---|
| 582 | (label, ssidref, policy) = default_security_details() |
|---|
| 583 | |
|---|
| 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() |
|---|
| 589 | |
|---|
| 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() |
|---|
| 596 | |
|---|
| 597 | return (label, ssidref, policy) |
|---|
| 598 | |
|---|
| 599 | |
|---|
| 600 | def unify_resname(resource): |
|---|
| 601 | """Makes all resource locations absolute. In case of physical |
|---|
| 602 | resources, '/dev/' is added to local file names""" |
|---|
| 603 | |
|---|
| 604 | if not resource: |
|---|
| 605 | return resource |
|---|
| 606 | |
|---|
| 607 | # sanity check on resource name |
|---|
| 608 | try: |
|---|
| 609 | (type, resfile) = resource.split(":", 1) |
|---|
| 610 | except: |
|---|
| 611 | err("Resource spec '%s' contains no ':' delimiter" % resource) |
|---|
| 612 | |
|---|
| 613 | if type == "tap": |
|---|
| 614 | try: |
|---|
| 615 | (subtype, resfile) = resfile.split(":") |
|---|
| 616 | except: |
|---|
| 617 | err("Resource spec '%s' contains no tap subtype" % resource) |
|---|
| 618 | |
|---|
| 619 | if type in ["phy", "tap"]: |
|---|
| 620 | if not resfile.startswith("/"): |
|---|
| 621 | resfile = "/dev/" + resfile |
|---|
| 622 | |
|---|
| 623 | #file: resources must specified with absolute path |
|---|
| 624 | if (not resfile.startswith("/")) or (not os.path.exists(resfile)): |
|---|
| 625 | err("Invalid resource.") |
|---|
| 626 | |
|---|
| 627 | # from here on absolute file names with resources |
|---|
| 628 | if type == "tap": |
|---|
| 629 | type = type + ":" + subtype |
|---|
| 630 | resource = type + ":" + resfile |
|---|
| 631 | return resource |
|---|
| 632 | |
|---|
| 633 | |
|---|
| 634 | def res_security_check(resource, domain_label): |
|---|
| 635 | """Checks if the given resource can be used by the given domain |
|---|
| 636 | label. Returns 1 if the resource can be used, otherwise 0. |
|---|
| 637 | """ |
|---|
| 638 | rtnval = 1 |
|---|
| 639 | |
|---|
| 640 | # if security is on, ask the hypervisor for a decision |
|---|
| 641 | if on(): |
|---|
| 642 | #build canonical resource name |
|---|
| 643 | resource = unify_resname(resource) |
|---|
| 644 | |
|---|
| 645 | (label, ssidref, policy) = get_res_security_details(resource) |
|---|
| 646 | domac = ['access_control'] |
|---|
| 647 | domac.append(['policy', active_policy]) |
|---|
| 648 | domac.append(['label', domain_label]) |
|---|
| 649 | domac.append(['type', 'dom']) |
|---|
| 650 | decision = get_decision(domac, ['ssidref', str(ssidref)]) |
|---|
| 651 | |
|---|
| 652 | # provide descriptive error messages |
|---|
| 653 | if decision == 'DENIED': |
|---|
| 654 | if label == ssidref2label(NULL_SSIDREF): |
|---|
| 655 | raise ACMError("Resource '"+resource+"' is not labeled") |
|---|
| 656 | rtnval = 0 |
|---|
| 657 | else: |
|---|
| 658 | raise ACMError("Permission denied for resource '"+resource+"' because label '"+label+"' is not allowed") |
|---|
| 659 | rtnval = 0 |
|---|
| 660 | |
|---|
| 661 | # security is off, make sure resource isn't labeled |
|---|
| 662 | else: |
|---|
| 663 | # Note, we can't canonicalise the resource here, because people using |
|---|
| 664 | # xm without ACM are free to use relative paths. |
|---|
| 665 | (label, policy) = get_res_label(resource) |
|---|
| 666 | if policy != 'NULL': |
|---|
| 667 | raise ACMError("Security is off, but '"+resource+"' is labeled") |
|---|
| 668 | rtnval = 0 |
|---|
| 669 | |
|---|
| 670 | return rtnval |
|---|