This adds support for partitioning with preexisting encrypted devices. The basic idea is that we scan for encrypted devices before reading the initial partition layout in partitions.setFromDisk. We prompt for passphrases for encrypted devic es we find. Those devices for which we obtain a valid passphrase get the encrypt ion data added to the RequestSpec. The information is then used by the partition ing interface so users can create filesytems on these preexisting encrypted devi ces. They can also remove the encryption from said devices, provided they are wi lling to reformat the filesystem. It is also possible to do one edit of a partit ion to delete the LUKS header and then, on a subsequent edit, create a new LUKS header on the same device.
The main thing that needs work IMO is some dependency resolution to provide orde ring for device access. Currently we just loop through opening all encrypted dev ices twice -- once before activating RAID and LVM, and once afterward. Ideally, we could see which devices need RAID and/or LVM to be active before we can try t o access them. The hammer seems to do the job in the meantime.
Comments are welcome, of course.
--- cryptodev.py | 23 +++++++++++++++++++++++ 1 files changed, 23 insertions(+), 0 deletions(-)
diff --git a/cryptodev.py b/cryptodev.py index 080d198..a651625 100644 --- a/cryptodev.py +++ b/cryptodev.py @@ -38,6 +38,17 @@ def isLuks(device): else: return True
+def luksUUID(device): + if not device.startswith("/"): + device = "/dev/" + device + + if not isLuks(device): + return None + + uuid = iutil.execWithCapture("cryptsetup", ["luksUUID", device]) + uuid = uuid.strip() + return uuid + class LUKSDevice: """LUKSDevice represents an encrypted block device using LUKS/dm-crypt. It requires an underlying block device and a passphrase to become @@ -46,6 +57,7 @@ class LUKSDevice: self._device = None self.passphrase = "" self.name = "" + self.uuid = None self.nameLocked = False self.format = format self.preexist = not format @@ -82,6 +94,17 @@ class LUKSDevice:
return dev
+ def getUUID(self): + if self.format: + # self.format means we're going to reformat but haven't yet + # so we shouldn't act like there's anything worth seeing there + return + + if not self.uuid: + self.uuid = luksUUID(self.getDevice(encrypted=1)) + + return self.uuid + def setName(self, name, lock=False): """Set the name of the mapped device, eg: 'dmcrypt-sda3'""" if self.name == name:
Scan for encrypted devices while reading the initial partition layout from disk. Prompt for passphrases and include encryption information in the RequestSpec. --- partitions.py | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 files changed, 83 insertions(+), 8 deletions(-)
diff --git a/partitions.py b/partitions.py index e3297f7..1f1e7b8 100644 --- a/partitions.py +++ b/partitions.py @@ -184,6 +184,12 @@ class Partitions: return self.protected
def getCryptoDev(self, device): + log.info("going to get passphrase for encrypted device %s" % device) + luksDev = self.encryptedDevices.get(device) + if luksDev: + log.debug("passphrase for device %s already known" % device) + return luksDev + intf = self.anaconda.intf luksDev = cryptodev.LUKSDevice(device) if self.globalPassphrase: @@ -275,19 +281,25 @@ class Partitions: if lvvg != vg: continue
- theDev = "/dev/%s/%s" %(vg, lv) + theDev = "/dev/mapper/%s-%s" %(vg, lv) if cryptodev.isLuks(theDev): - self.getCryptoDev("%s/%s" % (vg, lv)) + self.getCryptoDev("mapper/%s-%s" % (vg, lv))
lvm.vgdeactivate() diskset.stopMdRaid() for luksDev in self.encryptedDevices.values(): luksDev.closeDevice() + # try again now that encryption mappings are closed + lvm.vgdeactivate() + diskset.stopMdRaid() + for luksDev in self.encryptedDevices.values(): + luksDev.closeDevice()
def setFromDisk(self, diskset): """Clear the delete list and set self.requests to reflect disk.""" self.deletes = [] self.requests = [] + self.getEncryptedDevices(diskset) labels = diskset.getInfo() drives = diskset.disks.keys() drives.sort() @@ -316,7 +328,20 @@ class Partitions: # handling instead some day. if ptype is None: ptype = fsset.fileSystemTypeGet("foreign") - + + device = partedUtils.get_partition_name(part) + luksDev = self.encryptedDevices.get(device) + if luksDev and not luksDev.openDevice(): + mappedDev = luksDev.getDevice() + fsname = partedUtils.sniffFilesystemType("/dev/%s" % mappedDev) + if fsname == "lvm2pv": + ptype = fsset.fileSystemTypeGet("physical volume (LVM)") + else: + try: + ptype = fsset.fileSystemTypeGet(fsname) + except: + ptype = fsset.fileSystemTypeGet("foreign") + start = part.geom.start end = part.geom.end size = partedUtils.getPartSizeMB(part) @@ -329,6 +354,7 @@ class Partitions: drive = drive, format = format) spec.device = fsset.PartedPartitionDevice(part).getDevice() + spec.encryption = luksDev spec.maxResizeSize = partedUtils.getMaxAvailPartSizeMB(part)
# set label if makes sense @@ -336,7 +362,9 @@ class Partitions: if spec.device in labels.keys(): if labels[spec.device] and len(labels[spec.device])>0: spec.fslabel = labels[spec.device] - + elif luksDev and not luksDev.getStatus() and mappedDev in labels.keys(): + if labels[mappedDev] and len(labels[mappedDev])>0: + spec.fslabel = labels[mappedDev] self.addRequest(spec) part = disk.next_partition(part)
@@ -372,14 +400,20 @@ class Partitions: raidvols.append(req.uniqueID)
- fs = partedUtils.sniffFilesystemType("/dev/%s" %(theDev,)) + luksDev = self.encryptedDevices.get(theDev) + if luksDev and not luksDev.openDevice(): + device = luksDev.getDevice() + else: + device = theDev + + fs = partedUtils.sniffFilesystemType("/dev/%s" %(device,)) try: fsystem = fsset.fileSystemTypeGet(fs) except: fsystem = fsset.fileSystemTypeGet("foreign")
try: - fslabel = isys.readFSLabel(theDev) + fslabel = isys.readFSLabel(device) except: fslabel = None
@@ -397,6 +431,7 @@ class Partitions: chunksize = chunk, fslabel = fslabel) spec.size = spec.getActualSize(self, diskset) + spec.encryption = luksDev self.addRequest(spec)
lvm.writeForceConf() @@ -441,7 +476,14 @@ class Partitions: lvsize = float(size)
theDev = "/dev/%s/%s" %(vg, lv) - fs = partedUtils.sniffFilesystemType(theDev) + + luksDev = self.encryptedDevices.get("mapper/%s-%s" % (vg, lv)) + if luksDev and not luksDev.openDevice(): + device = luksDev.getDevice() + else: + device = theDev + + fs = partedUtils.sniffFilesystemType(device) fslabel = None
try: @@ -450,7 +492,7 @@ class Partitions: fsystem = fsset.fileSystemTypeGet("foreign")
try: - fslabel = isys.readFSLabel(theDev) + fslabel = isys.readFSLabel(device) except: fslabel = None
@@ -463,6 +505,7 @@ class Partitions: preexist = 1) if fsystem.isResizable(): spec.minResizeSize = fsystem.getMinimumSize("%s/%s" %(vg, lv)) + spec.encryption = luksDev self.addRequest(spec)
for vg in lvm.partialvgs(): @@ -470,8 +513,15 @@ class Partitions: self.addDelete(spec)
lvm.vgdeactivate() + diskset.stopMdRaid() + for luksDev in self.encryptedDevices.values(): + luksDev.closeDevice()
+ # try again now that encryption mappings are closed + lvm.vgdeactivate() diskset.stopMdRaid() + for luksDev in self.encryptedDevices.values(): + luksDev.closeDevice()
def addRequest (self, request): """Add a new request to the list.""" @@ -514,6 +564,13 @@ class Partitions: if tmp == device: return request elif request.device == device: + return request + elif request.encryption: + deviceUUID = cryptodev.luksUUID("/dev/" + device) + cryptoDev = request.encryption.getDevice() + cryptoUUID = request.encryption.getUUID() + if cryptoDev == device or \ + (cryptoUUID and cryptoUUID == deviceUUID): return request return None
@@ -1533,6 +1590,8 @@ class Partitions: diskset.startMPath() diskset.startDmRaid() diskset.startMdRaid() + for luksDev in self.encryptedDevices.values(): + luksDev.openDevice() lvm.vgactivate()
snapshots = {} @@ -1561,6 +1620,10 @@ class Partitions:
for name,vg in lvm_parent_deletes: log.info("removing lv %s" % (name,)) + key = "mapper/%s-%s" % (vg, name) + if key in self.encryptedDevices.keys(): + self.encryptedDevices[].closeDevice() + del self.encryptedDevices[key] lvm.lvremove(name, vg)
# now, go through and delete volume groups @@ -1571,6 +1634,18 @@ class Partitions: delete.setDeleted(1)
lvm.vgdeactivate() + + # now, remove obsolete cryptodev instances + for (device, luksDev) in self.encryptedDevices.items(): + luksDev.closeDevice() + found = 0 + for req in self.requests: + if req.encryption == luksDev: + found = 1 + + if not found: + del self.encryptedDevices[device] + diskset.stopMdRaid()
def doMetaResizes(self, diskset):
Use of the /dev/mapper/VolGroupXX-LogVolXX node allows us to rely on the basename being unique. --- cryptodev.py | 5 +---- partitions.py | 5 +++-- 2 files changed, 4 insertions(+), 6 deletions(-)
diff --git a/cryptodev.py b/cryptodev.py index a651625..47ae350 100644 --- a/cryptodev.py +++ b/cryptodev.py @@ -79,11 +79,8 @@ class LUKSDevice:
self._device = device if device is not None: - if device.startswith("/dev/"): - device = device[5:] - name = "%s-%s" % (self.scheme.lower(), - device.replace("/", "-")) + os.path.basename(device)) self.setName(name)
def getDevice(self, encrypted=0): diff --git a/partitions.py b/partitions.py index 1f1e7b8..a9bf132 100644 --- a/partitions.py +++ b/partitions.py @@ -204,8 +204,9 @@ class Partitions: return
buttons = [_("Back"), _("Continue")] + devname = os.path.basename(device) while True: - (passphrase, isglobal) = intf.passphraseEntryWindow(device) + (passphrase, isglobal) = intf.passphraseEntryWindow(devname) if not passphrase: rc = intf.messageWindow(_("Confirm"), _("Are you sure you want to skip " @@ -214,7 +215,7 @@ class Partitions: "If you skip this step the " "device's contents will not " "be available during " - "installation.") % device, + "installation.") % devname, type = "custom", default = 0, custom_buttons = buttons)
Add another pass at opening encrypted devices after starting RAID and LVM so that we can see what's on encrypted high-level devices. Also look for the correct key in the encryptedDevices dict when checking for encryption info. --- partedUtils.py | 8 +++++++- 1 files changed, 7 insertions(+), 1 deletions(-)
diff --git a/partedUtils.py b/partedUtils.py index 4c050ef..6cb4830 100644 --- a/partedUtils.py +++ b/partedUtils.py @@ -809,6 +809,11 @@ class DiskSet: lvm.vgscan() lvm.vgactivate()
+ for dev, crypto in self.anaconda.id.partitions.encryptedDevices.items(): + # FIXME: order these so LVM and RAID always work on the first try + if crypto.openDevice(): + log.error("failed to open encrypted device %s" % (dev,)) + for (vg, lv, size, lvorigin) in lvm.lvlist(): if lvorigin: continue @@ -816,7 +821,8 @@ class DiskSet: found = 0 theDev = dev node = "%s/%s" % (vg, lv) - crypto = self.anaconda.id.partitions.encryptedDevices.get(node) + dmnode = "mapper/%s-%s" % (vg, lv) + crypto = self.anaconda.id.partitions.encryptedDevices.get(dmnode) if crypto and not crypto.openDevice(): theDev = "/dev/%s" % (crypto.getDevice(),) elif crypto:
Basic rules are the same for partitions, LVs, RAID devices:
- Existing LUKS headers can only be removed if the device is being formatted. - You can create a new filesystem on the device and retain the preexisting LUKS header. - You can add a LUKS header to a non-encrypted preexisting device only if creating a new filesystem on the device. - We prompt for passphrase only for non-preexisting LUKS headers, meaning those that existed when we read the disk layout initially. We do not support adding or changing passphrases for preexisting encrypted devices. --- iw/lvm_dialog_gui.py | 8 ++++++-- iw/partition_dialog_gui.py | 32 ++++++++++++++++---------------- iw/partition_ui_helpers_gui.py | 7 ++++--- iw/raid_dialog_gui.py | 23 ++++++++++++++--------- ui/lukspassphrase.glade | 4 ++-- 5 files changed, 42 insertions(+), 32 deletions(-)
diff --git a/iw/lvm_dialog_gui.py b/iw/lvm_dialog_gui.py index 8bbb4a5..84e83a9 100644 --- a/iw/lvm_dialog_gui.py +++ b/iw/lvm_dialog_gui.py @@ -615,6 +615,7 @@ class VolumeGroupEditor:
# create potential request request = copy.copy(logrequest) + request.encryption = copy.deepcopy(logrequest.encryption) pesize = int(self.peCombo.get_active_value()) size = lvm.clampLVSizeRequest(size, pesize, roundup=1)
@@ -659,11 +660,14 @@ class VolumeGroupEditor: else: passphrase = ""
- passphrase = self.intf.getLuksPassphrase(passphrase) + if not request.encryption or request.encryption.format: + passphrase = self.intf.getLuksPassphrase(passphrase)
- if passphrase: + if passphrase and not request.encryption: request.encryption = LUKSDevice(passphrase=passphrase, format=1) + elif passphrase and request.encryption.format: + request.encryption.setPassphrase(passphrase) else: request.encryption = None
diff --git a/iw/partition_dialog_gui.py b/iw/partition_dialog_gui.py index a38448e..7e1c8e1 100644 --- a/iw/partition_dialog_gui.py +++ b/iw/partition_dialog_gui.py @@ -141,13 +141,15 @@ class PartitionEditor: passphrase = request.encryption.passphrase else: passphrase = "" - passphrase = self.intf.getLuksPassphrase(passphrase) + + if not request.encryption or request.encryption.format: + passphrase = self.intf.getLuksPassphrase(passphrase) + if passphrase and not request.encryption: request.encryption = LUKSDevice(passphrase=passphrase, format=1) - elif passphrase: + elif passphrase and request.encryption.format: request.encryption.setPassphrase(passphrase) - request.encryption.format = 1 else: request.encryption = None
@@ -212,6 +214,7 @@ class PartitionEditor: else: # preexisting partition, just set mount point and format flag request = copy.copy(self.origrequest) + request.encryption = copy.deepcopy(self.origrequest.encryption) if self.fsoptionsDict.has_key("formatcb"): request.format = self.fsoptionsDict["formatcb"].get_active() @@ -243,23 +246,21 @@ class PartitionEditor: else: request.mountpoint = None
- if self.fsoptionsDict.has_key("lukscb"): - lukscb = self.fsoptionsDict["lukscb"] - else: - lukscb = None - - if request.format and lukscb and lukscb.get_active(): + lukscb = self.fsoptionsDict.get("lukscb") + if lukscb and lukscb.get_active(): if request.encryption: passphrase = request.encryption.passphrase else: passphrase = "" - passphrase = self.intf.getLuksPassphrase(passphrase) + + if not request.encryption or request.encryption.format: + passphrase = self.intf.getLuksPassphrase(passphrase) + if passphrase and not request.encryption: request.encryption = LUKSDevice(passphrase=passphrase, format=1) - elif passphrase: + elif passphrase and request.encryption.format: request.encryption.setPassphrase(passphrase) - request.encryption.format = 1 else: request.encryption = None
@@ -331,8 +332,6 @@ class PartitionEditor: lbl = createAlignedLabel(_("File System _Type:")) maintable.attach(lbl, 0, 1, row, row + 1)
- self.lukscb = gtk.CheckButton(_("_Encrypt")) - self.lukscb.set_data("formatstate", 1) self.newfstypeCombo = createFSTypeMenu(self.origrequest.fstype, fstypechangeCB, self.mountCombo, @@ -485,14 +484,15 @@ class PartitionEditor:
# checkbutton for encryption using dm-crypt/LUKS if self.origrequest.type == REQUEST_NEW: + self.lukscb = gtk.CheckButton(_("_Encrypt")) + self.lukscb.set_data("formatstate", 1) + if self.origrequest.encryption: self.lukscb.set_active(1) else: self.lukscb.set_active(0) maintable.attach(self.lukscb, 0, 2, row, row + 1) row = row + 1 - else: - self.lukscb = None
# put main table into dialog self.dialog.vbox.pack_start(maintable) diff --git a/iw/partition_ui_helpers_gui.py b/iw/partition_ui_helpers_gui.py index 210c939..bc59604 100644 --- a/iw/partition_ui_helpers_gui.py +++ b/iw/partition_ui_helpers_gui.py @@ -240,7 +240,8 @@ def formatOptionCB(widget, data): if lukscb is not None: lukscb.set_data("formatstate", widget.get_active()) if not widget.get_active(): - lukscb.set_active(0) + # set "Encrypt" checkbutton to match partition's initial state + lukscb.set_active(lukscb.get_data("encrypted")) lukscb.set_sensitive(0) else: lukscb.set_sensitive(1) @@ -354,9 +355,9 @@ def createPreExistFSOptionSection(origrequest, maintable, row, mountCombo,
formatcb.connect("toggled", formatOptionResizeCB, resizesb)
- if origrequest.encryption and formatcb.get_active(): - # probably never happen + if origrequest.encryption: lukscb.set_active(1) + lukscb.set_data("encrypted", 1)
lukscb.set_sensitive(formatcb.get_active()) lukscb.set_data("formatstate", formatcb.get_active()) diff --git a/iw/raid_dialog_gui.py b/iw/raid_dialog_gui.py index a30624e..d62e368 100644 --- a/iw/raid_dialog_gui.py +++ b/iw/raid_dialog_gui.py @@ -146,6 +146,7 @@ class RaidEditor:
# read out UI into a partition specification request = copy.copy(self.origrequest) + request.encryption = copy.deepcopy(self.origrequest.encryption)
# doesn't make sense for RAID device if not self.origrequest.getPreExisting(): @@ -193,13 +194,15 @@ class RaidEditor: passphrase = request.encryption.passphrase else: passphrase = "" - passphrase = self.intf.getLuksPassphrase(passphrase) + + if not request.encryption or request.encryption.format: + passphrase = self.intf.getLuksPassphrase(passphrase) + if passphrase and not request.encryption: request.encryption = LUKSDevice(passphrase=passphrase, format=1) - elif passphrase: - request.encryption.setPassphrase(passphrase) - request.encryption.format = 1 + elif passphrase and request.encryption.format: + request.setPassphrase(passphrase) else: request.encryption = None else: @@ -228,18 +231,20 @@ class RaidEditor: request.mountpoint = None
lukscb = self.fsoptionsDict.get("lukscb") - if request.format and lukscb and lukscb.get_active(): + if lukscb and lukscb.get_active(): if request.encryption: passphrase = request.encryption.passphrase else: passphrase = "" - passphrase = self.intf.getLuksPassphrase(passphrase) + + if not request.encryption or request.encryption.format: + passphrase = self.intf.getLuksPassphrase(passphrase) + if passphrase and not request.encryption: request.encryption = LUKSDevice(passphrase=passphrase, format=1) - elif passphrase: - request.encryption.setPassphrase(passphrase) - request.encryption.format = 1 + elif passphrase and request.encryption.format: + request.setPassphrase(passphrase) else: request.encryption = None
diff --git a/ui/lukspassphrase.glade b/ui/lukspassphrase.glade index 7daee20..cb76e6b 100644 --- a/ui/lukspassphrase.glade +++ b/ui/lukspassphrase.glade @@ -223,8 +223,8 @@ <widget class="GtkDialog" id="passphraseEntryDialog"> <property name="visible">True</property> <property name="title" translatable="yes">Passphrase</property> - <property name="type">GTK_WINDOW_POPUP</property> - <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property> + <property name="type">GTK_WINDOW_TOPLEVEL</property> + <property name="window_position">GTK_WIN_POS_CENTER</property> <property name="modal">True</property> <property name="resizable">True</property> <property name="destroy_with_parent">False</property>
On Wed, 2008-03-26 at 14:14 -0500, David Lehman wrote:
fsname = partedUtils.sniffFilesystemType("/dev/%s" % mappedDev)
if fsname == "lvm2pv":
ptype = fsset.fileSystemTypeGet("physical volume (LVM)")
We should do this mapping in isys/isys.py:readFSType() instead of scattering it through the code. And then potentially at some point (not for F9!) change our name to be lvm2pv.
Otherwise, the patches look okay. We'll want to be sure to do some good testing to make sure there aren't any new problems lingering once things go in.
Jeremy
anaconda-devel@lists.stg.fedoraproject.org