diff --git a/debian/changelog b/debian/changelog index c74ab6c..c09e892 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,14 @@ +openmediavault-luksencryption (2.1.0) unstable; urgency=high + + * Tested on OMV 3.0. + * Activate LVM volume groups after unlocking for LVM-on-LUKS. + * Fix for duplicate entries in the case of, e.g. partitions on RAID devices. + * Refactor some code to avoid duplicated calls that led to typo bug in v2.0.0. + * Tweak description of devices/containers. + * Tweak filename for header backups. + + -- OpenMediaVault Plugin Developers Wed, 06 Jan 2016 14:53:13 +0000 + openmediavault-luksencryption (2.0.1) unstable; urgency=high * Bugfix: typo in createContainer() caused creating containers with passphrases to fail. diff --git a/usr/share/openmediavault/engined/rpc/luks.inc b/usr/share/openmediavault/engined/rpc/luks.inc index a57822e..96984ca 100644 --- a/usr/share/openmediavault/engined/rpc/luks.inc +++ b/usr/share/openmediavault/engined/rpc/luks.inc @@ -28,6 +28,7 @@ require_once("openmediavault/functions.inc"); require_once("openmediavault/luks.inc"); require_once("openmediavault/rpcservice.inc"); require_once("openmediavault/notify.inc"); +include_once("openmediavault/lvm.inc"); class OMVRpcServiceLuksMgmt extends OMVRpcServiceAbstract { @@ -276,94 +277,155 @@ class OMVRpcServiceLuksMgmt extends OMVRpcServiceAbstract { // Check that the container is not already open, then use // the supplied passphrase or key file to unlock it if not. if (FALSE === $luks->isOpen()) { - if(isset($params['keyfile']) && !empty($params['keyfile'])) - $success = $luks->open($params['keyfile'], TRUE); - else - $success = $luks->open($params['passphrase']); - if ($success === FALSE) { + if(isset($params['keyfile']) && !empty($params['keyfile'])) { + $key = $params['keyfile']; + $keyIsFile = TRUE; + } else { + $key = $params['passphrase']; + $keyIsFile = FALSE; + } + if ($luks->open($key, $keyIsFile) === FALSE) { throw new OMVException(OMVErrorMsg::E_EXEC_MISC, sprintf(gettext("Unable to unlock encrypted device: %s"), $luks->getLastError())); } } + /* Downstream operations with the decrypted container */ + $sdluks = new OMVStorageDeviceLUKS($luks->getDecryptedDeviceFile()); + $df = $sdluks->getDeviceFile(); + // If the container contains an LVM physical volume, determine the + // volume group and activate it (otherwise the logical volume and any + // filesystem on it won't be accessible) + if (class_exists("OMVLvmPhysicalVolume")) { + // Use LVM plugin if it's installed to get VG name + $pv = new OMVLvmPhysicalVolume($df); + if (TRUE === $pv->exists()) { + $vgName = $pv->getVGName(); + } + } else { + // Fall back to manual method without LVM plugin + $cmd = sprintf("export LANG=C; pvdisplay --noheadings ". + "-C -o vg_name %s ", + escapeshellarg($df)); + @OMVUtil::exec($cmd, $output, $result); + if($result === 0) { + $vgName = trim($output[0]); + } + } + // PV/VG was found - activate the VG, set devicefile to LV via + // LVM plugin if available - although LVM/udev seems to + // automatically mount filesystems in fstab from logical + // volumes when activated, so probably not strictly necessary. + if (isset($vgName)) { + $this->debug(sprintf("%s contains an LVM2 PV, part of VG: %s", + $df, $vgName)); + $cmd = sprintf("export LANG=C; vgchange -a y %s ", + escapeshellarg($vgName)); + @OMVUtil::exec($cmd, $output, $result); + if($result !== 0) { + $this->debug($output); + } else { + if (class_exists("OMVLvmVolumeGroup")) { + $vg = new OMVLvmVolumeGroup($vgName); + $lvNames = $vg->getLVName(); + $fsDevs = array_map(function($lv) use ($vgName) { + return "/dev/$vgName-$lv"; + }, $lvNames); + } + } + } else { + // If not an LVM2 PV, just pass the container devicefile + $fsDevs = array($df); + } // If the container contains a (referenced) filesystem, then mount it // (unless this automounting is disabled by the global configuration // option, OMV_LUKS_MOUNT_ON_UNLOCK - see initialize() above) if(TRUE === $this->mountOnUnlock) { - $sdluks = new OMVStorageDeviceLUKS($luks->getDecryptedDeviceFile()); - $df = $sdluks->getDeviceFile(); - if(FALSE !== OMVRpc::exec("FileSystemMgmt", "hasFilesystem", - array("devicefile" => $df), $context)) { - if(FALSE !== ($meObject = OMVRpc::exec("FsTab", "getByFsName", - array("id" => $df), $context))) { - switch (strtolower($meObject['type'])) { - case "btrfs": - /** - * Check if the unlocked device is part of a multi- - * device BTRFS filesystem, and don't attempt to mount - * it if it's not ready (not all devices are available, - * e.g. if more containers must be unlocked first, wait - * until they are all open). - * Note: using 'btrfs device ready' only works for the - * first time devices are opened - even if LUKS devices - * that are part of a multi-device BTRFS filesystem are - * later closed, some information is cached and so btrfs - * subsequently always reports the filesystem as ready. - * Hence, this workaround checks the number of devices - * online for the filesystem via 'btrfs filesystem show' - * instead, which should be more reliable. - * TODO: submit a patch to the core BTRFS backend that - * exposes this in a class function instead? - */ - // Find out how many devices are in the filesystem - $cmd = sprintf("export LANG=C; btrfs filesystem ". - "show %s | grep 'Total devices' | ". - "awk '{print $3}'", - escapeshellarg($meObject['uuid'])); - @OMVUtil::exec($cmd, $output, $result); - if($result !== 0) { - $this->setLastError($output); - continue; - } - $totalDevices = (int)$output[0]; - // If the fs has fewer than one device (an error - // of some kind occurred), skip mounting - if($totalDevices < 1) - continue; - unset($cmd, $output, $result); - // Find out how many are online - $cmd = sprintf("export LANG=C; btrfs filesystem ". - "show %s | grep 'devid' | wc -l", - escapeshellarg($meObject['uuid'])); - @OMVUtil::exec($cmd, $output, $result); - if($result !== 0) { - $this->setLastError($output); - continue; - } - $availableDevices = (int)$output[0]; - // If not all devices are online, skip mounting - if($availableDevices !== $totalDevices) - continue; - unset($cmd, $output, $result); - case "ext2": - case "ext3": - case "ext4": - case "jfs": - case "xfs": - case "hfsplus": - case "reiserfs": - case "iso9660": - case "udf": - case "vfat": - case "ntfs": - default: - OMVRpc::exec("FileSystemMgmt", "mount", - array( - "id" => $meObject['fsname'], - "fstab" => FALSE - ), - $context); + foreach ($fsDevs as $dev) { + $this->mountContainerFS($dev, $context); + } + } + } + + /** + * Helper function for mounting filesystems inside containers on unlocking. + * @param devicefile The decrypted block special device of the + * filesystem (inside the LUKS container) to mount. + * @param context The context of the caller. + * @return None. + */ + private function mountContainerFS($deviceFile, $context) { + if(FALSE !== OMVRpc::exec("FileSystemMgmt", "hasFilesystem", + array("devicefile" => $deviceFile), $context)) { + if(FALSE !== ($meObject = OMVRpc::exec("FsTab", "getByFsName", + array("id" => $deviceFile), $context))) { + switch (strtolower($meObject['type'])) { + case "btrfs": + /** + * Check if the unlocked device is part of a multi- + * device BTRFS filesystem, and don't attempt to mount + * it if it's not ready (not all devices are available, + * e.g. if more containers must be unlocked first, wait + * until they are all open). + * Note: using 'btrfs device ready' only works for the + * first time devices are opened - even if LUKS devices + * that are part of a multi-device BTRFS filesystem are + * later closed, some information is cached and so btrfs + * subsequently always reports the filesystem as ready. + * Hence, this workaround checks the number of devices + * online for the filesystem via 'btrfs filesystem show' + * instead, which should be more reliable. + * TODO: submit a patch to the core BTRFS backend that + * exposes this in a class function instead? + */ + // Find out how many devices are in the filesystem + $cmd = sprintf("export LANG=C; btrfs filesystem ". + "show %s | grep 'Total devices' | ". + "awk '{print $3}'", + escapeshellarg($meObject['uuid'])); + @OMVUtil::exec($cmd, $output, $result); + if($result !== 0) { + $this->setLastError($output); + continue; + } + $totalDevices = (int)$output[0]; + // If the fs has fewer than one device (an error + // of some kind occurred), skip mounting + if($totalDevices < 1) + continue; + unset($cmd, $output, $result); + // Find out how many are online + $cmd = sprintf("export LANG=C; btrfs filesystem ". + "show %s | grep 'devid' | wc -l", + escapeshellarg($meObject['uuid'])); + @OMVUtil::exec($cmd, $output, $result); + if($result !== 0) { + $this->setLastError($output); + continue; } + $availableDevices = (int)$output[0]; + // If not all devices are online, skip mounting + if($availableDevices !== $totalDevices) + continue; + unset($cmd, $output, $result); + case "ext2": + case "ext3": + case "ext4": + case "jfs": + case "xfs": + case "hfsplus": + case "reiserfs": + case "iso9660": + case "udf": + case "vfat": + case "ntfs": + default: + OMVRpc::exec("FileSystemMgmt", "mount", + array( + "id" => $meObject['fsname'], + "fstab" => FALSE + ), + $context); } } } @@ -482,14 +544,14 @@ class OMVRpcServiceLuksMgmt extends OMVRpcServiceAbstract { } // Create the container. $luks = new OMVLuksContainer($sd->getDeviceFile()); - if(isset($params['keyfile']) && !empty($params['keyfile'])) - $success = $luks->create($sd->getDeviceFile(), - $params['keyfile'], - TRUE); - else - $success = $luks->create($sd->getDeviceFile(), - $params['passphrase']); - if ($success === FALSE) { + if(isset($params['keyfile']) && !empty($params['keyfile'])) { + $key = $params['keyfile']; + $keyIsFile = TRUE; + } else { + $key = $params['passphrase']; + $keyIsFile = FALSE; + } + if ($luks->create($key, $keyIsFile) === FALSE) { throw new OMVException(OMVErrorMsg::E_EXEC_MISC, sprintf( gettext("Unable to create the encrypted device: %s"), $luks->getLastError())); @@ -747,11 +809,14 @@ class OMVRpcServiceLuksMgmt extends OMVRpcServiceAbstract { $params['devicefile'])); } // Remove the key - if(isset($params['keyfile']) && !empty($params['keyfile'])) - $success = $luks->removeKey($params['keyfile'], TRUE); - else - $success = $luks->removeKey($params['passphrase']); - if ($success === FALSE) { + if(isset($params['keyfile']) && !empty($params['keyfile'])) { + $key = $params['keyfile']; + $keyIsFile = TRUE; + } else { + $key = $params['passphrase']; + $keyIsFile = FALSE; + } + if ($luks->removeKey($key, $keyIsFile) === FALSE) { throw new OMVException(OMVErrorMsg::E_EXEC_MISC, sprintf(gettext("Unable to remove the key from the encrypted device: %s"), $luks->getLastError())); @@ -871,12 +936,12 @@ class OMVRpcServiceLuksMgmt extends OMVRpcServiceAbstract { "devicefile":{'.$GLOBALS['OMV_JSONSCHEMA_DEVICEFILE'].'} } }'); - // Check if container exists. + // Validate the container $luks = new OMVLuksContainer($params['devicefile']); - if (FALSE === $luks->exists()) { + if (is_null($luks) || !$luks->exists()) { throw new OMVException(OMVErrorMsg::E_MISC_FAILURE, - sprintf(gettext("No encryption found on '%s'"), - $params['devicefile'])); + sprintf(gettext("LUKS container on '%s' not found"), + $params['devicefile'])); } // Extract the header to a temporary location and modify the // file mode/owner to allow the WebGUI PHP backend to unlink it. @@ -892,9 +957,19 @@ class OMVRpcServiceLuksMgmt extends OMVRpcServiceAbstract { chmod($tmpFilePath, 0600); chgrp($tmpFilePath, $GLOBALS['OMV_WEBGUI_FILE_OWNERGROUP_NAME']); chown($tmpFilePath, $GLOBALS['OMV_WEBGUI_FILE_OWNERGROUP_NAME']); + // Build filename for file + $fn = preg_replace('/\s+/', + '-', + array( + $luks->getVendor(), + $luks->getModel(), + $luks->getSerialNumber(), + $luks->getUuid() + ) + ); // Return values required by generic download RPC implementation. return array( - "filename" => "LUKS_header_".$luks->getUuid().".bak", + "filename" => "LUKS_header_".implode('_', $fn).".bak", "filepath" => $tmpFilePath, "contenttype" => "application/octet-stream", "unlink" => TRUE diff --git a/usr/share/php/openmediavault/luks.inc b/usr/share/php/openmediavault/luks.inc index 81807fb..a0e1b2b 100644 --- a/usr/share/php/openmediavault/luks.inc +++ b/usr/share/php/openmediavault/luks.inc @@ -39,9 +39,7 @@ class OMVLuksContainers extends OMVObject { * ) */ public static function enumerate() { - $cmd = "export LANG=C; lsblk -o kname,fstype -lbnr ". - "2>/dev/null ". - "| grep crypto_LUKS | awk '{print \"/dev/\"\$1}' "; + $cmd = "export LANG=C; blkid -t TYPE=crypto_LUKS -o device 2>/dev/null"; @OMVUtil::exec($cmd, $output, $result); if($result !== 0) return FALSE; @@ -345,8 +343,8 @@ class OMVLuksContainer extends OMVStorageDeviceAbstract { public function getDescription() { if ($this->getData() === FALSE) return FALSE; - return sprintf(gettext("LUKS encrypted device %s [%s, %s]"), - $this->getDecryptedName(), + return sprintf(gettext("LUKS encrypted device %s[%s, %s]"), + ($this->getModel()) ? '('.$this->getModel().') ' : '', $this->getDeviceFile(), binary_format($this->getSize())); } @@ -361,7 +359,7 @@ class OMVLuksContainer extends OMVStorageDeviceAbstract { * the key file). Defaults to FALSE. * @return TRUE if successful, otherwise FALSE. */ - public function create($devicefile, $key, $keyIsFile=FALSE) { + public function create($key, $keyIsFile=FALSE) { switch($keyIsFile) { case TRUE: $cmd = sprintf("export LANG=C; cryptsetup luksFormat %s ". @@ -877,13 +875,24 @@ class OMVStorageDeviceLUKS extends OMVStorageDeviceDM { return OMVLuksContainer::isLuksContainer($dev); } + /** + * Get the underlying device model. + * @return The device model, otherwise an empty string. + */ + public function getModel() { + if(FALSE === ($luks = $this->getContainer())) + return ""; + return $luks->getModel(); + } + /** * Get the description of the LUKS container. * @return The LUKS container description, FALSE on failure. */ public function getDescription() { - return sprintf(gettext("LUKS encrypted device %s [%s, %s]"), - $this->getLuksEncryptedDeviceName(), + return sprintf(gettext("LUKS encrypted container on %s %s[%s, %s]"), + $this->getCanonicalLuksEncryptedDeviceFile(), + ($this->getModel()) ? '('.$this->getModel().') ' : '', $this->getDeviceFile(), binary_format($this->getSize())); }