def cryptdisks_start_cli(): """ Usage: cryptdisks-start-fallback NAME Reads /etc/crypttab and unlocks the encrypted filesystem with the given NAME. This program emulates the functionality of Debian's cryptdisks_start program, but it only supports LUKS encryption and a small subset of the available encryption options. """ # Enable logging to the terminal and system log. coloredlogs.install(syslog=True) # Get the name of the encrypted filesystem from the command line arguments # and show a simple usage message when no name is given as an argument. try: target = sys.argv[1] except IndexError: usage(dedent(cryptdisks_start_cli.__doc__)) else: # Call our Python implementation of `cryptdisks_start'. try: cryptdisks_start(target) except ValueError as e: # cryptdisks_start() raises ValueError when the given target isn't # listed in /etc/crypttab. This doesn't deserve a traceback on the # terminal. warning("Error: %s", e) sys.exit(1) except Exception as e: # Any other exceptions are logged to the terminal and system log. logger.exception("Aborting due to exception!") sys.exit(1)
def cryptdisks_start_helper(self, emulated): """ Test cryptdisks_start integration and emulation. This test requires the following line to be present in ``/etc/crypttab``:: linux-utils /tmp/linux-utils.img /tmp/linux-utils.key discard,luks,noauto,readonly,tries=1 """ if not any(entry.target == TEST_TARGET_NAME and entry.source == TEST_IMAGE_FILE and entry.key_file == TEST_KEY_FILE and 'luks' in entry.options for entry in parse_crypttab()): return self.skipTest( "/etc/crypttab isn't set up to test cryptdisks_start!") context = LocalContext() if emulated: # Disable the use of the `cryptdisks_start' program. context.find_program = MagicMock(return_value=[]) # Generate the key file. with TemporaryKeyFile(filename=TEST_KEY_FILE): # Create the image file and the encrypted filesystem. create_image_file(filename=TEST_IMAGE_FILE, size=coerce_size('10 MiB')) create_encrypted_filesystem(device_file=TEST_IMAGE_FILE, key_file=TEST_KEY_FILE) # Make sure the mapped device file doesn't exist yet. assert not os.path.exists(TEST_TARGET_DEVICE) # Unlock the encrypted filesystem using `cryptdisks_start'. if emulated: cryptdisks_start(context=context, target=TEST_TARGET_NAME) else: returncode, output = run_cli(cryptdisks_start_cli, TEST_TARGET_NAME) assert returncode == 0 # Make sure the mapped device file has appeared. assert os.path.exists(TEST_TARGET_DEVICE) # Unlock the encrypted filesystem again (this should be a no-op). cryptdisks_start(context=context, target=TEST_TARGET_NAME) # Make sure the mapped device file still exists. assert os.path.exists(TEST_TARGET_DEVICE) # Lock the filesystem before we finish. if emulated: cryptdisks_stop(context=context, target=TEST_TARGET_NAME) else: returncode, output = run_cli(cryptdisks_stop_cli, TEST_TARGET_NAME) assert returncode == 0 # Make sure the mapped device file has disappeared. assert not os.path.exists(TEST_TARGET_DEVICE) # Lock the filesystem again (this should be a no-op). cryptdisks_stop(context=context, target=TEST_TARGET_NAME) # Make sure the mapped device file is still gone. assert not os.path.exists(TEST_TARGET_DEVICE) # Test the error handling. for function in cryptdisks_start, cryptdisks_stop: self.assertRaises( ValueError if emulated else ExternalCommandFailed, function, context=context, target=TEST_UNKNOWN_TARGET, )
def unlock_device(self): """ Automatically unlock the encrypted filesystem to which backups are written. :raises: The following exceptions can be raised: - :exc:`.DestinationContextUnavailable`, refer to :attr:`destination_context` for details. - :exc:`~executor.ExternalCommandFailed` when the cryptdisks_start_ command reports an error. When :attr:`crypto_device` is set this method uses :func:`~linux_utils.luks.cryptdisks_start()` to unlock the encrypted filesystem to which backups are written before the backup starts. When :func:`~linux_utils.luks.cryptdisks_start()` was called before the backup started, :func:`~linux_utils.luks.cryptdisks_stop()` will be called when the backup finishes. To enable the use of :func:`~linux_utils.luks.cryptdisks_start()` and :func:`~linux_utils.luks.cryptdisks_stop()` you need to create an `/etc/crypttab`_ entry that maps your physical device to a symbolic name. If you want this process to run fully unattended you can configure a key file in `/etc/crypttab`_, otherwise you will be asked for the password when the encrypted filesystem is unlocked. .. _/etc/crypttab: https://manpages.debian.org/crypttab .. _cryptdisks_start: https://manpages.debian.org/cryptdisks_start """ if self.crypto_device: if self.crypto_device_unlocked: logger.info("Encrypted filesystem is already unlocked (%s) ..", self.crypto_device) else: cryptdisks_start( context=self.destination_context, target=self.crypto_device, ) if not self.crypto_device_unlocked: msg = "Failed to unlock encrypted filesystem! (%s)" raise FailedToUnlockError(msg % self.crypto_device) self.destination_context.cleanup( cryptdisks_stop, context=self.destination_context, target=self.crypto_device, )
def activate_encrypted_drive(mapper_name, physical_device, keys_directory, reset=False): """ Initialize and activate an encrypted volume. :param mapper_name: The device mapper name for the virtual keys device (a string). :param physical_device: The pathname of the physical drive that contains the encrypted LUKS volume (a string). :param keys_directory: The mount point for the virtual keys device (a string). :param reset: If ``True`` the key file for the encrypted volume will be regenerated (overwriting any previous key). :return: An integer created by combining members of the :class:`DriveStatus` enumeration using bitwise or. :raises: :exc:`~executor.ExternalCommandFailed` when a program like ``cryptsetup`` or ``mount`` reports an error. """ status = DriveStatus.DEFAULT mapper_device = '/dev/mapper/%s' % mapper_name device_exists = os.path.exists(mapper_device) if reset or not device_exists: key_file = os.path.join(keys_directory, '%s.key' % mapper_name) if reset or not os.path.isfile(key_file): logger.info("Creating %s to unlock %s (%s)", key_file, mapper_name, physical_device) execute('dd', 'if=/dev/urandom', 'of=%s' % key_file, 'bs=4', 'count=1024') logger.info("Installing %s on %s ..", key_file, physical_device) execute('cryptsetup', 'luksAddKey', physical_device, key_file) status |= DriveStatus.INITIALIZED os.chmod(key_file, 0o400) if not device_exists: logger.info("Unlocking encrypted drive %s ..", mapper_name) cryptdisks_start(mapper_name) status |= DriveStatus.UNLOCKED if drive_needs_mounting(mapper_device): logger.info("Mounting %s ..", mapper_device) execute('mount', mapper_device) status |= DriveStatus.MOUNTED return status
def unlocked_device(crypto_device): """Context manager that runs ``cryptdisks_start`` and ``cryptdisks_stop``.""" cryptdisks_start(target=crypto_device) yield cryptdisks_stop(target=crypto_device)