source: trunk/packages/xen-3.1/xen-3.1/tools/python/xen/util/security.py @ 34

Last change on this file since 34 was 34, checked in by hartmans, 18 years ago

Add xen and xen-common

File size: 22.0 KB
Line 
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
20import commands
21import logging
22import sys, os, string, re
23import traceback
24import shutil
25from xen.lowlevel import acm
26from xen.xend import sxp
27from xen.xend.XendLogging import log
28from xen.util import dictio
29
30#global directories and tools for security management
31policy_dir_prefix = "/etc/xen/acm-security/policies"
32res_label_filename = policy_dir_prefix + "/resource_labels"
33boot_filename = "/boot/grub/menu.lst"
34altboot_filename = "/boot/grub/grub.conf"
35xensec_xml2bin = "/usr/sbin/xensec_xml2bin"
36xensec_tool = "/usr/sbin/xensec_tool"
37
38#global patterns for map file
39#police_reference_tagname = "POLICYREFERENCENAME"
40primary_entry_re = re.compile("\s*PRIMARY\s+.*", re.IGNORECASE)
41secondary_entry_re = re.compile("\s*SECONDARY\s+.*", re.IGNORECASE)
42label_template_re =  re.compile(".*security_label_template.xml", re.IGNORECASE)
43mapping_filename_re = re.compile(".*\.map", re.IGNORECASE)
44policy_reference_entry_re = re.compile("\s*POLICYREFERENCENAME\s+.*", re.IGNORECASE)
45vm_label_re = re.compile("\s*LABEL->SSID\s+VM\s+.*", re.IGNORECASE)
46res_label_re = re.compile("\s*LABEL->SSID\s+RES\s+.*", re.IGNORECASE)
47all_label_re = re.compile("\s*LABEL->SSID\s+.*", re.IGNORECASE)
48access_control_re = re.compile("\s*access_control\s*=", re.IGNORECASE)
49
50#global patterns for boot configuration file
51xen_title_re = re.compile("\s*title\s+XEN", re.IGNORECASE)
52any_title_re = re.compile("\s*title\s", re.IGNORECASE)
53xen_kernel_re = re.compile("\s*kernel.*xen.*\.gz", re.IGNORECASE)
54kernel_ver_re = re.compile("\s*module.*vmlinuz", re.IGNORECASE)
55any_module_re = re.compile("\s*module\s", re.IGNORECASE)
56empty_line_re = re.compile("^\s*$")
57binary_name_re = re.compile(".*[chwall|ste|chwall_ste].*\.bin", re.IGNORECASE)
58policy_name_re = re.compile(".*[chwall|ste|chwall_ste].*", re.IGNORECASE)
59
60#other global variables
61NULL_SSIDREF = 0
62
63log = 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.
67class ACMError(Exception):
68    def __init__(self,value):
69        self.value = value
70    def __str__(self):
71        return repr(self.value)
72
73
74
75def err(msg):
76    """Raise ACM exception.
77    """
78    sys.stderr.write("ACMError: " + msg + "\n")
79    raise ACMError(msg)
80
81
82
83active_policy = None
84
85
86def 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
98refresh_security_policy()
99
100def 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 ...]
111def 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
150def 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
181def 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
235def 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
300def 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
348def 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
402def 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
431def 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
474def 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
485def 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
508def 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
519def 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
540def 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
569def 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
600def 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
634def 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
Note: See TracBrowser for help on using the repository browser.