# Software License Agreement (BSD License) # # Copyright (c) 2009, Eucalyptus Systems, Inc. # All rights reserved. # # Redistribution and use of this software in source and binary forms, with or # without modification, are permitted provided that the following conditions # are met: # # Redistributions of source code must retain the above # copyright notice, this list of conditions and the # following disclaimer. # # Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the # following disclaimer in the documentation and/or other # materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # # Author: Neil Soman neil@eucalyptus.com import boto import getopt, sys, os, stat import tarfile import gzip from xml.dom.minidom import Document from xml.dom import minidom from hashlib import sha1 as sha from M2Crypto import BN, EVP, RSA, util, Rand, m2, X509 from binascii import hexlify, unhexlify from subprocess import * import platform import urllib import re import shutil from boto.ec2.regioninfo import RegionInfo import logging BUNDLER_NAME = "euca-tools" BUNDLER_VERSION = "1.0" VERSION = "2007-10-10" RELEASE = "31337" AES = 'AES-128-CBC' IP_PROTOCOLS = ['tcp', 'udp', 'icmp'] IMAGE_IO_CHUNK = 10 * 1024 IMAGE_SPLIT_CHUNK = IMAGE_IO_CHUNK * 1024; MAX_LOOP_DEVS = 256; METADATA_URL = "http://169.254.169.254/latest/meta-data/" class LinuxImage: ALLOWED_FS_TYPES = ['ext2', 'ext3', 'xfs', 'jfs', 'reiserfs'] BANNED_MOUNTS = ['/dev', '/media', '/mnt', '/proc', '/sys', '/cdrom', '/tmp'] ESSENTIAL_DIRS = ['proc', 'tmp', 'dev', 'mnt', 'sys'] ESSENTIAL_DEVS = [[os.path.join('dev', 'console'), 'c', '5', '1'], [os.path.join('dev', 'full'), 'c', '1', '7'], [os.path.join('dev', 'null'), 'c', '1', '3'], [os.path.join('dev', 'zero'), 'c', '1', '5'], [os.path.join('dev', 'tty'), 'c', '5', '0'], [os.path.join('dev', 'tty0'), 'c', '4', '0'], [os.path.join('dev', 'tty1'), 'c', '4', '1'], [os.path.join('dev', 'tty2'), 'c', '4', '2'], [os.path.join('dev', 'tty3'), 'c', '4', '3'], [os.path.join('dev', 'tty4'), 'c', '4', '4'], [os.path.join('dev', 'tty5'), 'c', '4', '5'], [os.path.join('dev', 'xvc0'), 'c', '204', '191']] MAKEFS_CMD = 'mkfs.ext3' NEW_FSTAB = """ /dev/sda1 / ext3 defaults 1 1 /dev/sdb /mnt ext3 defaults 0 0 none /dev/pts devpts gid=5,mode=620 0 0 none /proc proc defaults 0 0 none /sys sysfs defaults 0 0 """ OLD_FSTAB = """/dev/sda1 / ext3 defaults,errors=remount-ro 0 0 /dev/sda2 /mnt ext3 defaults 0 0 /dev/sda3 swap swap defaults 0 0 proc /proc proc defaults 0 0 devpts /dev/pts devpts gid=5,mode=620 0 0""" def __init__(self, debug=False): self.debug = debug def create_image(self, size_in_MB, image_path): dd_cmd = ["dd"] dd_cmd.append("if=/dev/zero") dd_cmd.append("of=%s" % (image_path)) dd_cmd.append("count=1") dd_cmd.append("bs=1M") dd_cmd.append("seek=%s" % (size_in_MB-1)) if self.debug: print 'Creating disk image...', image_path Popen(dd_cmd, PIPE).communicate()[0] def make_fs(self, image_path): try: Util().check_prerequisite_command(self.MAKEFS_CMD) except NotFoundError: sys.exit(1) if self.debug: print "Creating filesystem..." makefs_cmd = Popen([self.MAKEFS_CMD, "-F", image_path], PIPE).communicate()[0] def add_fstab(self, mount_point, generate_fstab, fstab_path): if not fstab_path: return fstab = None if fstab_path == "old": if not generate_fstab: return fstab = self.OLD_FSTAB elif fstab_path == "new": if not generate_fstab: return fstab = self.NEW_FSTAB etc_file_path = os.path.join(mount_point, "etc") fstab_file_path = os.path.join(etc_file_path, "fstab") if not os.path.exists(etc_file_path): os.mkdir(etc_file_path) else: if os.path.exists(fstab_file_path): fstab_copy_path = fstab_file_path + ".old" shutil.copyfile(fstab_file_path, fstab_copy_path) if self.debug: print "Updating fstab entry" fstab_file = open(fstab_file_path, "w") if fstab: fstab_file.write(fstab) else: orig_fstab_file = open(fstab_path, "r") while 1: data = orig_fstab_file.read(IMAGE_IO_CHUNK) if not data: break fstab_file.write(data) orig_fstab_file.close() fstab_file.close() def make_essential_devs(self, image_path): for entry in self.ESSENTIAL_DEVS: cmd = ['mknod'] entry[0] = os.path.join(image_path, entry[0]) cmd.extend(entry) Popen(cmd, stdout=PIPE, stderr=PIPE).communicate() class SolarisImage: ALLOWED_FS_TYPES = ['ext2', 'ext3', 'xfs', 'jfs', 'reiserfs'] BANNED_MOUNTS = ['/dev', '/media', '/mnt', '/proc', '/sys', '/cdrom', '/tmp'] ESSENTIAL_DIRS = ['proc', 'tmp', 'dev', 'mnt', 'sys'] def __init__(self, debug=False): self.debug = debug def create_image(self, size_in_MB, image_path): print "Sorry. Solaris not supported yet" sys.exit(1) def make_fs(self, image_path): print "Sorry. Solaris not supported yet" sys.exit(1) def make_essential_devs(self, image_path): print "Sorry. Solaris not supported yet" sys.exit(1) class Util: usage_string = """ -a, --access-key User's Access Key ID. -s, --secret-key User's Secret Key. -U, --url URL of the Cloud to connect to. --config Read credentials and cloud settings from the specified config file (defaults to $HOME/.eucarc or /etc/euca2ools/eucarc). -h, --help Display this help message. --version Display the version of this tool. --debug Turn on debugging. Euca2ools will use the environment variables EC2_URL, EC2_ACCESS_KEY, EC2_SECRET_KEY, EC2_CERT, EC2_PRIVATE_KEY, S3_URL, EUCALYPTUS_CERT by default. """ def usage(self, compat=False): if compat: self.usage_string = self.usage_string.replace("-s,", "-S,") self.usage_string = self.usage_string.replace("-a,", "-A,") print self.usage_string sys.exit(1) def check_prerequisite_command(self, command): cmd = [command] try: output = Popen(cmd, stdout=PIPE, stderr=PIPE).communicate() except OSError, e: error_string = "%s" % e if "No such" in error_string: print "Command %s not found. Is it installed?" % command raise NotFoundError else: raise OSError(e) class AddressValidationError: def __init__(self): self.message = 'Invalid address' class InstanceValidationError: def __init__(self): self.message = 'Invalid instance id' class VolumeValidationError: def __init__(self): self.message = 'Invalid volume id' class SizeValidationError: def __init__(self): self.message = 'Invalid size' class SnapshotValidationError: def __init__(self): self.message = 'Invalid snapshot id' class ProtocolValidationError: def __init__(self): self.message = 'Invalid protocol' class FileValidationError: def __init__(self): self.message = 'Invalid file' class DirValidationError: def __init__(self): self.message = 'Invalid directory' class CopyError: def __init__(self): self.message = 'Unable to copy' class MetadataReadError: def __init__(self): self.message = "Unable to read metadata" class NullHandler(logging.Handler): def emit(self, record): pass class NotFoundError: def __init__(self): self.message = "Unable to find" class Euca2ool: def process_args(self): ids = [] for arg in self.args: ids.append(arg) return ids def __init__(self, short_opts=None, long_opts=None, is_s3=False, compat=False): self.ec2_user_access_key = None self.ec2_user_secret_key = None self.ec2_url = None self.s3_url = None self.config_file_path = None self.is_s3 = is_s3 if compat: self.secret_key_opt = 'S' self.access_key_opt = 'A' else: self.secret_key_opt = 's' self.access_key_opt = 'a' if not short_opts: short_opts = '' if not long_opts: long_opts = [''] short_opts += 'hU:' short_opts += '%s:' % self.secret_key_opt short_opts += '%s:' % self.access_key_opt long_opts += ['access-key=', 'secret-key=', 'url=', 'help', 'version', 'debug', 'config='] opts, args = getopt.gnu_getopt(sys.argv[1:], short_opts, long_opts) self.opts = opts self.args = args self.debug = False for name, value in opts: if name in ('-%s' % self.access_key_opt, '--access-key'): self.ec2_user_access_key = value elif name in ('-%s' % self.secret_key_opt, '--secret-key'): try: self.ec2_user_secret_key = int(value) self.ec2_user_secret_key = None except ValueError: self.ec2_user_secret_key = value elif name in ('-U', '--url'): self.ec2_url = value elif name == '--debug': self.debug = True elif name == '--config': self.config_file_path = value system_string = platform.system() if system_string == "Linux": self.img = LinuxImage(self.debug) elif system_string == "SunOS": self.img = SolarisImage(self.debug) else: self.img = "Unsupported" self.setup_environ() h = NullHandler() logging.getLogger("boto").addHandler(h) SYSTEM_EUCARC_PATH = os.path.join("/etc", "euca2ools", "eucarc") def setup_environ(self): self.environ = {} user_eucarc = None if 'HOME' in os.environ: os.path.join(os.getenv('HOME'), ".eucarc") base_path = None read_config = False if self.config_file_path and os.path.exists(self.config_file_path): base_path = os.path.dirname(self.config_file_path) eucarc = open(self.config_file_path, "r") read_config = True elif user_eucarc is not None and os.path.exists(user_eucarc): base_path = os.path.dirname(user_eucarc) eucarc = open(user_eucarc, "r") read_config = True elif os.path.exists(self.SYSTEM_EUCARC_PATH): base_path = os.path.dirname(self.SYSTEM_EUCARC_PATH) eucarc = open(self.SYSTEM_EUCARC_PATH, "r") read_config = True if read_config: lines = eucarc.readlines() comment = re.compile('^#') for line in lines: line = line.strip('export') line = line.replace('\'', '') line = line.strip() line = line.replace('${EUCA_KEY_DIR}', base_path) if not comment.match(line): parts = line.split('=', 1) if len(parts) == 2: self.environ[parts[0]] = parts[1] eucarc.close() else: self.environ['EC2_ACCESS_KEY'] = os.getenv('EC2_ACCESS_KEY') self.environ['EC2_SECRET_KEY'] = os.getenv('EC2_SECRET_KEY') self.environ['S3_URL'] = os.getenv('S3_URL') self.environ['EC2_URL'] = os.getenv('EC2_URL') self.environ['EC2_CERT'] = os.getenv('EC2_CERT') self.environ['EC2_PRIVATE_KEY'] = os.getenv('EC2_PRIVATE_KEY') self.environ['EUCALYPTUS_CERT'] = os.getenv('EUCALYPTUS_CERT') self.environ['EC2_USER_ID'] = os.getenv('EC2_USER_ID') def get_environ(self, name): if self.environ.has_key(name): return self.environ[name] else: print '%s not found' % name sys.exit(1) def make_connection(self): if not self.ec2_user_access_key: self.ec2_user_access_key = self.environ['EC2_ACCESS_KEY'] if not self.ec2_user_access_key: print 'EC2_ACCESS_KEY environment variable must be set.' sys.exit(1) if not self.ec2_user_secret_key: self.ec2_user_secret_key = self.environ['EC2_SECRET_KEY'] if not self.ec2_user_secret_key: print 'EC2_SECRET_KEY environment variable must be set.' sys.exit(1) if not self.is_s3: if not self.ec2_url: self.ec2_url = self.environ['EC2_URL'] if not self.ec2_url: self.ec2_url = 'http://localhost:8773/services/Eucalyptus' print 'EC2_URL not specified. Trying %s' % (self.ec2_url) else: if not self.ec2_url: self.ec2_url = self.environ['S3_URL'] if not self.ec2_url: self.ec2_url = 'http://localhost:8773/services/Walrus' print 'S3_URL not specified. Trying %s' % (self.ec2_url) self.port = None self.service_path = "/" if (self.ec2_url.find('https://') >= 0): self.ec2_url = self.ec2_url.replace('https://', '') self.is_secure = True else: self.ec2_url = self.ec2_url.replace('http://', '') self.is_secure = False self.host = self.ec2_url url_parts = self.ec2_url.split(':') if (len(url_parts) > 1): self.host = url_parts[0] path_parts = url_parts[1].split('/', 1) if (len(path_parts) > 1): self.port = int(path_parts[0]) self.service_path = self.service_path + path_parts[1] else: self.port = int(url_parts[1]) if not self.is_s3: return boto.connect_ec2(aws_access_key_id=self.ec2_user_access_key, aws_secret_access_key=self.ec2_user_secret_key, is_secure=self.is_secure, region=RegionInfo(None, "eucalyptus", self.host), port=self.port, path=self.service_path) else: return boto.s3.Connection(aws_access_key_id=self.ec2_user_access_key, aws_secret_access_key=self.ec2_user_secret_key, is_secure=self.is_secure, host=self.host, port=self.port, calling_format=boto.s3.connection.OrdinaryCallingFormat(), path=self.service_path) def validate_address(self, address): if not re.match("[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(\/[0-9]+)?$", address): raise AddressValidationError def validate_instance_id(self, id): if not re.match("i-", id): raise InstanceValidationError def validate_volume_id(self, id): if not re.match("vol-", id): raise VolumeValidationError def validate_volume_size(self, size): if size < 0 or size > 1024: raise SizeValidationError def validate_snapshot_id(self, id): if not re.match("snap-", id): raise SnapshotValidationError def validate_protocol(self, proto): if not proto in IP_PROTOCOLS: raise ProtocolValidationError def validate_file(self, path): if not os.path.exists(path) or not os.path.isfile(path): raise FileValidationError def validate_dir(self, path): if not os.path.exists(path) or not os.path.isdir(path): raise DirValidationError def get_relative_filename(self, filename): f_parts = filename.split('/') return f_parts[len(f_parts) - 1] def get_file_path(self, filename): relative_filename = self.get_relative_filename(filename) file_path = filename.replace(relative_filename, '') if len(file_path) == 0: file_path = "." return file_path def split_file(self, file, chunk_size): parts = [] parts_digest = [] file_size = os.path.getsize(file) in_file = open(file, "rb") number_parts = int(file_size / chunk_size) number_parts += 1 bytes_read = 0 for i in range(0, number_parts, 1): filename = '%s.%d' % (file, i) part_digest = sha() file_part = open(filename, "wb") print "Part:", self.get_relative_filename(filename) part_bytes_written = 0 while part_bytes_written < IMAGE_SPLIT_CHUNK: data = in_file.read(IMAGE_IO_CHUNK) file_part.write(data) part_digest.update(data) data_len = len(data) part_bytes_written += data_len bytes_read += data_len if bytes_read >= file_size: break file_part.close() parts.append(filename) parts_digest.append(hexlify(part_digest.digest())) in_file.close() return parts, parts_digest def check_image(self, image_file, path): print 'Checking image' if not os.path.exists(path): os.makedirs(path) image_size = os.path.getsize(image_file) if self.debug: print "Image Size:", image_size, "bytes" in_file = open(image_file, "rb") sha_image = sha() while 1: buf=in_file.read(IMAGE_IO_CHUNK) if not buf: break sha_image.update(buf) return image_size, hexlify(sha_image.digest()) # def tarzip_image(self, prefix, file, path): # print 'Tarring image' # tar_file = '%s.tar.gz' % os.path.join(path, prefix) # tar = tarfile.open(tar_file, "w|gz") # tar.add(file, arcname=prefix) # tar.close() # return tar_file def tarzip_image(self, prefix, file, path): try: Util().check_prerequisite_command('tar') except NotFoundError: sys.exit(1) print 'Tarring image' tar_file = '%s.tar.gz' % os.path.join(path, prefix) outfile = open(tar_file, "wb") file_path = self.get_file_path(file) tar_cmd = ["tar", "c", "-S"] if file_path: tar_cmd.append("-C") tar_cmd.append(file_path) tar_cmd.append(self.get_relative_filename(file)) else: tar_cmd.append(file) p1 = Popen(tar_cmd, stdout=PIPE) p2 = Popen(["gzip"], stdin=p1.stdout, stdout=outfile) p2.communicate() outfile.close if os.path.getsize(tar_file) <= 0: print "Could not tar image" sys.exit(1) return tar_file def hexToBytes(self, hexString): bytes = [] hexString = ''.join(hexString.split(" ")) for i in range(0, len(hexString), 2): bytes.append(chr(int (hexString[i:i+2], 16))) return ''.join( bytes ) def crypt_file(self, cipher, in_file, out_file) : while 1: buf=in_file.read(IMAGE_IO_CHUNK) if not buf: break out_file.write(cipher.update(buf)) out_file.write(cipher.final()) def encrypt_image(self, file): print 'Encrypting image' enc_file = '%s.part' % (file.replace('.tar.gz', '')) key = (hex(BN.rand(16 * 8))[2:34]).replace('L', 'c') if self.debug: print 'Key: %s' % (key) iv = (hex(BN.rand(16 * 8))[2:34]).replace('L', 'c') if self.debug: print 'IV: %s' % (iv) k=EVP.Cipher(alg='aes_128_cbc', key=unhexlify(key), iv=unhexlify(iv), op=1) in_file = open(file) out_file = open(enc_file, "wb") self.crypt_file(k, in_file, out_file) in_file.close() out_file.close() bundled_size = os.path.getsize(enc_file) return enc_file, key, iv, bundled_size def split_image(self, file): print 'Splitting image...' return self.split_file(file, IMAGE_SPLIT_CHUNK) def get_verification_string(self, manifest_string): start_mc = manifest_string.find('') end_mc = manifest_string.find('') mc_config_string = manifest_string[start_mc:end_mc + len('')] start_image = manifest_string.find('') end_image = manifest_string.find('') image_string = manifest_string[start_image:end_image + len('')] return mc_config_string + image_string def parse_manifest(self, manifest_filename): parts = [] encrypted_key = None encrypted_iv = None dom = minidom.parse(manifest_filename) manifest_elem = dom.getElementsByTagName('manifest')[0] parts_list = manifest_elem.getElementsByTagName('filename') for part_elem in parts_list: nodes = part_elem.childNodes for node in nodes: if node.nodeType == node.TEXT_NODE: parts.append(node.data) encrypted_key_elem = manifest_elem.getElementsByTagName('user_encrypted_key')[0] nodes = encrypted_key_elem.childNodes for node in nodes: if node.nodeType == node.TEXT_NODE: encrypted_key = node.data encrypted_iv_elem = manifest_elem.getElementsByTagName('user_encrypted_iv')[0] nodes = encrypted_iv_elem.childNodes for node in nodes: if node.nodeType == node.TEXT_NODE: encrypted_iv = node.data return parts, encrypted_key, encrypted_iv def assemble_parts(self, src_directory, directory, manifest_path, parts): manifest_filename = self.get_relative_filename(manifest_path) encrypted_filename = os.path.join(directory, manifest_filename.replace('.manifest.xml', '.enc.tar.gz')) if (len(parts) > 0): if not os.path.exists(directory): os.makedirs(directory) encrypted_file = open(encrypted_filename, "wb") for part in parts: print "Part:", self.get_relative_filename(part) part_filename = os.path.join(src_directory, part) part_file = open(part_filename, "rb") while 1: data = part_file.read(IMAGE_IO_CHUNK) if not data: break encrypted_file.write(data) part_file.close() encrypted_file.close() return encrypted_filename def decrypt_image(self, encrypted_filename, encrypted_key, encrypted_iv, private_key_path): user_priv_key = RSA.load_key(private_key_path) key = user_priv_key.private_decrypt(unhexlify(encrypted_key), RSA.pkcs1_padding) iv = user_priv_key.private_decrypt(unhexlify(encrypted_iv), RSA.pkcs1_padding) k=EVP.Cipher(alg='aes_128_cbc', key=unhexlify(key), iv=unhexlify(iv), op=0) decrypted_filename = encrypted_filename.replace('.enc', '') decrypted_file = open(decrypted_filename, "wb") encrypted_file = open(encrypted_filename, "rb") self.crypt_file(k, encrypted_file, decrypted_file) encrypted_file.close() decrypted_file.close() return decrypted_filename def untarzip_image(self, path, file): untarred_filename = file.replace('.tar.gz', '') tar_file = tarfile.open(file, "r|gz") tar_file.extractall(path) untarred_names = tar_file.getnames() tar_file.close() return untarred_names def get_block_devs(self, mapping): virtual = [] devices = [] vname = None for m in mapping: if not vname: vname = m virtual.append(vname) else: devices.append(m) vname = None return virtual, devices def generate_manifest(self, path, prefix, parts, parts_digest, file, key, iv, cert_path, ec2cert_path, private_key_path, target_arch, image_size, bundled_size, image_digest, user, kernel, ramdisk, mapping=None, product_codes=None, ancestor_ami_ids=None): user_pub_key = X509.load_cert(cert_path).get_pubkey().get_rsa() cloud_pub_key = X509.load_cert(ec2cert_path).get_pubkey().get_rsa() user_encrypted_key = hexlify(user_pub_key.public_encrypt(key, RSA.pkcs1_padding)) user_encrypted_iv = hexlify(user_pub_key.public_encrypt(iv, RSA.pkcs1_padding)) cloud_encrypted_key = hexlify(cloud_pub_key.public_encrypt(key, RSA.pkcs1_padding)) cloud_encrypted_iv = hexlify(cloud_pub_key.public_encrypt(iv, RSA.pkcs1_padding)) user_priv_key = RSA.load_key(private_key_path) manifest_file = '%s.manifest.xml' % os.path.join(path, prefix) if self.debug: print 'Manifest: ', manifest_file print 'Generating manifest %s' % manifest_file manifest_out_file = open(manifest_file, "wb") doc = Document() manifest_elem = doc.createElement("manifest") doc.appendChild(manifest_elem) #version version_elem = doc.createElement("version") version_value = doc.createTextNode(VERSION) version_elem.appendChild(version_value) manifest_elem.appendChild(version_elem) #bundler info bundler_elem = doc.createElement("bundler") bundler_name_elem = doc.createElement("name") bundler_name_value = doc.createTextNode(BUNDLER_NAME) bundler_name_elem.appendChild(bundler_name_value) bundler_version_elem = doc.createElement("version") bundler_version_value = doc.createTextNode(BUNDLER_VERSION) bundler_version_elem.appendChild(bundler_version_value) bundler_elem.appendChild(bundler_name_elem) bundler_elem.appendChild(bundler_version_elem) #release release_elem = doc.createElement("release") release_value = doc.createTextNode(RELEASE) release_elem.appendChild(release_value) bundler_elem.appendChild(release_elem) manifest_elem.appendChild(bundler_elem) #machine config machine_config_elem = doc.createElement("machine_configuration") manifest_elem.appendChild(machine_config_elem) target_arch_elem = doc.createElement("architecture") target_arch_value = doc.createTextNode(target_arch) target_arch_elem.appendChild(target_arch_value) machine_config_elem.appendChild(target_arch_elem) #block device mapping if mapping: block_dev_mapping_elem = doc.createElement("block_device_mapping") virtual_names, device_names = self.get_block_devs(mapping) vname_index = 0 for vname in virtual_names: dname = device_names[vname_index] mapping_elem = doc.createElement("mapping") virtual_elem = doc.createElement("virtual") virtual_value = doc.createTextNode(vname) virtual_elem.appendChild(virtual_value) mapping_elem.appendChild(virtual_elem) device_elem = doc.createElement("device") device_value = doc.createTextNode(dname) device_elem.appendChild(device_value) mapping_elem.appendChild(device_elem) block_dev_mapping_elem.appendChild(mapping_elem) vname_index = vname_index + 1 machine_config_elem.appendChild(block_dev_mapping_elem) if product_codes: product_codes_elem = doc.createElement("product_codes") for product_code in product_codes: product_code_elem = doc.createElement("product_code"); product_code_value = doc.createTextNode(product_code) product_code_elem.appendChild(product_code_value) product_codes_elem.appendChild(product_code_elem) machine_config_elem.appendChild(product_codes_elem) #kernel and ramdisk if kernel: kernel_id_elem = doc.createElement("kernel_id") kernel_id_value = doc.createTextNode(kernel) kernel_id_elem.appendChild(kernel_id_value) machine_config_elem.appendChild(kernel_id_elem) if ramdisk: ramdisk_id_elem = doc.createElement("ramdisk_id") ramdisk_id_value = doc.createTextNode(ramdisk) ramdisk_id_elem.appendChild(ramdisk_id_value) machine_config_elem.appendChild(ramdisk_id_elem) image_elem = doc.createElement("image") manifest_elem.appendChild(image_elem) #name image_name_elem = doc.createElement("name") image_name_value = doc.createTextNode(self.get_relative_filename(file)) image_name_elem.appendChild(image_name_value) image_elem.appendChild(image_name_elem) #user user_elem = doc.createElement("user") user_value = doc.createTextNode("%s" % (user)) user_elem.appendChild(user_value) image_elem.appendChild(user_elem) #type #TODO: fixme image_type_elem = doc.createElement("type") image_type_value = doc.createTextNode("machine") image_type_elem.appendChild(image_type_value) image_elem.appendChild(image_type_elem) #ancestor ami ids if ancestor_ami_ids: ancestry_elem = doc.createElement("ancestry") for ancestor_ami_id in ancestor_ami_ids: ancestor_id_elem = doc.createElement("ancestor_ami_id"); ancestor_id_value = doc.createTextNode(ancestor_ami_id) ancestor_id_elem.appendChild(ancestor_id_value) ancestry_elem.appendChild(ancestor_id_elem) image_elem.appendChild(ancestry_elem) #digest image_digest_elem = doc.createElement("digest") image_digest_elem.setAttribute('algorithm', 'SHA1') image_digest_value = doc.createTextNode('%s' % (image_digest)) image_digest_elem.appendChild(image_digest_value) image_elem.appendChild(image_digest_elem) #size image_size_elem = doc.createElement("size") image_size_value = doc.createTextNode("%s" % (image_size)) image_size_elem.appendChild(image_size_value) image_elem.appendChild(image_size_elem) #bundled size bundled_size_elem = doc.createElement("bundled_size") bundled_size_value = doc.createTextNode("%s" % (bundled_size)) bundled_size_elem.appendChild(bundled_size_value) image_elem.appendChild(bundled_size_elem) #key, iv cloud_encrypted_key_elem = doc.createElement("ec2_encrypted_key") cloud_encrypted_key_value = doc.createTextNode("%s" % (cloud_encrypted_key)) cloud_encrypted_key_elem.appendChild(cloud_encrypted_key_value) cloud_encrypted_key_elem.setAttribute("algorithm", AES) image_elem.appendChild(cloud_encrypted_key_elem) user_encrypted_key_elem = doc.createElement("user_encrypted_key") user_encrypted_key_value = doc.createTextNode("%s" % (user_encrypted_key)) user_encrypted_key_elem.appendChild(user_encrypted_key_value) user_encrypted_key_elem.setAttribute("algorithm", AES) image_elem.appendChild(user_encrypted_key_elem) cloud_encrypted_iv_elem = doc.createElement("ec2_encrypted_iv") cloud_encrypted_iv_value = doc.createTextNode("%s" % (cloud_encrypted_iv)) cloud_encrypted_iv_elem.appendChild(cloud_encrypted_iv_value) image_elem.appendChild(cloud_encrypted_iv_elem) user_encrypted_iv_elem = doc.createElement("user_encrypted_iv") user_encrypted_iv_value = doc.createTextNode("%s" % (user_encrypted_iv)) user_encrypted_iv_elem.appendChild(user_encrypted_iv_value) image_elem.appendChild(user_encrypted_iv_elem) #parts parts_elem = doc.createElement("parts") parts_elem.setAttribute("count", '%s' % (len(parts))) part_number = 0 for part in parts: part_elem = doc.createElement("part") filename_elem = doc.createElement("filename") filename_value = doc.createTextNode(self.get_relative_filename(part)) filename_elem.appendChild(filename_value) part_elem.appendChild(filename_elem) #digest part_digest_elem = doc.createElement("digest") part_digest_elem.setAttribute('algorithm', 'SHA1') part_digest_value = doc.createTextNode(parts_digest[part_number]) part_digest_elem.appendChild(part_digest_value) part_elem.appendChild(part_digest_elem) part_elem.setAttribute("index", '%s' % (part_number)) parts_elem.appendChild(part_elem) part_number += 1 image_elem.appendChild(parts_elem) manifest_string = doc.toxml() string_to_sign = self.get_verification_string(manifest_string) signature_elem = doc.createElement("signature") sha_manifest = sha() sha_manifest.update(string_to_sign) signature_value = doc.createTextNode("%s" % (hexlify(user_priv_key.sign(sha_manifest.digest())))) signature_elem.appendChild(signature_value) manifest_elem.appendChild(signature_elem) manifest_out_file.write(doc.toxml()) manifest_out_file.close() def add_excludes(self, path, excludes): if self.debug: print "Reading /etc/mtab..." mtab_file = open("/etc/mtab", "r") while 1: mtab_line = mtab_file.readline() if not mtab_line: break mtab_line_parts = mtab_line.split(' ') mount_point = mtab_line_parts[1] fs_type = mtab_line_parts[2] if (mount_point.find(path) == 0) and (fs_type not in self.img.ALLOWED_FS_TYPES): if self.debug: print 'Excluding %s...' % mount_point excludes.append(mount_point) mtab_file.close() for banned in self.img.BANNED_MOUNTS: excludes.append(banned) def make_image(self, size_in_MB, excludes, prefix, destination_path): image_file = '%s.img' % (prefix) image_path = '%s/%s' % (destination_path, image_file) if not os.path.exists(destination_path): os.makedirs(destination_path) if self.img == "Unsupported": print "Platform not fully supported." sys.exit(1) self.img.create_image(size_in_MB, image_path) self.img.make_fs(image_path) return image_path def create_loopback(self, image_path): try: Util().check_prerequisite_command('losetup') except NotFoundError: sys.exit(1) tries = 0 while tries < MAX_LOOP_DEVS: loop_dev = Popen(["losetup", "-f"], stdout=PIPE).communicate()[0].replace('\n', '') if loop_dev: output = Popen(["losetup", "%s" % loop_dev, "%s" % image_path], stdout=PIPE, stderr=PIPE).communicate() if not output[1]: return loop_dev else: print "Could not create loopback device. Aborting" sys.exit(1) tries += 1 def mount_image(self, image_path): try: Util().check_prerequisite_command('mount') except NotFoundError: sys.exit(1) tmp_mnt_point = "/tmp/%s" % (hex(BN.rand(16)))[2:6] if not os.path.exists(tmp_mnt_point): os.makedirs(tmp_mnt_point) if self.debug: print "Creating loopback device..." loop_dev = self.create_loopback(image_path) if self.debug: print "Mounting image..." Popen(["mount", loop_dev, tmp_mnt_point], stdout=PIPE).communicate() return tmp_mnt_point, loop_dev def copy_to_image(self, mount_point, volume_path, excludes): try: Util().check_prerequisite_command('rsync') except NotFoundError: raise CopyError rsync_cmd = ["rsync", "-aXS"] for exclude in excludes: rsync_cmd.append("--exclude") rsync_cmd.append(exclude) rsync_cmd.append(volume_path) rsync_cmd.append(mount_point) if self.debug: print "Copying files..." for exclude in excludes: print "Excluding:", exclude output = Popen(rsync_cmd, stdout=PIPE, stderr=PIPE).communicate() for dir in self.img.ESSENTIAL_DIRS: dir_path = os.path.join(mount_point, dir) if not os.path.exists(dir_path): os.mkdir(dir_path) if dir == "tmp": os.chmod(dir_path, 01777) self.img.make_essential_devs(mount_point) mtab_file = open("/etc/mtab", "r") while 1: mtab_line = mtab_file.readline() if not mtab_line: break mtab_line_parts = mtab_line.split(' ') mount_location = mtab_line_parts[1] fs_type = mtab_line_parts[2] if fs_type == "tmpfs": mount_location = mount_location[1:] dir_path = os.path.join(mount_point, mount_location) if not os.path.exists(dir_path): if self.debug: print 'Making essential directory %s' % mount_location os.makedirs(dir_path) mtab_file.close() if output[1]: raise CopyError def unmount_image(self, mount_point): try: Util().check_prerequisite_command('umount') except NotFoundError: sys.exit(1) if self.debug: print "Unmounting image..." Popen(["umount", "-d", mount_point], stdout=PIPE).communicate()[0] os.rmdir(mount_point) def copy_volume(self, image_path, volume_path, excludes, generate_fstab, fstab_path): mount_point, loop_dev = self.mount_image(image_path) try: output = self.copy_to_image(mount_point, volume_path, excludes) if self.img == "Unsupported": print "Platform not fully supported." sys.exit(1) self.img.add_fstab(mount_point, generate_fstab, fstab_path) except CopyError: raise CopyError finally: self.unmount_image(mount_point) def can_read_instance_metadata(self): meta_data = urllib.urlopen(METADATA_URL) def get_instance_metadata(self, type): if self.debug: print "Reading instance metadata", type metadata = urllib.urlopen(METADATA_URL + type).read() if "Not" in metadata and "Found" in metadata and "404" in metadata: raise MetadataReadError return metadata def get_instance_ramdisk(self): return self.get_instance_metadata('ramdisk-id') def get_instance_kernel(self): return self.get_instance_metadata('kernel-id') def get_instance_product_codes(self): return self.get_instance_metadata('product-codes') def get_ancestor_ami_ids(self): return self.get_instance_metadata('ancestor-ami-ids') def get_instance_block_device_mappings(self): keys = self.get_instance_metadata('block-device-mapping').split('\n') mapping = [] for k in keys: mapping.append(k) mapping.append(self.get_instance_metadata(os.path.join('block-device-mapping', k))) return mapping def display_error_and_exit(self, msg): code = None message = None index = msg.find("<") if (index < 0): print msg sys.exit(1) msg = msg[index-1:] msg = msg.replace("\n", "") dom = minidom.parseString(msg) try: error_elem = dom.getElementsByTagName('Error')[0] code_elem = error_elem.getElementsByTagName('Code')[0] nodes = code_elem.childNodes for node in nodes: if node.nodeType == node.TEXT_NODE: code = node.data msg_elem = error_elem.getElementsByTagName('Message')[0] nodes = msg_elem.childNodes for node in nodes: if node.nodeType == node.TEXT_NODE: message = node.data print "%s:" % code, message except Exception: print msg sys.exit(1)