Skip to content

Commit

Permalink
Merge pull request #2 from OpenMediaVault-Plugin-Developers/img-dev
Browse files Browse the repository at this point in the history
Merge dev branch: LVM and partition fixes
  • Loading branch information
imgrant committed Jan 6, 2016
2 parents 4f2aa19 + f0a38f2 commit 44b0ac1
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 104 deletions.
11 changes: 11 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -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 <[email protected]> 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.
Expand Down
267 changes: 171 additions & 96 deletions usr/share/openmediavault/engined/rpc/luks.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -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()));
Expand Down Expand Up @@ -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()));
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down
25 changes: 17 additions & 8 deletions usr/share/php/openmediavault/luks.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()));
}
Expand All @@ -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 ".
Expand Down Expand Up @@ -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()));
}
Expand Down

0 comments on commit 44b0ac1

Please sign in to comment.