예제 #1
0
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)
예제 #2
0
    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,
                )
예제 #3
0
    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
예제 #5
0
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)