summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Lehman <dlehman@redhat.com>2012-10-05 12:19:08 -0500
committerDavid Lehman <dlehman@redhat.com>2012-10-08 16:36:06 -0500
commit49fe0f93dc0c1f044d9c96f3f6a91d332d64cfe5 (patch)
tree5838f226f8b75dc980b85c029c35a1e166949e50
parent6322810b886a4f3f049912ed3ca7b5b40cdbca8c (diff)
downloadanaconda-49fe0f93dc0c1f044d9c96f3f6a91d332d64cfe5.tar.gz
anaconda-49fe0f93dc0c1f044d9c96f3f6a91d332d64cfe5.tar.xz
anaconda-49fe0f93dc0c1f044d9c96f3f6a91d332d64cfe5.zip
Add support to the custom spoke for encrypted block devices.
-rw-r--r--pyanaconda/ui/gui/spokes/custom.py223
-rw-r--r--pyanaconda/ui/gui/spokes/storage.py6
2 files changed, 192 insertions, 37 deletions
diff --git a/pyanaconda/ui/gui/spokes/custom.py b/pyanaconda/ui/gui/spokes/custom.py
index 6afe98540..d34f1581b 100644
--- a/pyanaconda/ui/gui/spokes/custom.py
+++ b/pyanaconda/ui/gui/spokes/custom.py
@@ -48,18 +48,22 @@ from pyanaconda.storage.formats import device_formats
from pyanaconda.storage.formats import getFormat
from pyanaconda.storage.size import Size
from pyanaconda.storage import Root
+from pyanaconda.storage import findExistingInstallations
from pyanaconda.storage.partitioning import doPartitioning
from pyanaconda.storage.partitioning import doAutoPartition
from pyanaconda.storage.errors import StorageError
from pyanaconda.storage.errors import NoDisksError
from pyanaconda.storage.errors import NotEnoughFreeSpaceError
from pyanaconda.storage.errors import ErrorRecoveryFailure
+from pyanaconda.storage.errors import CryptoError
from pyanaconda.storage.devicelibs import mdraid
+from pyanaconda.storage.devices import LUKSDevice
from pyanaconda.ui.gui import GUIObject
from pyanaconda.ui.gui.spokes import NormalSpoke
from pyanaconda.ui.gui.spokes.storage import StorageChecker
from pyanaconda.ui.gui.spokes.lib.cart import SelectedDisksDialog
+from pyanaconda.ui.gui.spokes.lib.passphrase import PassphraseDialog
from pyanaconda.ui.gui.spokes.lib.accordion import *
from pyanaconda.ui.gui.utils import enlightbox, setViewportBackground
from pyanaconda.ui.gui.categories.storage import StorageCategory
@@ -73,6 +77,7 @@ __all__ = ["CustomPartitioningSpoke"]
NOTEBOOK_LABEL_PAGE = 0
NOTEBOOK_DETAILS_PAGE = 1
+NOTEBOOK_LUKS_PAGE = 2
new_install_name = _("New %s %s Installation") % (productName, productVersion)
unrecoverable_error_msg = _("Storage configuration reset due to unrecoverable "
@@ -273,7 +278,14 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
""" Register actions from the UI with the main Storage instance. """
for ui_action in actions:
ui_device = ui_action.device
- device = self.storage.devicetree.getDeviceByID(ui_device.id)
+ if isinstance(ui_device, LUKSDevice) and ui_device.exists:
+ # LUKS device that was unlocked during this spoke visit
+ # The ids won't match in this case because we instantiated the
+ # LUKSDevice both in the spoke's devicetree and in the main one.
+ device = self.storage.devicetree.getDeviceByName(ui_device.name)
+ else:
+ device = self.storage.devicetree.getDeviceByID(ui_device.id)
+
if device is None:
# This device does not exist in the main devicetree.
#
@@ -325,6 +337,31 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
for action in ui_devicetree.findActions():
log.debug("%s" % action)
+ # Find any LUKS devices that have been unlocked in this visit to the
+ # custom spoke and find their descendant devices before we do anything
+ # else.
+ ui_luks_devices = [d for d in self._devices
+ if isinstance(d, LUKSDevice) and
+ d.exists and not
+ self.storage.devicetree.getDeviceByID(d.id)]
+ for ui_luks_device in ui_luks_devices:
+ if ui_luks_device not in self._devices:
+ # since removed
+ continue
+
+ ui_slave = ui_luks_device.slave
+ slave = self.storage.devicetree.getDeviceByID(ui_slave.id)
+ slave.format.passphrase = ui_slave.format._LUKS__passphrase
+ luks_device = LUKSDevice(slave.format.mapName,
+ parents=[slave],
+ exists=True)
+ luks_device.setup()
+ self.storage.devicetree._addDevice(luks_device)
+
+ # XXX What if the user has changed storage config in the shell?
+ if ui_luks_devices:
+ self.storage.devicetree.populate()
+
# schedule actions for device removals, resizes
actions = ui_devicetree.findActions(type="destroy")
partition_destroys = [a for a in actions
@@ -396,6 +433,13 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
ui_device.size == ui_device.currentSize:
self.storage.resetDevice(device)
+ # apply global passphrase to all new encrypted devices
+ self.data.autopart.passphrase = self.passphrase
+ for device in self.storage.devices:
+ if device.format.type == "luks" and not device.format.exists:
+ log.debug("using global passphrase for %s" % device.name)
+ device.format.passphrase = self.data.autopart.passphrase
+
# set up bootloader and check the configuration
self.storage.setUpBootLoader()
StorageChecker.run(self)
@@ -561,6 +605,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
if t:
t.join()
+ self.passphrase = self.data.autopart.passphrase
self._reset_storage()
self._do_refresh()
# update our free space number based on Storage
@@ -590,7 +635,8 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
# Now it's time to populate the accordion.
# A device scheduled for formatting only belongs in the new root.
- new_devices = [d for d in self._devices if not d.format.exists and
+ new_devices = [d for d in self._devices if d.isleaf and
+ not d.format.exists and
not d.partitioned]
# If mountpoints have been assigned to any existing devices, go ahead
@@ -800,7 +846,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
log.info("ui: saving changes to device %s" % device.name)
- # TODO: encryption, raid, member type
+ # TODO: member type, disk set
size = self.builder.get_object("sizeSpinner").get_value()
log.debug("new size: %s" % size)
@@ -819,10 +865,11 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
fs_type = fs_type_combo.get_model()[fs_type_index][0]
log.debug("new fs type: %s" % fs_type)
+ prev_encrypted = device.encrypted
+ log.debug("old encryption setting: %s" % prev_encrypted)
encrypted = self.builder.get_object("encryptCheckbox").get_active()
log.debug("new encryption setting: %s" % encrypted)
- # TODO: get disks
label = self.builder.get_object("labelEntry").get_text()
old_label = getattr(device.format, "label", "") or ""
@@ -845,9 +892,9 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
with ui_storage_logger():
# create a new factory using the appropriate size and type
- # XXX ignoring encryption for now
factory = self.__storage.getDeviceFactory(device_type, size,
disks=device.disks,
+ encrypted=encrypted,
raid_level=raid_level)
# for raid settings, we'll need to adjust the member set and container,
@@ -863,8 +910,13 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
"lvmlv": AUTOPART_TYPE_LVM,
"btrfs subvolume": AUTOPART_TYPE_BTRFS,
"mdarray": None}
- current_device_type = device_types.get(device.type)
+ current_device_type = device.type
+ if current_device_type == "luks/dm-crypt":
+ current_device_type = device.slave.type
+ current_device_type = device_types.get(current_device_type)
if current_device_type != device_type:
+ log.info("changing device type from %s to %s" % (current_device_type,
+ device_type))
# remove the current device
self.clear_errors()
root = self._current_selector._root
@@ -874,7 +926,6 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
return
with ui_storage_logger():
- # XXX skipping encryption for now
# Use any disks with space in addition to any disks used by
# a defined container.
disks = [d for d in self._clearpartDevices
@@ -887,6 +938,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
self._replace_device(device_type, size, fstype=fs_type,
disks=disks, mountpoint=mountpoint,
label=label, raid_level=raid_level,
+ encrypted=encrypted,
selector=selector)
except ErrorRecoveryFailure as e:
self._error = e
@@ -921,6 +973,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
mountpoint=old_mountpoint,
label=old_label,
raid_level=raid_level,
+ encrypted=encrypted,
selector=selector)
except StorageError as e:
# failed to recover.
@@ -998,6 +1051,37 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
self._updateSpaceDisplay()
##
+ ## ENCRYPTION
+ ##
+ old_device = device
+ if prev_encrypted and not encrypted:
+ log.info("removing encryption from %s" % device.name)
+ with ui_storage_logger():
+ self.__storage.destroyDevice(device)
+ self._devices.remove(device)
+ old_device = device
+ device = device.slave
+ selector._device = device
+ for s in self._accordion.allSelectors:
+ if s._device == old_device:
+ s._device = device
+ elif encrypted and not prev_encrypted:
+ log.info("applying encryption to %s" % device.name)
+ with ui_storage_logger():
+ old_device = device
+ new_fmt = getFormat("luks", device=device.path)
+ self.__storage.formatDevice(device, new_fmt)
+ luks_dev = LUKSDevice("luks-" + device.name,
+ parents=[device])
+ self.__storage.createDevice(luks_dev)
+ self._devices.append(luks_dev)
+ device = luks_dev
+ selector._device = device
+ for s in self._accordion.allSelectors:
+ if s._device == old_device:
+ s._device = device
+
+ ##
## FORMATTING
##
if fs_type != device.format.name:
@@ -1027,7 +1111,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
# it to the new page
for page in self._accordion.allPages:
for _selector in getattr(page, "_members", []):
- if _selector._device == device:
+ if _selector._device in (device, old_device):
page.removeSelector(_selector)
if not page._members:
log.debug("removing empty page %s" % page.pageTitle)
@@ -1037,9 +1121,6 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
return
- # for encryption, I'm not sure if we'll want to modify the
- # container/device or just create a new one
-
##
## FORMATTING ATTRIBUTES
##
@@ -1208,6 +1289,10 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
fsCombo = self.builder.get_object("fileSystemTypeCombo")
device = selector._device
+ if device.type == "luks/dm-crypt":
+ use_dev = device.slave
+ else:
+ use_dev = device
selectedDeviceLabel.set_text(selector.props.name)
selectedDeviceDescLabel.set_text(self._description(selector.props.name))
@@ -1222,7 +1307,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
not device.format.exists)
labelEntry.set_sensitive(can_label)
- if labelEntry.get_sensitive():
+ if hasattr(device.format, "label"):
labelEntry.props.has_tooltip = False
else:
labelEntry.set_tooltip_text(_("This file system does not support labels."))
@@ -1248,8 +1333,8 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
encryptCheckbox.set_active(device.encrypted)
# if the format is swap the device type can't be btrfs
- include_btrfs = device.format.type not in ("swap", "prepboot",
- "biosboot")
+ include_btrfs = use_dev.format.type not in ("swap", "prepboot",
+ "biosboot")
# btrfs has to be the last type in the list
btrfs_included = typeCombo.get_model()[-1][0] == "BTRFS"
if include_btrfs and not btrfs_included:
@@ -1269,20 +1354,20 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
# FIXME: What do we do if we can't figure it out?
raid_level = None
- if device.type == "lvmlv":
+ if use_dev.type == "lvmlv":
# TODO: striping/mirroring
typeCombo.set_active(DEVICE_TYPE_LVM)
- elif device.type == "mdarray":
+ elif use_dev.type == "mdarray":
typeCombo.set_active(DEVICE_TYPE_MD)
- raid_level = mdraid.raidLevelString(device.level)
- elif device.type == "partition":
+ raid_level = mdraid.raidLevelString(use_dev.level)
+ elif use_dev.type == "partition":
typeCombo.set_active(DEVICE_TYPE_PARTITION)
- elif device.type.startswith("btrfs"):
+ elif use_dev.type.startswith("btrfs"):
typeCombo.set_active(DEVICE_TYPE_BTRFS)
- if hasattr(device, "volume"):
- raid_level = device.volume.dataLevel or "single"
+ if hasattr(use_dev, "volume"):
+ raid_level = use_dev.volume.dataLevel or "single"
else:
- raid_level = device.dataLevel or "single"
+ raid_level = use_dev.dataLevel or "single"
# you can't change the type of an existing device
typeCombo.set_sensitive(not device.exists)
@@ -1309,6 +1394,21 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
# to the install summary screen.
def on_finish_clicked(self, button):
self._save_right_side(self._current_selector)
+
+ new_luks = any([d for d in self.__storage.devices
+ if d.format.type == "luks" and
+ not d.format.exists])
+ if new_luks:
+ dialog = PassphraseDialog(self.data)
+ with enlightbox(self.window, dialog.window):
+ rc = dialog.run()
+
+ if rc == 0:
+ # Cancel. Leave the old passphrase set if there was one.
+ return
+
+ self.passphrase = dialog.passphrase
+
NormalSpoke.on_back_clicked(self, button)
def on_add_clicked(self, button):
@@ -1432,16 +1532,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
log.debug("show first mountpoint: %s" % getattr(page, "pageTitle", None))
if getattr(page, "_members", []):
- log.debug("page %s has %d members" % (page.pageTitle, len(page._members)))
- # Make sure we're showing details instead of the "here's how you create
- # a new OS" label.
- if self._current_selector:
- self._current_selector.set_chosen(False)
- self._partitionsNotebook.set_current_page(NOTEBOOK_DETAILS_PAGE)
- self._current_selector = page._members[0]
- self._current_selector.set_chosen(True)
- self._populate_right_side(page._members[0])
- page._members[0].grab_focus()
+ self.on_selector_clicked(page._members[0])
else:
self._current_selector = None
@@ -1510,6 +1601,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
# Now that devices have been removed from the installation root,
# refreshing the display will have the effect of making them disappear.
# It's like they never existed.
+ self._unused_devices = None # why do we cache this?
self._do_refresh()
self._updateSpaceDisplay()
self._show_first_mountpoint()
@@ -1529,17 +1621,34 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
if not self._initialized:
return
- # Make sure we're showing details instead of the "here's how you create
- # a new OS" label.
- self._partitionsNotebook.set_current_page(NOTEBOOK_DETAILS_PAGE)
-
# Take care of the previously chosen selector.
if self._current_selector and self._initialized:
log.debug("current selector: %s" % self._current_selector._device)
log.debug("new selector: %s" % selector._device)
- self._save_right_side(self._current_selector)
+ nb_page = self._partitionsNotebook.get_current_page()
+ log.debug("notebook page = %s" % nb_page)
+ if nb_page != NOTEBOOK_LUKS_PAGE:
+ self._save_right_side(self._current_selector)
+
self._current_selector.set_chosen(False)
+ if selector._device.format.type == "luks" and \
+ selector._device.format.exists:
+ self._partitionsNotebook.set_current_page(NOTEBOOK_LUKS_PAGE)
+ selectedDeviceLabel = self.builder.get_object("encryptedDeviceLabel")
+ selectedDeviceDescLabel = self.builder.get_object("encryptedDeviceDescriptionLabel")
+ selectedDeviceLabel.set_text(selector.props.name)
+ selectedDeviceDescLabel.set_text(self._description(selector.props.name))
+ selector.set_chosen(True)
+ self._current_selector = selector
+ self._configButton.set_sensitive(False)
+ self._removeButton.set_sensitive(True)
+ return
+
+ # Make sure we're showing details instead of the "here's how you create
+ # a new OS" label.
+ self._partitionsNotebook.set_current_page(NOTEBOOK_DETAILS_PAGE)
+
# Set up the newly chosen selector.
self._populate_right_side(selector)
selector.set_chosen(True)
@@ -1710,3 +1819,43 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
def on_apply_clicked(self, button):
""" call _save_right_side, then, perhaps, populate_right_side. """
self._save_right_side(self._current_selector)
+
+ def on_unlock_clicked(self, button):
+ """ try to open the luks device, populate, then call _do_refresh. """
+ self.clear_errors()
+ device = self._current_selector._device
+ log.info("trying to unlock %s..." % device.name)
+ entry = self.builder.get_object("passphraseEntry")
+ passphrase = entry.get_text()
+ device.format.passphrase = passphrase
+ try:
+ device.format.setup()
+ except CryptoError as e:
+ log.error("failed to unlock %s: %s" % (device.name, e))
+ self._error = e
+ device.format.passphrase = None
+ entry.set_text("")
+ self.window.set_info(Gtk.MessageType.WARNING,
+ _("Failed to unlock encrypted block device. "
+ "Click for details"))
+ self.window.show_all()
+ return
+
+ log.info("unlocked %s, now going to populate devicetree..." % device.name)
+ with ui_storage_logger():
+ luks_dev = LUKSDevice(device.format.mapName,
+ parents=[device],
+ exists=True)
+ self.__storage.devicetree._addDevice(luks_dev)
+ # save the passphrase for possible reset and to try for other devs
+ self.__storage._Storage__luksDevs[device.format.uuid] = passphrase
+ self.__storage.devicetree._DeviceTree__luksDevs[device.format.uuid] = passphrase
+ # XXX What if the user has changed things using the shell?
+ self.__storage.devicetree.populate()
+ # look for new roots
+ self.__storage.roots = findExistingInstallations(self.__storage.devicetree)
+
+ self._devices = self.__storage.devices
+ self._unused_devices = None # why do we cache this?
+ self._current_selector = None
+ self._do_refresh()
diff --git a/pyanaconda/ui/gui/spokes/storage.py b/pyanaconda/ui/gui/spokes/storage.py
index 34985170d..578e5774b 100644
--- a/pyanaconda/ui/gui/spokes/storage.py
+++ b/pyanaconda/ui/gui/spokes/storage.py
@@ -331,6 +331,8 @@ class StorageSpoke(NormalSpoke, StorageChecker):
self.data.ignoredisk.onlyuse = self.selected_disks[:]
self.data.clearpart.drives = self.selected_disks[:]
self.data.autopart.autopart = self.autopart
+ self.data.autopart.encrypted = self.encrypted
+ self.data.autopart.passphrase = self.passphrase
# no thanks, lvm
self.data.autopart.type = AUTOPART_TYPE_PLAIN
@@ -351,6 +353,8 @@ class StorageSpoke(NormalSpoke, StorageChecker):
self.data.clearpart.type = self.clearPartType
self.storage.config.update(self.data)
self.storage.autoPartType = self.data.autopart.type
+ self.storage.encryptedAutoPart = self.data.autopart.encrypted
+ self.storage.encryptionPassphrase = self.data.autopart.passphrase
# If autopart is selected we want to remove whatever has been
# created/scheduled to make room for autopart.
@@ -437,6 +441,8 @@ class StorageSpoke(NormalSpoke, StorageChecker):
# synchronize our local data store with the global ksdata
self.selected_disks = self.data.ignoredisk.onlyuse[:]
self.autopart = self.data.autopart.autopart
+ self.encrypted = self.data.autopart.encrypted
+ self.passphrase = self.data.autopart.passphrase
# update the selections in the ui
overviews = self.local_disks_box.get_children()