#!/usr/bin/env python # Copyright (c) 2013 OpenStack Foundation # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # NOTE: XenServer still only supports Python 2.4 in it's dom0 userspace # which means the Nova xenapi plugins must use only Python 2.4 features # TODO(sfinucan): Resolve all 'noqa' items once the above is no longer true """Inject network configuration into iPXE ISO for boot.""" import logging import os import shutil import utils # FIXME(sirp): should this use pluginlib from 5.6? import dom0_pluginlib dom0_pluginlib.configure_logging('ipxe') ISOLINUX_CFG = """SAY iPXE ISO boot image TIMEOUT 30 DEFAULT ipxe.krn LABEL ipxe.krn KERNEL ipxe.krn INITRD netcfg.ipxe """ NETCFG_IPXE = """#!ipxe :start imgfree ifclose net0 set net0/ip %(ip_address)s set net0/netmask %(netmask)s set net0/gateway %(gateway)s set dns %(dns)s ifopen net0 goto menu :menu chain %(boot_menu_url)s goto boot :boot sanboot --no-describe --drive 0x80 """ def _write_file(filename, data): # If the ISO was tampered with such that the destination is a symlink, # that could allow a malicious user to write to protected areas of the # dom0 filesystem. /HT to comstud for pointing this out. # # Short-term, checking that the destination is not a symlink should be # sufficient. # # Long-term, we probably want to perform all file manipulations within a # chroot jail to be extra safe. if os.path.islink(filename): raise RuntimeError('SECURITY: Cannot write to symlinked destination') logging.debug("Writing to file '%s'" % filename) f = open(filename, 'w') try: f.write(data) finally: f.close() def _unbundle_iso(sr_path, filename, path): logging.debug("Unbundling ISO '%s'" % filename) read_only_path = utils.make_staging_area(sr_path) try: utils.run_command(['mount', '-o', 'loop', filename, read_only_path]) try: shutil.copytree(read_only_path, path) finally: utils.run_command(['umount', read_only_path]) finally: utils.cleanup_staging_area(read_only_path) def _create_iso(mkisofs_cmd, filename, path): logging.debug("Creating ISO '%s'..." % filename) orig_dir = os.getcwd() os.chdir(path) try: utils.run_command([mkisofs_cmd, '-quiet', '-l', '-o', filename, '-c', 'boot.cat', '-b', 'isolinux.bin', '-no-emul-boot', '-boot-load-size', '4', '-boot-info-table', '.']) finally: os.chdir(orig_dir) def inject(session, sr_path, vdi_uuid, boot_menu_url, ip_address, netmask, gateway, dns, mkisofs_cmd): iso_filename = '%s.img' % os.path.join(sr_path, 'iso', vdi_uuid) # Create staging area so we have a unique path but remove it since # shutil.copytree will recreate it staging_path = utils.make_staging_area(sr_path) utils.cleanup_staging_area(staging_path) try: _unbundle_iso(sr_path, iso_filename, staging_path) # Write Configs _write_file(os.path.join(staging_path, 'netcfg.ipxe'), NETCFG_IPXE % {"ip_address": ip_address, "netmask": netmask, "gateway": gateway, "dns": dns, "boot_menu_url": boot_menu_url}) _write_file(os.path.join(staging_path, 'isolinux.cfg'), ISOLINUX_CFG) _create_iso(mkisofs_cmd, iso_filename, staging_path) finally: utils.cleanup_staging_area(staging_path) if __name__ == "__main__": utils.register_plugin_calls(inject)