def is_encrypted(context): """ Detect whether a remote system is using root disk encryption. :param context: A :class:`~executor.contexts.RemoteContext` object. :returns: :data:`True` if root disk encryption is being used, :data:`False` otherwise. """ logger.info("Checking root disk encryption on %s ..", context) for entry in parse_crypttab(context=context): if context.test('test', '-b', entry.source_device): logger.verbose("Checking if %s contains root filesystem ..", entry.source_device) listing = context.capture('lsblk', entry.source_device) if '/' in listing.split(): logger.info( "Yes it looks like the system is using root disk encryption." ) return True else: logger.verbose("Ignoring %s because it's not a block device.", entry.source_device) logger.info( "No it doesn't look like the system is using root disk encryption.") return False
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 cryptdisks_stop(target, context=None): """ Execute cryptdisks_stop_ or emulate its functionality. :param target: The mapped device name (a string). :param context: An execution context created by :mod:`executor.contexts` (coerced using :func:`.coerce_context()`). :raises: :exc:`~executor.ExternalCommandFailed` when a command fails, :exc:`~exceptions.ValueError` when no entry in `/etc/crypttab`_ matches `target`. .. _cryptdisks_stop: https://manpages.debian.org/cryptdisks_stop """ context = coerce_context(context) logger.debug("Checking if `cryptdisks_stop' program is installed ..") if context.find_program('cryptdisks_stop'): logger.debug("Using the real `cryptdisks_stop' program ..") context.execute('cryptdisks_stop', target, sudo=True) else: logger.debug( "Emulating `cryptdisks_stop' functionality (program not installed) .." ) for entry in parse_crypttab(context=context): if entry.target == target and 'luks' in entry.options: logger.debug("Matched /etc/crypttab entry: %s", entry) if entry.is_unlocked: lock_filesystem(context=context, target=target) else: logger.debug( "Encrypted filesystem is already locked, doing nothing .." ) break else: msg = "Encrypted filesystem not listed in /etc/crypttab! (%r)" raise ValueError(msg % target)
def cryptdisks_start(target, context=None): """ Execute :man:`cryptdisks_start` or emulate its functionality. :param target: The mapped device name (a string). :param context: See :func:`.coerce_context()` for details. :raises: :exc:`~executor.ExternalCommandFailed` when a command fails, :exc:`~exceptions.ValueError` when no entry in `/etc/crypttab`_ matches `target`. """ context = coerce_context(context) logger.debug("Checking if `cryptdisks_start' program is installed ..") if context.find_program('cryptdisks_start'): logger.debug("Using the real `cryptdisks_start' program ..") context.execute('cryptdisks_start', target, sudo=True) else: logger.debug("Emulating `cryptdisks_start' functionality (program not installed) ..") for entry in parse_crypttab(context=context): if entry.target == target and 'luks' in entry.options: logger.debug("Matched /etc/crypttab entry: %s", entry) if entry.is_unlocked: logger.debug("Encrypted filesystem is already unlocked, doing nothing ..") else: unlock_filesystem(context=context, device_file=entry.source_device, key_file=entry.key_file, options=entry.options, target=entry.target) break else: msg = "Encrypted filesystem not listed in /etc/crypttab! (%r)" raise ValueError(msg % target)
def find_managed_drives(keys_directory): """ Find the encrypted drives managed by `crypto-drive-manager`. :param keys_directory: The mount point for the virtual keys device (a string). :returns: A generator of :class:`~linux_utils.crypttab.EncryptedFileSystemEntry` objects. """ for entry in parse_crypttab(): if ('luks' in entry.options and entry.key_file and match_prefix(entry.key_file, keys_directory)): yield entry
def crypttab_entry(self): """ The entry in ``/etc/crypttab`` corresponding to :attr:`crypto_device`. The value of this property is computed automatically by parsing ``/etc/crypttab`` and looking for an entry whose `target` (the first of the four fields) matches :attr:`crypto_device`. When an entry is found an :class:`~linux_utils.crypttab.EncryptedFileSystemEntry` object is constructed, otherwise the result is :data:`None`. """ if self.crypto_device: logger.debug( "Parsing /etc/crypttab to determine device file of encrypted filesystem %r ..", self.crypto_device) for entry in parse_crypttab(context=self.destination_context): if entry.target == self.crypto_device: return entry
def test_parse_crypttab(self): """Test the ``/etc/crypttab`` parsing.""" source = 'UUID=36c44011-999d-4e4d-98cd-43e169b839e7' key_file = '/root/keys/backups.key' options = ['luks', 'discard', 'noauto'] fake_entry = [TEST_UNKNOWN_TARGET, source, key_file, ','.join(options)] with tempfile.NamedTemporaryFile() as temporary_file: # Create a fake /etc/crypttab file with a valid entry. temporary_file.write((' '.join(fake_entry) + '\n').encode('ascii')) # Also add a corrupt entry to the file. temporary_file.write('oops!\n'.encode('ascii')) # Make sure the contents are on disk. temporary_file.flush() # Parse the file. entries = list(parse_crypttab(filename=temporary_file.name)) # Check the results. assert len(entries) == 1 assert entries[0].is_available is False assert entries[0].is_unlocked is False assert entries[0].key_file == key_file assert entries[0].options == options assert entries[0].source == source assert entries[0].source_device.startswith('/dev/disk/by-uuid/') assert entries[0].target == TEST_UNKNOWN_TARGET