def test_multiple_options(self): brkt_config_in = { 'api_host': api_host_port, 'hsmproxy_host': hsmproxy_host_port, 'network_host': network_host_port, 'ntp_servers': [ntp_server1], 'identity_token': test_jwt } ic = InstanceConfig(brkt_config_in) ic.add_brkt_file('ca_cert.pem.example.com', 'DUMMY CERT') ud = ic.make_userdata() brkt_config_json = get_mime_part_payload(ud, BRKT_CONFIG_CONTENT_TYPE) brkt_config = json.loads(brkt_config_json)['brkt'] self.assertEqual(brkt_config['identity_token'], test_jwt) self.assertEqual(brkt_config['ntp_servers'], [ntp_server1]) self.assertEqual(brkt_config['api_host'], api_host_port) self.assertEqual(brkt_config['hsmproxy_host'], hsmproxy_host_port) self.assertEqual(brkt_config['network_host'], network_host_port) brkt_files = get_mime_part_payload(ud, BRKT_FILES_CONTENT_TYPE) self.assertEqual(brkt_files, "/var/brkt/ami_config/ca_cert.pem.example.com: " + "{contents: DUMMY CERT}\n") """
def test_proxy_config(self): # The proxy file goes in a brkt-file part, # so the brkt config should be empty ic = InstanceConfig({}) p = Proxy(host=proxy_host, port=proxy_port) proxy_config = proxy.generate_proxy_config(p) ic.add_brkt_file('proxy.yaml', proxy_config) _verify_proxy_config_in_userdata(self, ic.make_userdata())
def test_brkt_env(self): brkt_config_in = { 'api_host': api_host_port, 'hsmproxy_host': hsmproxy_host_port } ic = InstanceConfig(brkt_config_in) config_json = ic.make_brkt_config_json() expected_json = '{"brkt": {"api_host": "%s", "hsmproxy_host": "%s"}}' % \ (api_host_port, hsmproxy_host_port) self.assertEqual(config_json, expected_json)
def make_instance_config(values=None, brkt_env=None, mode=INSTANCE_CREATOR_MODE): log.debug('Creating instance config with %s', brkt_env) brkt_config = {} if not values: return InstanceConfig(brkt_config, mode) if brkt_env: add_brkt_env_to_brkt_config(brkt_env, brkt_config) if values.token: brkt_config['identity_token'] = values.token if values.ntp_servers: brkt_config['ntp_servers'] = values.ntp_servers if mode in (INSTANCE_CREATOR_MODE, INSTANCE_UPDATER_MODE): brkt_config['status_port'] = (values.status_port or encryptor_service.ENCRYPTOR_STATUS_PORT) ic = InstanceConfig(brkt_config, mode) # Now handle the args that cause files to be added to brkt-files proxy_config = get_proxy_config(values) if proxy_config: ic.add_brkt_file('proxy.yaml', proxy_config) if 'ca_cert' in values and values.ca_cert: if mode != INSTANCE_CREATOR_MODE: raise ValidationError( 'Can only specify ca-cert for instance in Creator mode' ) if not values.brkt_env: raise ValidationError( 'Must specify brkt-env when specifying ca-cert.' ) try: with open(values.ca_cert, 'r') as f: ca_cert_data = f.read() except IOError as e: raise ValidationError(e) try: x509.load_pem_x509_certificate(ca_cert_data, default_backend()) except Exception as e: raise ValidationError('Error validating CA cert: %s' % e) domain = get_domain_from_brkt_env(brkt_env) ca_cert_filename = 'ca_cert.pem.' + domain ic.add_brkt_file(ca_cert_filename, ca_cert_data) return ic
def test_ntp_servers(self): # First with just one server ic = InstanceConfig({'ntp_servers': [ntp_server1]}) config_json = ic.make_brkt_config_json() expected_json = '{"brkt": {"ntp_servers": ["%s"]}}' % ntp_server1 self.assertEqual(config_json, expected_json) # Now try two servers ic = InstanceConfig({'ntp_servers': [ntp_server1, ntp_server2]}) config_json = ic.make_brkt_config_json() expected_json = '{"brkt": {"ntp_servers": ["%s", "%s"]}}' % \ (ntp_server1, ntp_server2) self.assertEqual(config_json, expected_json)
def test_cleanup(self): gce_svc = DummyGCEService() encrypt_gce_image.encrypt(gce_svc=gce_svc, enc_svc_cls=DummyEncryptorService, image_id=IGNORE_IMAGE, encryptor_image='encryptor-image', encrypted_image_name='ubuntu-encrypted', zone='us-central1-a', instance_config=InstanceConfig( {'identity_token': TOKEN})) self.assertEqual(len(gce_svc.disks), 0) self.assertEqual(len(gce_svc.instances), 0)
def test_cleanup_on_fail(self): gce_svc = DummyGCEService() with self.assertRaises(Exception): update_gce_image.update_gce_image( gce_svc=gce_svc, enc_svc_cls=FailedEncryptionService, image_id=IGNORE_IMAGE, encryptor_image='encryptor-image', encrypted_image_name='ubuntu-encrypted', zone='us-central1-a', instance_config=InstanceConfig({'identity_token': TOKEN})) self.assertEqual(len(gce_svc.disks), 0) self.assertEqual(len(gce_svc.instances), 0)
def test_multiple_options(self): brkt_config_in = { 'api_host': api_host_port, 'hsmproxy_host': hsmproxy_host_port, 'ntp_servers': [ntp_server1], 'identity_token': test_jwt } ic = InstanceConfig(brkt_config_in) ic.add_brkt_file('ca_cert.pem.example.com', 'DUMMY CERT') ud = ic.make_userdata() brkt_config_json = get_mime_part_payload(ud, BRKT_CONFIG_CONTENT_TYPE) brkt_config = json.loads(brkt_config_json)['brkt'] self.assertEqual(brkt_config['identity_token'], test_jwt) self.assertEqual(brkt_config['ntp_servers'], [ntp_server1]) self.assertEqual(brkt_config['api_host'], api_host_port) self.assertEqual(brkt_config['hsmproxy_host'], hsmproxy_host_port) brkt_files = get_mime_part_payload(ud, BRKT_FILES_CONTENT_TYPE) self.assertEqual( brkt_files, "/var/brkt/ami_config/ca_cert.pem.example.com: " + "{contents: DUMMY CERT}\n") """
def make_instance_config(values=None, brkt_env=None, mode=INSTANCE_CREATOR_MODE): log.debug('Creating instance config with %s', brkt_env) brkt_config = {} if not values: return InstanceConfig(brkt_config, mode) if brkt_env: add_brkt_env_to_brkt_config(brkt_env, brkt_config) if values.token: brkt_config['identity_token'] = values.token if values.ntp_servers: brkt_config['ntp_servers'] = values.ntp_servers if mode in (INSTANCE_CREATOR_MODE, INSTANCE_UPDATER_MODE): brkt_config['status_port'] = (values.status_port or encryptor_service.ENCRYPTOR_STATUS_PORT) ic = InstanceConfig(brkt_config, mode) # Now handle the args that cause files to be added to brkt-files proxy_config = get_proxy_config(values) if proxy_config: ic.add_brkt_file('proxy.yaml', proxy_config) if 'ca_cert' in values and values.ca_cert: if mode != INSTANCE_CREATOR_MODE: raise ValidationError( 'Can only specify ca-cert for instance in Creator mode') if not values.brkt_env: raise ValidationError( 'Must specify brkt-env when specifying ca-cert.') try: with open(values.ca_cert, 'r') as f: ca_cert_data = f.read() except IOError as e: raise ValidationError(e) try: x509.load_pem_x509_certificate(ca_cert_data, default_backend()) except Exception as e: raise ValidationError('Error validating CA cert: %s' % e) domain = get_domain_from_brkt_env(brkt_env) ca_cert_filename = 'ca_cert.pem.' + domain ic.add_brkt_file(ca_cert_filename, ca_cert_data) return ic
def _run_encryptor_instance( aws_svc, encryptor_image_id, snapshot, root_size, guest_image_id, security_group_ids=None, subnet_id=None, zone=None, instance_config=None, status_port=encryptor_service.ENCRYPTOR_STATUS_PORT): bdm = BlockDeviceMapping() if instance_config is None: instance_config = InstanceConfig() image = aws_svc.get_image(encryptor_image_id) virtualization_type = image.virtualization_type # Use gp2 for fast burst I/O copying root drive guest_unencrypted_root = EBSBlockDeviceType( volume_type='gp2', snapshot_id=snapshot, delete_on_termination=True) # Use gp2 for fast burst I/O copying root drive log.info('Launching encryptor instance with snapshot %s', snapshot) # They are creating an encrypted AMI instead of updating it # Use gp2 for fast burst I/O copying root drive guest_encrypted_root = EBSBlockDeviceType( volume_type='gp2', delete_on_termination=True) guest_encrypted_root.size = 2 * root_size + 1 if virtualization_type == 'paravirtual': bdm['/dev/sda4'] = guest_unencrypted_root bdm['/dev/sda5'] = guest_encrypted_root else: # Use 'sd' names even though AWS maps these to 'xvd' # The AWS GUI only exposes 'sd' names, and won't allow # the user to attach to an existing 'sd' name in use, but # would allow conflicts if we used 'xvd' names here. bdm['/dev/sdf'] = guest_unencrypted_root bdm['/dev/sdg'] = guest_encrypted_root # If security groups were not specified, create a temporary security # group that allows us to poll the metavisor for encryption progress. temp_sg_id = None instance = None try: run_instance = aws_svc.run_instance if not security_group_ids: vpc_id = None if subnet_id: subnet = aws_svc.get_subnet(subnet_id) vpc_id = subnet.vpc_id temp_sg_id = create_encryptor_security_group( aws_svc, vpc_id=vpc_id, status_port=status_port).id security_group_ids = [temp_sg_id] # Wrap with a retry, to handle eventual consistency issues with # the newly-created group. run_instance = aws_svc.retry( aws_svc.run_instance, error_code_regexp='InvalidGroup\.NotFound' ) user_data = instance_config.make_userdata() compressed_user_data = gzip_user_data(user_data) instance = run_instance( encryptor_image_id, security_group_ids=security_group_ids, user_data=compressed_user_data, placement=zone, block_device_map=bdm, subnet_id=subnet_id ) aws_svc.create_tags( instance.id, name=NAME_ENCRYPTOR, description=DESCRIPTION_ENCRYPTOR % {'image_id': guest_image_id} ) log.info('Launching encryptor instance %s', instance.id) instance = wait_for_instance(aws_svc, instance.id) # Tag volumes. bdm = instance.block_device_mapping if virtualization_type == 'paravirtual': aws_svc.create_tags( bdm['/dev/sda5'].volume_id, name=NAME_ENCRYPTED_ROOT_VOLUME) aws_svc.create_tags( bdm['/dev/sda2'].volume_id, name=NAME_METAVISOR_ROOT_VOLUME) aws_svc.create_tags( bdm['/dev/sda1'].volume_id, name=NAME_METAVISOR_GRUB_VOLUME) aws_svc.create_tags( bdm['/dev/sda3'].volume_id, name=NAME_METAVISOR_LOG_VOLUME) else: aws_svc.create_tags( bdm['/dev/sda1'].volume_id, name=NAME_METAVISOR_ROOT_VOLUME) aws_svc.create_tags( bdm['/dev/sdg'].volume_id, name=NAME_ENCRYPTED_ROOT_VOLUME) except: cleanup_instance_ids = [] cleanup_sg_ids = [] if instance: cleanup_instance_ids = [instance.id] if temp_sg_id: cleanup_sg_ids = [temp_sg_id] clean_up( aws_svc, instance_ids=cleanup_instance_ids, security_group_ids=cleanup_sg_ids ) raise return instance, temp_sg_id
def test_jwt(self): ic = InstanceConfig({'identity_token': test_jwt}) config_json = ic.make_brkt_config_json() expected_json = '{"brkt": {"identity_token": "%s"}}' % test_jwt self.assertEqual(config_json, expected_json)
def instance_config_from_values(values=None, mode=INSTANCE_CREATOR_MODE, cli_config=None): """ Return an InstanceConfig object, based on options specified on the command line and Metavisor mode. :param values an argparse.Namespace object :param mode the mode in which Metavisor is running :param cli_config an brkt_cli.config.CLIConfig instance """ brkt_config = {} if not values: return InstanceConfig(brkt_config, mode) # Handle BracketEnvironment, depending on the mode. brkt_env = None if mode in (INSTANCE_CREATOR_MODE, INSTANCE_UPDATER_MODE): # Yeti environment should only be set in CREATOR or UPDATER mode. # When launching, we want to preserve the original environment that # was specified during encryption. # # If the Yeti environment was not specified, use the production # environment. brkt_env = brkt_cli.brkt_env_from_values(values) if cli_config is not None and brkt_env is None: name, brkt_env = cli_config.get_current_env() log.info('Using %s environment', name) log.debug(brkt_env) config_brkt_env = brkt_env or brkt_cli.get_prod_brkt_env() add_brkt_env_to_brkt_config(config_brkt_env, brkt_config) # We only monitor status when encrypting or updating. brkt_config['status_port'] = ( values.status_port or encryptor_service.ENCRYPTOR_STATUS_PORT ) if values.token: brkt_config['identity_token'] = values.token if values.ntp_servers: brkt_config['ntp_servers'] = values.ntp_servers log.debug('Parsed brkt_config %s', brkt_config) ic = InstanceConfig(brkt_config, mode) # Now handle the args that cause files to be added to brkt-files proxy_config = get_proxy_config(values) if proxy_config: ic.add_brkt_file('proxy.yaml', proxy_config) if 'ca_cert' in values and values.ca_cert: if not brkt_env: raise ValidationError( 'Must specify --service-domain or --brkt-env when specifying ' '--ca-cert.' ) try: with open(values.ca_cert, 'r') as f: ca_cert_data = f.read() except IOError as e: raise ValidationError(e) try: x509.load_pem_x509_certificate(ca_cert_data, default_backend()) except Exception as e: raise ValidationError('Error validating CA cert: %s' % e) domain = get_domain_from_brkt_env(brkt_env) ca_cert_filename = 'ca_cert.pem.' + domain ic.add_brkt_file(ca_cert_filename, ca_cert_data) if 'guest_fqdn' in values and values.guest_fqdn: ic.add_brkt_file('vpn.yaml', 'fqdn: ' + values.guest_fqdn) return ic
def update_ami(aws_svc, encrypted_ami, updater_ami, encrypted_ami_name, subnet_id=None, security_group_ids=None, enc_svc_class=encryptor_service.EncryptorService, guest_instance_type='m3.medium', updater_instance_type='m3.medium', instance_config=None, status_port=encryptor_service.ENCRYPTOR_STATUS_PORT): encrypted_guest = None updater = None mv_root_id = None temp_sg_id = None if instance_config is None: instance_config = InstanceConfig() try: guest_image = aws_svc.get_image(encrypted_ami) # Step 1. Launch encrypted guest AMI # Use 'updater' mode to avoid chain loading the guest # automatically. We just want this AMI/instance up as the # base to create a new AMI and preserve license # information embedded in the guest AMI log.info("Launching encrypted guest/updater") instance_config.brkt_config['solo_mode'] = 'updater' instance_config.brkt_config['status_port'] = status_port encrypted_guest = aws_svc.run_instance( encrypted_ami, instance_type=guest_instance_type, ebs_optimized=False, subnet_id=subnet_id, user_data=json.dumps(instance_config.brkt_config)) aws_svc.create_tags( encrypted_guest.id, name=NAME_GUEST_CREATOR, description=DESCRIPTION_GUEST_CREATOR % {'image_id': encrypted_ami} ) # Run updater in same zone as guest so we can swap volumes user_data = instance_config.make_userdata() compressed_user_data = gzip_user_data(user_data) # If the user didn't specify a security group, create a temporary # security group that allows brkt-cli to get status from the updater. run_instance = aws_svc.run_instance if not security_group_ids: vpc_id = None if subnet_id: subnet = aws_svc.get_subnet(subnet_id) vpc_id = subnet.vpc_id temp_sg_id = create_encryptor_security_group( aws_svc, vpc_id=vpc_id, status_port=status_port).id security_group_ids = [temp_sg_id] # Wrap with a retry, to handle eventual consistency issues with # the newly-created group. run_instance = aws_svc.retry( aws_svc.run_instance, error_code_regexp='InvalidGroup\.NotFound' ) updater = run_instance( updater_ami, instance_type=updater_instance_type, user_data=compressed_user_data, ebs_optimized=False, subnet_id=subnet_id, placement=encrypted_guest.placement, security_group_ids=security_group_ids) aws_svc.create_tags( updater.id, name=NAME_METAVISOR_UPDATER, description=DESCRIPTION_METAVISOR_UPDATER, ) wait_for_instance(aws_svc, encrypted_guest.id, state="running") log.info("Launched guest: %s Updater: %s" % (encrypted_guest.id, updater.id) ) # Step 2. Wait for the updater to finish and stop the instances aws_svc.stop_instance(encrypted_guest.id) updater = wait_for_instance(aws_svc, updater.id, state="running") host_ips = [] if updater.ip_address: host_ips.append(updater.ip_address) if updater.private_ip_address: host_ips.append(updater.private_ip_address) log.info('Adding %s to NO_PROXY environment variable' % updater.private_ip_address) if os.environ.get('NO_PROXY'): os.environ['NO_PROXY'] += "," + \ updater.private_ip_address else: os.environ['NO_PROXY'] = updater.private_ip_address enc_svc = enc_svc_class(host_ips, port=status_port) log.info('Waiting for updater service on %s (port %s on %s)', updater.id, enc_svc.port, ', '.join(host_ips)) wait_for_encryptor_up(enc_svc, Deadline(600)) try: wait_for_encryption(enc_svc) except Exception as e: # Stop the updater instance, to make the console log available. encrypt_ami.stop_and_wait(aws_svc, updater.id) log_exception_console(aws_svc, e, updater.id) raise aws_svc.stop_instance(updater.id) encrypted_guest = wait_for_instance( aws_svc, encrypted_guest.id, state="stopped") updater = wait_for_instance(aws_svc, updater.id, state="stopped") guest_bdm = encrypted_guest.block_device_mapping updater_bdm = updater.block_device_mapping # Step 3. Detach old BSD drive(s) and delete from encrypted guest if guest_image.virtualization_type == 'paravirtual': d_list = ['/dev/sda1', '/dev/sda2', '/dev/sda3'] else: d_list = [encrypted_guest.root_device_name] for d in d_list: log.info("Detaching old metavisor disk: %s from %s" % (guest_bdm[d].volume_id, encrypted_guest.id)) aws_svc.detach_volume(guest_bdm[d].volume_id, instance_id=encrypted_guest.id, force=True ) aws_svc.delete_volume(guest_bdm[d].volume_id) # Step 4. Snapshot MV volume(s) log.info("Creating snapshots") if guest_image.virtualization_type == 'paravirtual': description = DESCRIPTION_SNAPSHOT % {'image_id': updater.id} snap_root = aws_svc.create_snapshot( updater_bdm['/dev/sda2'].volume_id, name=NAME_METAVISOR_ROOT_SNAPSHOT, description=description ) snap_log = aws_svc.create_snapshot( updater_bdm['/dev/sda3'].volume_id, name=NAME_METAVISOR_LOG_SNAPSHOT, description=description ) wait_for_snapshots(aws_svc, snap_root.id, snap_log.id) dev_root = EBSBlockDeviceType(volume_type='gp2', snapshot_id=snap_root.id, delete_on_termination=True) dev_log = EBSBlockDeviceType(volume_type='gp2', snapshot_id=snap_log.id, delete_on_termination=True) guest_bdm['/dev/sda2'] = dev_root guest_bdm['/dev/sda3'] = dev_log # Use updater as base instance for create_image boot_snap_name = NAME_METAVISOR_GRUB_SNAPSHOT root_device_name = updater.root_device_name guest_root = '/dev/sda5' d_list.append(guest_root) else: # Use guest_instance as base instance for create_image boot_snap_name = NAME_METAVISOR_ROOT_SNAPSHOT root_device_name = guest_image.root_device_name guest_root = '/dev/sdf' d_list.append(guest_root) # Preserve volume type for any additional attached volumes for d in guest_bdm.keys(): if d not in d_list: log.debug("Preserving volume type for disk %s", d) vol_id = guest_bdm[d].volume_id vol = aws_svc.get_volume(vol_id) guest_bdm[d].volume_type = vol.type # Step 5. Move new MV boot disk to base instance log.info("Detach boot volume from %s" % (updater.id,)) mv_root_id = updater_bdm['/dev/sda1'].volume_id aws_svc.detach_volume(mv_root_id, instance_id=updater.id, force=True ) # Step 6. Attach new boot disk to guest instance log.info("Attaching new metavisor boot disk: %s to %s" % (mv_root_id, encrypted_guest.id) ) aws_svc.attach_volume(mv_root_id, encrypted_guest.id, root_device_name) encrypted_guest = encrypt_ami.wait_for_volume_attached( aws_svc, encrypted_guest.id, root_device_name) guest_bdm[root_device_name] = \ encrypted_guest.block_device_mapping[root_device_name] guest_bdm[root_device_name].delete_on_termination = True guest_bdm[root_device_name].volume_type = 'gp2' guest_root_vol_id = guest_bdm[guest_root].volume_id guest_root_vol = aws_svc.get_volume(guest_root_vol_id) guest_bdm[guest_root].volume_type = guest_root_vol.type # Step 7. Create new AMI. Preserve billing/license info log.info("Creating new AMI") ami = aws_svc.create_image( encrypted_guest.id, encrypted_ami_name, description=guest_image.description, no_reboot=True, block_device_mapping=guest_bdm ) wait_for_image(aws_svc, ami) image = aws_svc.get_image(ami, retry=True) aws_svc.create_tags( image.block_device_mapping[root_device_name].snapshot_id, name=boot_snap_name, ) aws_svc.create_tags( image.block_device_mapping[guest_root].snapshot_id, name=NAME_ENCRYPTED_ROOT_SNAPSHOT, ) aws_svc.create_tags(ami) return ami finally: instance_ids = set() volume_ids = set() sg_ids = set() if encrypted_guest: instance_ids.add(encrypted_guest.id) if updater: instance_ids.add(updater.id) if mv_root_id: volume_ids.add(mv_root_id) if temp_sg_id: sg_ids.add(temp_sg_id) clean_up(aws_svc, instance_ids=instance_ids, volume_ids=volume_ids, security_group_ids=sg_ids)