| 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 <sailer@us.ibm.com> |
|---|
| 17 | # Contributions: Stefan Berger <stefanb@us.ibm.com> |
|---|
| 18 | #============================================================================ |
|---|
| 19 | """Configuring a security policy into the boot configuration |
|---|
| 20 | """ |
|---|
| 21 | |
|---|
| 22 | import sys |
|---|
| 23 | import traceback |
|---|
| 24 | import tempfile |
|---|
| 25 | import os, stat |
|---|
| 26 | import shutil |
|---|
| 27 | import string |
|---|
| 28 | import re |
|---|
| 29 | from xen.util.security import err |
|---|
| 30 | from xen.util.security import policy_dir_prefix, xen_title_re |
|---|
| 31 | from xen.util.security import boot_filename, altboot_filename |
|---|
| 32 | from xen.util.security import any_title_re, xen_kernel_re, any_module_re |
|---|
| 33 | from xen.util.security import empty_line_re, binary_name_re, policy_name_re |
|---|
| 34 | from xen.xm.opts import OptionError |
|---|
| 35 | |
|---|
| 36 | def help(): |
|---|
| 37 | return """ |
|---|
| 38 | Adds a 'module' line to the Xen grub configuration file entry |
|---|
| 39 | so that Xen boots with a specific access control policy. If |
|---|
| 40 | boot-title is not given, then this script tries to determine |
|---|
| 41 | it by looking for a title starting with \"XEN\". If there are |
|---|
| 42 | multiple entries matching, then it must be called with the unique |
|---|
| 43 | beginning of the title's name.\n""" |
|---|
| 44 | |
|---|
| 45 | def strip_title(line): |
|---|
| 46 | """ |
|---|
| 47 | strips whitespace left and right and cuts 'title' |
|---|
| 48 | """ |
|---|
| 49 | s_title = string.strip(line) |
|---|
| 50 | pos = string.index(s_title, "title") |
|---|
| 51 | if pos >= 0: |
|---|
| 52 | return s_title[pos+6:] |
|---|
| 53 | else: |
|---|
| 54 | return s_title |
|---|
| 55 | |
|---|
| 56 | |
|---|
| 57 | def insert_policy(boot_file, alt_boot_file, user_title, policy_name): |
|---|
| 58 | """ |
|---|
| 59 | inserts policy binary file as last line of the grub entry |
|---|
| 60 | matching the user_title or default title |
|---|
| 61 | """ |
|---|
| 62 | if user_title: |
|---|
| 63 | #replace "(" by "\(" and ")" by "\)" for matching |
|---|
| 64 | user_title = string.replace(user_title, "(", "\(") |
|---|
| 65 | user_title = string.replace(user_title, ")", "\)") |
|---|
| 66 | user_title_re = re.compile("\s*title\s+.*%s" \ |
|---|
| 67 | % user_title, re.IGNORECASE) |
|---|
| 68 | else: |
|---|
| 69 | user_title_re = xen_title_re |
|---|
| 70 | |
|---|
| 71 | within_xen_title = 0 |
|---|
| 72 | within_xen_entry = 0 |
|---|
| 73 | insert_at_end_of_entry = 0 |
|---|
| 74 | path_prefix = '' |
|---|
| 75 | this_title = '' |
|---|
| 76 | extended_titles = [] |
|---|
| 77 | (tmp_fd, tmp_grub) = tempfile.mkstemp() |
|---|
| 78 | #First check whether menu.lst exists |
|---|
| 79 | if not os.path.isfile(boot_file): |
|---|
| 80 | #take alternate boot file (grub.conf) instead |
|---|
| 81 | boot_file = alt_boot_file |
|---|
| 82 | #follow symlink since menue.lst might be linked to grub.conf |
|---|
| 83 | if stat.S_ISLNK(os.lstat(boot_file)[stat.ST_MODE]): |
|---|
| 84 | new_name = os.readlink(boot_file) |
|---|
| 85 | if new_name[0] == "/": |
|---|
| 86 | boot_file = new_name |
|---|
| 87 | else: |
|---|
| 88 | path = boot_file.split('/') |
|---|
| 89 | path[len(path)-1] = new_name |
|---|
| 90 | boot_file = '/'.join(path) |
|---|
| 91 | if not os.path.exists(boot_file): |
|---|
| 92 | err("Boot file \'%s\' not found." % boot_file) |
|---|
| 93 | grub_fd = open(boot_file) |
|---|
| 94 | for line in grub_fd: |
|---|
| 95 | if user_title_re.match(line): |
|---|
| 96 | this_title = strip_title(line) |
|---|
| 97 | within_xen_title = 1 |
|---|
| 98 | elif within_xen_title and xen_kernel_re.match(line): |
|---|
| 99 | insert_at_end_of_entry = 1 |
|---|
| 100 | #use prefix from xen.gz path for policy |
|---|
| 101 | path_prefix = line.split()[1] |
|---|
| 102 | idx = path_prefix.rfind('/') |
|---|
| 103 | if idx >= 0: |
|---|
| 104 | path_prefix = path_prefix[0:idx+1] |
|---|
| 105 | else: |
|---|
| 106 | path_prefix = '' |
|---|
| 107 | elif any_module_re.match(line) and insert_at_end_of_entry: |
|---|
| 108 | if binary_name_re.match(line): |
|---|
| 109 | #delete existing policy module line |
|---|
| 110 | line='' |
|---|
| 111 | elif any_title_re.match(line): |
|---|
| 112 | within_xen_title = 0 |
|---|
| 113 | |
|---|
| 114 | if (empty_line_re.match(line) or any_title_re.match(line)) and \ |
|---|
| 115 | insert_at_end_of_entry: |
|---|
| 116 | #newline or new title: we insert the policy module line here |
|---|
| 117 | os.write(tmp_fd, "\tmodule " + path_prefix + policy_name + ".bin\n") |
|---|
| 118 | extended_titles.append(this_title) |
|---|
| 119 | insert_at_end_of_entry = 0 |
|---|
| 120 | #write the line that was read (except potential existing policy entry) |
|---|
| 121 | os.write(tmp_fd, line) |
|---|
| 122 | |
|---|
| 123 | if insert_at_end_of_entry: |
|---|
| 124 | #last entry, no empty line at end of file |
|---|
| 125 | os.write(tmp_fd, "\tmodule " + path_prefix + policy_name + ".bin\n") |
|---|
| 126 | extended_titles.append(this_title) |
|---|
| 127 | |
|---|
| 128 | #if more than one entry was changed, abort |
|---|
| 129 | if len(extended_titles) > 1: |
|---|
| 130 | err("Following boot entries matched: %s. \nPlease specify " |
|---|
| 131 | "unique part of the boot title." % extended_titles) |
|---|
| 132 | if len(extended_titles) == 0: |
|---|
| 133 | err("Boot entry not found. Please specify unique part " |
|---|
| 134 | "of the boot title.") |
|---|
| 135 | |
|---|
| 136 | #temp file might be destroyed when closing it, first copy it |
|---|
| 137 | shutil.move(boot_file, boot_file+"_save") |
|---|
| 138 | shutil.copyfile(tmp_grub, boot_file) |
|---|
| 139 | os.close(tmp_fd) |
|---|
| 140 | #sometimes the temp file does not disappear |
|---|
| 141 | try: |
|---|
| 142 | os.remove(tmp_grub) |
|---|
| 143 | except: |
|---|
| 144 | pass |
|---|
| 145 | return extended_titles[0] |
|---|
| 146 | |
|---|
| 147 | |
|---|
| 148 | def main(argv): |
|---|
| 149 | user_kver = None |
|---|
| 150 | user_title = None |
|---|
| 151 | if len(argv) == 2: |
|---|
| 152 | policy = argv[1] |
|---|
| 153 | elif len(argv) == 3: |
|---|
| 154 | policy = argv[1] |
|---|
| 155 | user_title = argv[2] |
|---|
| 156 | else: |
|---|
| 157 | raise OptionError('Invalid number of arguments') |
|---|
| 158 | |
|---|
| 159 | if not policy_name_re.match(policy): |
|---|
| 160 | raise OptionError("Illegal policy name: '%s'" % policy) |
|---|
| 161 | |
|---|
| 162 | policy_file = '/'.join([policy_dir_prefix] + policy.split('.')) |
|---|
| 163 | src_binary_policy_file = policy_file + ".bin" |
|---|
| 164 | #check if .bin exists or if policy file exists |
|---|
| 165 | if not os.path.isfile(src_binary_policy_file): |
|---|
| 166 | if not os.path.isfile(policy_file + "-security_policy.xml"): |
|---|
| 167 | raise OptionError("Unknown policy '%s'" % policy) |
|---|
| 168 | else: |
|---|
| 169 | err_msg = "Cannot find binary file for policy '%s'." % policy |
|---|
| 170 | err_msg += " Please use makepolicy to create binary file." |
|---|
| 171 | raise OptionError(err_msg) |
|---|
| 172 | |
|---|
| 173 | dst_binary_policy_file = "/boot/" + policy + ".bin" |
|---|
| 174 | shutil.copyfile(src_binary_policy_file, dst_binary_policy_file) |
|---|
| 175 | |
|---|
| 176 | entryname = insert_policy(boot_filename, altboot_filename, |
|---|
| 177 | user_title, policy) |
|---|
| 178 | print "Boot entry '%s' extended and \'%s\' copied to /boot" \ |
|---|
| 179 | % (entryname, policy + ".bin") |
|---|
| 180 | |
|---|
| 181 | if __name__ == '__main__': |
|---|
| 182 | try: |
|---|
| 183 | main(sys.argv) |
|---|
| 184 | except Exception, e: |
|---|
| 185 | sys.stderr.write('Error: ' + str(e) + '\n') |
|---|
| 186 | sys.exit(-1) |
|---|