diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1904193 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.pc/ +debian/openmediavault-mpd/ +debian/openmediavault-mpd*.debhelper* +debian/openmediavault-mpd*.substvars +debian/files +debian/tmp/ +.*.swp +.*~ +*~ diff --git a/README.md b/README.md new file mode 100644 index 0000000..36fc634 --- /dev/null +++ b/README.md @@ -0,0 +1,6 @@ +openmediavault-luksencryption +============================= + +LUKS (Linux Unified Key Setup) encryption plugin for OpenMediaVault. + +LUKS is the standard for Linux hard disk encryption. This plugin provides for basic manipulation of LUKS-encrypted devices in OpenMediaVault; encrypted devices can be created, destroyed, opened (unlocked/mounted) and closed (locked/unmounted). \ No newline at end of file diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..25e86ef --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +openmediavault-luksencryption (1.0.0) UNRELEASED; urgency=low + + * Initial release. + + -- OpenMediaVault Plugin Developers Wed, 28 Oct 2015 16:31:25 +0000 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..e15a5fd --- /dev/null +++ b/debian/control @@ -0,0 +1,18 @@ +Source: openmediavault-luksencryption +Section: admin +Priority: optional +Maintainer: OpenMediaVault Plugin Developers +Build-Depends: debhelper (>= 9) +Standards-Version: 3.9.6 +XB-Plugin-Section: filesystems + +Package: openmediavault-luksencryption +Architecture: all +Depends: openmediavault (>= 1.12), cryptsetup (>= 1.4), ${misc:Depends} +Description: OpenMediaVault LUKS encryption plugin + LUKS is the standard for Linux hard disk encryption. By providing a + standard on-disk-format, it not only facilitates compatibility + among distributions, but also provides secure management of multiple + user passwords. In contrast to existing solutions, LUKS stores all + necessary setup information in the partition header, enabling the + user to transport or migrate his data seamlessly. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..bad2284 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,12 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ + +Files: * +Copyright: Copyright (C) 2015 OpenMediaVault Plugin Developers +License: GPL-3+ + +Files: var/www/openmediavault/images/* +Copyright: Copyright (C) Keyamoon +License: GPL-3+ or CC-BY-4.0 + +License: GPL-3+ + /usr/share/common-licenses/GPL-3 diff --git a/debian/install b/debian/install new file mode 100644 index 0000000..dd9f558 --- /dev/null +++ b/debian/install @@ -0,0 +1,3 @@ +usr/share/openmediavault/* usr/share/openmediavault +var/www/openmediavault/* var/www/openmediavault +usr/share/php/openmediavault/* usr/share/php/openmediavault diff --git a/debian/lintian-overrides b/debian/lintian-overrides new file mode 100644 index 0000000..c712afb --- /dev/null +++ b/debian/lintian-overrides @@ -0,0 +1 @@ +dir-or-file-in-var-www diff --git a/debian/postinst b/debian/postinst new file mode 100644 index 0000000..1d50634 --- /dev/null +++ b/debian/postinst @@ -0,0 +1,27 @@ +#!/bin/sh + +set -e + +. /etc/default/openmediavault +. /usr/share/openmediavault/scripts/helper-functions + +case "$1" in + configure) + # Activate package triggers. These triggers are only set during the + # package installation. + dpkg-trigger update-fixperms + dpkg-trigger update-locale + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument" >&2 + exit 1 + ;; +esac + +#DEBHELPER# + +exit 0 diff --git a/debian/postrm b/debian/postrm new file mode 100644 index 0000000..4a53cf5 --- /dev/null +++ b/debian/postrm @@ -0,0 +1,26 @@ +#!/bin/sh + +set -e + +. /etc/default/openmediavault +. /usr/share/openmediavault/scripts/helper-functions + +case "$1" in + purge) + ;; + + remove) + ;; + + upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + ;; + + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +#DEBHELPER# + +exit 0 diff --git a/debian/rules b/debian/rules new file mode 100644 index 0000000..2d33f6a --- /dev/null +++ b/debian/rules @@ -0,0 +1,4 @@ +#!/usr/bin/make -f + +%: + dh $@ diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..89ae9db --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/debian/triggers b/debian/triggers new file mode 100644 index 0000000..c182f8d --- /dev/null +++ b/debian/triggers @@ -0,0 +1 @@ +activate restart-engined diff --git a/usr/share/openmediavault/engined/module/luks.inc b/usr/share/openmediavault/engined/module/luks.inc new file mode 100644 index 0000000..afb28f8 --- /dev/null +++ b/usr/share/openmediavault/engined/module/luks.inc @@ -0,0 +1,87 @@ + + * @copyright Copyright (c) 2009-2015 Volker Theile + * + * OpenMediaVault is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * OpenMediaVault is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OpenMediaVault. If not, see . + */ +require_once("openmediavault/module.inc"); +require_once("openmediavault/error.inc"); + +class OMVModuleLuks extends OMVModuleServiceAbstract + implements OMVINotifyListener, OMVIModuleNotification { + /** + * Get the module name. + */ + public function getName() { + return "luks"; + } + + /** + * Defines the modules that if present should start before the service + * provided by this module. + * @return An array of modules. + */ + public function shouldStart() { return array("email"); } + + /** + * Generate the configuration. + * @throw E_EXEC_FAILED + */ + public function applyConfig() { + $cmd = sprintf("export LANG=C; omv-mkconf %s 2>&1", $this->getName()); + if(0 !== $this->exec($cmd, $output)) { + throw new OMVException(OMVErrorMsg::E_EXEC_FAILED, + $cmd, implode("\n", $output)); + } + } + + /** + * Get the notification configuration. + */ + public function getNotificationConfig() { + return array( + array( + "id" => "luks", + "type" => getText("Storage"), + "title" => gettext("Encryption") + ) + ); + } + + /** + * Bind listeners. + */ + function bindListeners(OMVNotifyDispatcher $dispatcher) { + $dispatcher->addListener( + OMV_NOTIFY_CREATE | OMV_NOTIFY_MODIFY | OMV_NOTIFY_DELETE, + "org.openmediavault.system.storage.luks.container", + array($this, "setDirty")); + $dispatcher->addListener( + OMV_NOTIFY_MODIFY, + "org.openmediavault.system.email", + array($this, "setDirty")); + $dispatcher->addListener( + OMV_NOTIFY_MODIFY, + "org.openmediavault.system.notification.notifications", + array($this, "setDirty")); + } +} + +// Register module. +$moduleMgr = &OMVModuleMgr::getInstance(); +$moduleMgr->registerModule(new OMVModuleLuks()); diff --git a/usr/share/openmediavault/engined/rpc/luks.inc b/usr/share/openmediavault/engined/rpc/luks.inc new file mode 100644 index 0000000..cee5a49 --- /dev/null +++ b/usr/share/openmediavault/engined/rpc/luks.inc @@ -0,0 +1,463 @@ + + * @copyright Copyright (c) 2009-2015 Volker Theile + * + * OpenMediaVault is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * OpenMediaVault is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OpenMediaVault. If not, see . + */ +require_once("openmediavault/object.inc"); +require_once("openmediavault/error.inc"); +require_once("openmediavault/system.inc"); +require_once("openmediavault/functions.inc"); +require_once("openmediavault/luks.inc"); +require_once("openmediavault/rpcservice.inc"); +require_once("openmediavault/notify.inc"); + +class OMVRpcServiceLuksMgmt extends OMVRpcServiceAbstract { + /** + * Get the RPC service name. + */ + public function getName() { + return "LuksMgmt"; + } + + /** + * Initialize the RPC service. + */ + public function initialize() { + $this->registerMethod("enumerateContainers"); + $this->registerMethod("getContainersList"); + $this->registerMethod("getContainerCandidates"); + $this->registerMethod("getContainerDetails"); + $this->registerMethod("createContainer"); + $this->registerMethod("deleteContainer"); + $this->registerMethod("modifyContainer"); + $this->registerMethod("openContainer"); + $this->registerMethod("closeContainer"); + } + + /** + * Enumerate all LUKS containers on the system. + * @param params The method parameters. + * @param context The context of the caller. + * @return An array of objects. Each object represents a LUKS container + * with the following properties: + * device file, uuid, size, status + * @throw E_EXEC_MISC + */ + public function enumerateContainers($params, $context) { + // Validate the RPC caller context. + $this->validateMethodContext($context, array( + "role" => OMV_ROLE_ADMINISTRATOR + )); + // Enumerate all LUKS containers on the system. + if (FALSE === ($devs = OMVLuksContainers::enumerate())) { + throw new OMVException(OMVErrorMsg::E_EXEC_MISC, + "Failed to get list of encrypted devices"); + } + $result = array(); + foreach($devs as $devk => $devv) { + // Get the container details. + $luks = new OMVLuksContainer($devv); + if(!$luks->exists()) + continue; + $result[] = array( + "devicefile" => $luks->getDeviceFile(), + "uuid" => $luks->getUuid(), + "size" => $luks->getSize(), + "unlocked" => $luks->isOpen(), + "decrypteddevicefile" => $luks->getDecryptedDeviceFile() + ); + } + return $result; + } + + /** + * Get a list of LUKS containers. + * @param params An array containing the following fields: + * \em start The index where to start. + * \em limit The number of objects to process. + * \em sortfield The name of the column used to sort. + * \em sortdir The sort direction, ASC or DESC. + * @param context The context of the caller. + * @return An array containing the requested objects. The field \em total + * contains the total number of objects, \em data contains the object + * array. An exception will be thrown in case of an error. + */ + function getContainersList($params, $context) { + // Validate the RPC caller context. + $this->validateMethodContext($context, array( + "role" => OMV_ROLE_ADMINISTRATOR + )); + // Validate the parameters of the RPC service method. + $this->validateMethodParams($params, '{ + "type":"object", + "properties":{ + "start":{"type":"integer"}, + "limit":{'.$GLOBALS['OMV_JSONSCHEMA_COUNTFIELD'].'}, + "sortfield":{'.$GLOBALS['OMV_JSONSCHEMA_SORTFIELD'].'}, + "sortdir":{'.$GLOBALS['OMV_JSONSCHEMA_SORTDIR'].'} + } + }'); + // Enumerate all LUKS containers on the system. + $containers = $this->callMethod("enumerateContainers", NULL, $context); + foreach($containers as $luksk => &$luksv) { + $used = FALSE; + // Does the logical volume contain a filesystem and is it used? + if(FALSE !== OMVRpc::exec("FsTab", "getByFsName", array( + "id" => $luksv['decrypteddevicefile']), $context)) { + $used = TRUE; + } + $luksv['_used'] = $used; + } + // Filter result. + return $this->applyFilter($containers, $params['start'], $params['limit'], + $params['sortfield'], $params['sortdir']); + } + + /** + * Get list of devices that can be used to create a LUKS container. + * @param params The method parameters. + * @param context The context of the caller. + * @return An array containing objects with the following fields: + * devicefile, size and description. + * @throw E_EXEC_MISC + */ + public function getContainerCandidates($params, $context) { + global $xmlConfig; + // Validate the RPC caller context. + $this->validateMethodContext($context, array( + "role" => OMV_ROLE_ADMINISTRATOR + )); + // Get a list of all potential usable devices. + if (FALSE === ($devs = OMVStorageDevices::enumerateUnused())) { + throw new OMVException(OMVErrorMsg::E_EXEC_MISC, + "Failed to get list of devices"); + } + // Prepare the result list. + $result = array(); + foreach ($devs as $devk => $devv) { + // Get the object that implements the given storage device. + $sd = OMVStorageDeviceFactory::get($devv); + if (is_null($sd) || !$sd->exists()) + continue; + // Skip read-only devices like CDROM. + if (TRUE === $sd->isReadOnly()) + continue; + // Check if device is referenced/used by a plugin. + $xpath = sprintf("//services/devicefile[contains(.,'%s')]", + $sd->getDeviceFile()); + if (TRUE === $xmlConfig->exists($xpath)) + continue; + // Does this device already contain a filesystem? + if (FALSE !== OMVFilesystem::hasFileSystem($sd->getDeviceFile())) + continue; + // Is the device an already open LUKS container? + // Don't allow LUKS-on-LUKS + $luks = new OMVStorageDeviceLUKS($sd->getDeviceFile()); + if (TRUE === $luks->isLuks()) + continue; + // The device is a potential candidate + // to be used as a LUKS container. + $result[] = array( + "devicefile" => $sd->getDeviceFile(), + "size" => $sd->getSize(), + "description" => $sd->getDescription() + ); + } + return $result; + } + + /** + * Get detail about a LUKS device. + * @param params An array containing the following fields: + * \em devicefile The LUKS device file to get details from. + * @param context The context of the caller. + * @return The details of the given LUKS device as string. + * @throw E_MISC_DEVICE_NOT_FOUND + */ + public function getContainerDetails($params, $context) { + // Validate the RPC caller context. + $this->validateMethodContext($context, array( + "role" => OMV_ROLE_ADMINISTRATOR + )); + // Validate the parameters of the RPC service method. + $this->validateMethodParams($params, '{ + "type":"object", + "properties":{ + "devicefile":{'.$GLOBALS['OMV_JSONSCHEMA_DEVICEFILE'].'} + } + }'); + // Get software RAID device details. + $luks = new OMVLuksContainer($params['devicefile']); + if(FALSE === $luks->exists()) { + throw new OMVException(OMVErrorMsg::E_MISC_DEVICE_NOT_FOUND, + sprintf(gettext("LUKS container on '%s' not found"), + $params['devicefile'])); + } + return $luks->getDetail(); + } + + /** + * Open (unlock) a LUKS container. + * @param params An array containing the following fields: + * \em devicefile The block special device of the LUKS container to open. + * @param context The context of the caller. + * @return None + * @throw E_MISC_FAILURE + */ + public function openContainer($params, $context) { + global $xmlConfig; + // Validate the RPC caller context. + $this->validateMethodContext($context, array( + "role" => OMV_ROLE_ADMINISTRATOR + )); + // Validate the parameters of the RPC service method. + $this->validateMethodParams($params, '{ + "type":"object", + "properties":{ + "devicefile":{'.$GLOBALS['OMV_JSONSCHEMA_DEVICEFILE'].'}, + "passphrase":{"type":"string"} + } + }'); + // Validate the container + $luks = new OMVLuksContainer($params['devicefile']); + if (is_null($luks) || !$luks->exists()) { + throw new OMVException(OMVErrorMsg::E_MISC_FAILURE, + sprintf(gettext("LUKS container on '%s' not found"), + $params['devicefile'])); + } + // Check that the container is not already open, then use + // the supplied passphrase to unlock it if not + if (FALSE === $luks->isOpen()) { + if (FALSE === $luks->open($params['passphrase'])) { + throw new OMVException(OMVErrorMsg::E_MISC_FAILURE, + sprintf(gettext("Unable to unlock encrypted device: %s"), + $luks->getLastError())); + } + } + } + + /** + * Close (lock) a LUKS container. + * @param params An array containing the following fields: + * \em devicefile The block special device of the LUKS container to close. + * @param context The context of the caller. + * @return None. + * @throw E_MISC_FAILURE + */ + public function closeContainer($params, $context) { + global $xmlConfig; + // Validate the RPC caller context. + $this->validateMethodContext($context, array( + "role" => OMV_ROLE_ADMINISTRATOR + )); + // Validate the parameters of the RPC service method. + $this->validateMethodParams($params, '{ + "type":"object", + "properties":{ + "devicefile":{'.$GLOBALS['OMV_JSONSCHEMA_DEVICEFILE'].'} + } + }'); + // Validate the container + $luks = new OMVLuksContainer($params['devicefile']); + if (is_null($luks) || !$luks->exists()) { + throw new OMVException(OMVErrorMsg::E_MISC_FAILURE, + sprintf(gettext("LUKS container on '%s' not found"), + $params['devicefile'])); + } + // Check if the container is open, and proceed to close it if so + if (TRUE === $luks->isOpen()) { + if (FALSE === $luks->close()) { + throw new OMVException(OMVErrorMsg::E_MISC_FAILURE, + sprintf(gettext("Unable to lock encrypted device: %s"), + $luks->getLastError())); + } + } + } + + /** + * Create a LUKS container. + * @param params An array containing the following fields: + * \em devicefile The device file where to create the LUKS container. + * \em passphrase The passphrase to unlock the device. + * @param context The context of the caller. + * @return None. + * @throw E_MISC_DEVICE_NOT_FOUND + * @throw E_EXEC_FAILED + */ + public function createContainer($params, $context) { + // Validate the RPC caller context. + $this->validateMethodContext($context, array( + "role" => OMV_ROLE_ADMINISTRATOR + )); + // Validate the parameters of the RPC service method. + $this->validateMethodParams($params, '{ + "type":"object", + "properties":{ + "devicefile":{'.$GLOBALS['OMV_JSONSCHEMA_DEVICEFILE'].'}, + "passphrase":{"type": "string"} + } + }'); + // Get the storage device object. + $sd = OMVStorageDeviceFactory::get($params['devicefile']); + if (is_null($sd) || !$sd->exists()) { + throw new OMVException(OMVErrorMsg::E_MISC_DEVICE_NOT_FOUND, + $params['devicefile']); + } + // Get the storage device backend of the given device. + $sdb = OMVStorageDevices::getBackend($sd->getDeviceFile()); + if (is_null($sdb)) { + throw new OMVException(OMVErrorMsg::E_MISC_FAILURE, sprintf( + "No storage device backend exists for device %s", + $sd->getDeviceFile())); + } + switch ($sdb->getType()) { + case OMV_STORAGE_DEVICE_TYPE_SOFTWARERAID: + case OMV_STORAGE_DEVICE_TYPE_DEVICEMAPPER: + // Wipe existing filesystems. + $cmd = sprintf("export LANG=C; wipefs -a %s 2>&1", + $sd->getDeviceFile()); + @OMVUtil::exec($cmd, $output, $result); + if ($result !== 0) { + throw new OMVException(OMVErrorMsg::E_EXEC_FAILED, + $cmd, implode("\n", $output)); + } + break; + default: + // Wipe existing filesystems. + $cmd = sprintf("export LANG=C; sgdisk --zap-all %s 2>&1", + escapeshellarg($sd->getDeviceFile())); + @OMVUtil::exec($cmd, $output, $result); + if ($result !== 0) { + throw new OMVException(OMVErrorMsg::E_EXEC_FAILED, + $cmd, implode("\n", $output)); + } + break; + } + // Reread partition table. + $cmd = sprintf("export LANG=C; partprobe %s", escapeshellarg( + $sd->getDeviceFile())); + @OMVUtil::exec($cmd, $output, $result); + if ($result !== 0) { + throw new OMVException(OMVErrorMsg::E_EXEC_FAILED, + $cmd, implode("\n", $output)); + } + // Create the container. + $luks = new OMVLuksContainer($sd->getDeviceFile()); + if (!$luks->create($params)) { + throw new OMVException(OMVErrorMsg::E_EXEC_MISC, sprintf( + gettext("Failed to create encrypted device: %s"), + $luks->getLastError())); + } + // Notify configuration changes. + $dispatcher = &OMVNotifyDispatcher::getInstance(); + $dispatcher->notify(OMV_NOTIFY_CREATE, + "org.openmediavault.system.storage.luks.container", array( + "devicefile" => $params['devicefile'] + )); + } + + /** + * Delete a LUKS container. + * @param params An array containing the following fields: + * \em devicefile The devicefile of the LUKS device. + * @param context The context of the caller. + * @return None. + * @throw E_MISC_FAILURE + * @throw E_EXEC_MISC + */ + public function deleteContainer($params, $context) { + // Validate the RPC caller context. + $this->validateMethodContext($context, array( + "role" => OMV_ROLE_ADMINISTRATOR + )); + // Validate the parameters of the RPC service method. + $this->validateMethodParams($params, '{ + "type":"object", + "properties":{ + "devicefile":{'.$GLOBALS['OMV_JSONSCHEMA_DEVICEFILE'].'} + } + }'); + // Check if the container exists. + $luks = new OMVLuksContainer($params['devicefile']); + if (!$luks->exists()) { + throw new OMVException(OMVErrorMsg::E_MISC_FAILURE, + sprintf(gettext("No encryption found on '%s'"), + $params['devicefile'])); + } + // Remove the container. + if (!$luks->remove()) { + throw new OMVException(OMVErrorMsg::E_EXEC_MISC, sprintf( + gettext("Failed to remove the encrypted device: %s"), + $luks->getLastError())); + } + // Notify configuration changes. + $dispatcher = &OMVNotifyDispatcher::getInstance(); + $dispatcher->notify(OMV_NOTIFY_DELETE, + "org.openmediavault.system.storage.luks.container", array( + "devicefile" => $params['devicefile'] + )); + } + + /** + * Modify a LUKS container. + * @param params An array containing the following fields: + * \em devicefile The device file of the logical volume to modify. + * \em openatboot A boolean value representing whether to unlock + * the encrypted device at boot or not. + * @param context The context of the caller. + * @return None. + * @throw E_MISC_FAILURE + * @throw E_EXEC_FAILED + */ + public function modifyContainer($params, $context) { + // Validate the RPC caller context. + $this->validateMethodContext($context, array( + "role" => OMV_ROLE_ADMINISTRATOR + )); + // Validate the parameters of the RPC service method. + $this->validateMethodParams($params, '{ + "type":"object", + "properties":{ + "devicefile":{'.$GLOBALS['OMV_JSONSCHEMA_DEVICEFILE'].'}, + "openatboot":{"type":"boolean"} + } + }'); + // Check if logical volume exists. + $luks = new OMVLuksContainer($params['devicefile']); + if (FALSE === $luks->exists()) { + throw new OMVException(OMVErrorMsg::E_MISC_FAILURE, + sprintf("No encryption found on '%s'", + $params['devicefile'])); + } + // Set the unlock at boot status + // TODO: change crypttab method + // Notify configuration changes. + $dispatcher = &OMVNotifyDispatcher::getInstance(); + $dispatcher->notify(OMV_NOTIFY_MODIFY, + "org.openmediavault.system.storage.luks.container", array( + "devicefile" => $params['devicefile'], + "openatboot" => array_boolval($params, "openatboot") + )); + } +} + +// Register the RPC service. +$rpcServiceMgr = &OMVRpcServiceMgr::getInstance(); +$rpcServiceMgr->registerService(new OMVRpcServiceLuksMgmt()); +?> diff --git a/usr/share/openmediavault/mkconf/luks b/usr/share/openmediavault/mkconf/luks new file mode 100644 index 0000000..66de237 --- /dev/null +++ b/usr/share/openmediavault/mkconf/luks @@ -0,0 +1,77 @@ +#!/bin/sh +# +# This file is part of OpenMediaVault. +# +# @license http://www.gnu.org/licenses/gpl.html GPL Version 3 +# @author Volker Theile +# @copyright Copyright (c) 2009-2015 Volker Theile +# +# OpenMediaVault is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# any later version. +# +# OpenMediaVault is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with OpenMediaVault. If not, see . + +# Documentation/Howto: +# http://linux.die.net/man/8/cryptsetup +# http://linux.die.net/man/5/crypttab +# https://gitlab.com/cryptsetup/cryptsetup + +set -e + +. /etc/default/openmediavault +. /usr/share/openmediavault/scripts/helper-functions + +OMV_LUKS_CRYPTDISKS_DEFAULT=${OMV_LUKS_CRYPTDISKS_DEFAULT:-"/etc/default/cryptdisks"} +OMV_LUKS_CRYPTTAB_CONFIG=${OMV_LUKS_CRYPTTAB_CONFIG:-"/etc/crypttab"} +OMV_LUKS_CRYPTDISKS_INITSCRIPT=${OMV_LUKS_CRYPTDISKS_INITSCRIPT:-"/etc/init.d/cryptdisks"} +OMV_LUKS_CRYPTDISKS_EARLY_INITSCRIPT=${OMV_LUKS_CRYPTDISKS_EARLY_INITSCRIPT:-"/etc/init.d/cryptdisks-early"} +OMV_LUKS_CRYPTDISKS_ENABLE=${OMV_LUKS_CRYPTDISKS_ENABLE:-"Yes"} +OMV_LUKS_CRYPTDISKS_MOUNT=${OMV_LUKS_CRYPTDISKS_MOUNT:-""} + +# Generate configuration files +mkconf() { + +# Create the '/etc/default/cryptdisks' file. +cat > ${OMV_LUKS_CRYPTDISKS_DEFAULT} < ${OMV_LUKS_CRYPTTAB_CONFIG} < +EOF + +} + + +case "$1" in +mkconf|*) + # Do nothing for the time being... + # mkconf + ;; +esac +exit 0 diff --git a/usr/share/php/openmediavault/luks.inc b/usr/share/php/openmediavault/luks.inc new file mode 100644 index 0000000..f9c439a --- /dev/null +++ b/usr/share/php/openmediavault/luks.inc @@ -0,0 +1,570 @@ + + * @copyright Copyright (c) 2009-2015 Volker Theile + * + * OpenMediaVault is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * OpenMediaVault is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OpenMediaVault. If not, see . + */ +require_once("openmediavault/globals.inc"); +require_once("openmediavault/object.inc"); +require_once("openmediavault/functions.inc"); +require_once("openmediavault/util.inc"); + +/** + * Class for handling LUKS-encrypted disks. + */ +class OMVLuksContainers extends OMVObject { + /** + * Enumerate LUKS devices. + * @return A list of devices that are LUKS containers, otherwise FALSE. + * Example: array( + * 0 => /dev/sdb + * 1 => /dev/sdd + * ) + */ + public static function enumerate() { + $cmd = "export LANG=C; lsblk -o kname,fstype -lbnr ". + "2>/dev/null ". + "| grep crypto_LUKS | awk '{print \"/dev/\"\$1}' "; + @OMVUtil::exec($cmd, $output, $result); + if($result !== 0) + return FALSE; + $list = array(); + // Parse command output: + // /dev/sdc + // /dev/sdd + // unknown device + foreach($output as $outputk => $outputv) { + $deviceFile = trim($outputv); + if(!is_devicefile($deviceFile)) + continue; + $list[] = $deviceFile; + } + return $list; + } +} + +/** + * Class for handling a LUKS-encrypted device (aka a LUKS container). + */ +class OMVLuksContainer extends OMVStorageDeviceAbstract { + + protected $uuid = ""; + protected $isOpen = FALSE; + protected $headerInfo = ""; + protected $usedKeySlots = 0; + protected $freeKeySlots = 8; + + protected $deviceMapperDeviceFile = ""; + protected $deviceMapperName = ""; + + private $dataCached = FALSE; + + /** + * Get the name of the device mapper device. + * @return The name of the device mapper device. + */ + public function getDeviceMapperName() { + // Make sure the canonical device file is used to extract the name + // of the device. + $path = sprintf("/sys/block/%s/dm/name", $this->getDeviceMapperCanonicalName()); + if(!file_exists($path)) + return FALSE; + return trim(file_get_contents($path)); + } + + /** + * Get the UUID of the device mapper device. + * @return The UUID of the device mapper device, otherwise FALSE. + */ + public function getDeviceMapperUuid() { + // Make sure the canonical device file is used to extract the name + // of the device. + $path = sprintf("/sys/block/%s/dm/uuid", $this->getDeviceMapperCanonicalName()); + if(!file_exists($path)) + return FALSE; + return trim(file_get_contents($path)); + } + + /** + * Get the holder device file of the container if it's open + * @return A device file string (/dev/dm-0), otherwise FALSE. + */ + public function getDeviceMapperDeviceFile() { + if(FALSE === ($holders = $this->getHolders())) + return FALSE; + if(count($holders)!=1) + return FALSE; + return $holders[0]; // Should only be one holder, just return the first + } + + /** + * Get the holder device name of the container if it's open + * @return A device name (dm-0), otherwise FALSE. + */ + public function getDeviceMapperCanonicalName() { + // Get the device file and extract the name, e.g. /dev/sda => sda. + if(FALSE === ($dev = $this->getDeviceMapperDeviceFile())) { + return FALSE; + } + $deviceName = str_replace("/dev/", "", $dev); + return $deviceName; + } + + /** + * Get detailed information for the container. + * @private + * @return TRUE if successful, otherwise FALSE. + */ + private function getData() { + if($this->dataCached !== FALSE) + return TRUE; + + // Look up the UUID for the LUKS container + $cmd = sprintf("export LANG=C; cryptsetup luksUUID %s", + $this->getDeviceFile()); + @OMVUtil::exec($cmd, $output, $result); + if($result !== 0) { + $this->setLastError($output); + return FALSE; + } + $uuid = trim($output[0]); + $this->uuid = $uuid; + unset($cmd, $output, $result); + + // Get size of device + $this->size = $this->getSize(); + + // Check if the device is open and get the device mapper name if so + if (FALSE !== ($dev = $this->getDeviceMapperDeviceFile())) { + // Any devices of the form /dev/dm-n are for internal use only and + // should never be used. Because of that the device file returned + // should look like /dev/mapper/. See for more information: + // https://access.redhat.com/site/documentation/en-US/Red_Hat_Enterprise_Linux/6/html-single/DM_Multipath + if(1 == preg_match("/^\/dev\/dm-\d+$/", $dev)) { + if(FALSE !== ($name = $this->getDeviceMapperName())) { + $this->deviceMapperDeviceFile = sprintf("/dev/mapper/%s", $name); + $this->deviceMapperName = $name; + } else { + $this->deviceMapperDeviceFile = $dev; + } + } + $this->isOpen = TRUE; + } + + // Collect the detailed output from luksDump + $cmd = sprintf("export LANG=C; cryptsetup luksDump %s", + $this->getDeviceFile()); + @OMVUtil::exec($cmd, $output, $result); + if($result !== 0) { + $this->setLastError($output); + return FALSE; + } + $this->headerInfo = $output; + $this->usedKeySlots = count(preg_grep("/^Key Slot \d: ENABLED$/", $this->headerInfo)); + $this->freeKeySlots = count(preg_grep("/^Key Slot \d: DISABLED$/", $this->headerInfo)); + unset($cmd, $output, $result); + + // Set flag to mark information has been successfully read. + $this->dataCached = TRUE; + + return TRUE; + } + + /** + * Refresh the cached information. + * @return TRUE if successful, otherwise FALSE. + */ + public function refresh() { + $this->dataCached = FALSE; + if($this->getData() === FALSE) + return FALSE; + return TRUE; + } + + /** + * Checks if the container exists - synonym for isLuks(). + * @return TRUE if the container exists, otherwise FALSE. + */ + public function exists() { + return $this->isLuks(); + } + + /** + * Checks if the device exists and is a LUKS container + * @return TRUE if the device is a LUKS container, otherwise FALSE. + */ + public function isLuks() { + if (FALSE === parent::exists()) + return FALSE; + return self::isLuksContainer($this->getDeviceFile()); + } + + /** + * Helper function for determining if a device is a LUKS container. + * @return TRUE if the device is a LUKS container, otherwise FALSE. + */ + public static function isLuksContainer($deviceFile) { + $cmd = sprintf("export LANG=C; cryptsetup isLuks %s", $deviceFile); + @OMVUtil::exec($cmd, $output, $result); + if($result !== 0) + return FALSE; + return TRUE; + } + + /** + * Get the holder devices of the LUKS container (i.e. the decrypted device, + * if open). + * @return An array of device files, otherwise FALSE. + */ + public function getHolders() { + // Make sure the canonical device file is used to extract the name + // of the device. + $path = sprintf("/sys/block/%s/holders", $this->getDeviceName(TRUE)); + if(!file_exists($path)) + return FALSE; + $result = array(); + $dir = new DirectoryIterator($path); + foreach($dir as $item) { + if($item->isDot() || !$item->isLink()) + continue; + $result[] = sprintf("/dev/%s", $item->getFilename()); + } + return $result; + } + + /** + * Get detailed information about the container. + * @return Detailed information about the container, FALSE on failure. + */ + public function getDetail() { + if($this->getData() === FALSE) + return FALSE; + return implode("\n", $this->headerInfo); + } + + /** + * How many key slots are used. + * @return Number of used key slots, FALSE on failure. + */ + public function getUsedKeySlots() { + if($this->getData() === FALSE) + return FALSE; + return $this->usedKeySlots; + } + + /** + * How many key slots are unused. + * @return Number of free key slots, FALSE on failure. + */ + public function getFreeKeySlots() { + if($this->getData() === FALSE) + return FALSE; + return $this->freeKeySlots; + } + + /** + * Get the UUID of the container. + * @return The UUID of the container, FALSE on failure. + */ + public function getUuid() { + if($this->getData() === FALSE) + return FALSE; + return $this->uuid; + } + + /** + * Is the container open? (i.e. unlocked and mapped). + * @return TRUE if the container is open, otherwise FALSE. + */ + public function isOpen() { + if($this->getData() === FALSE) + return FALSE; + return $this->isOpen; + } + + /** + * Get the devicefile of the mapped device (i.e. the open container). + * @return The mapped devicefile of the container, FALSE on failure. + */ + public function getDecryptedDeviceFile() { + if($this->getData() === FALSE) + return FALSE; + return $this->deviceMapperDeviceFile; + } + + /** + * Get the device name of the mapped device (i.e. the open container). + * @return The mapped device name of the container, FALSE on failure. + */ + public function getDecryptedName() { + if($this->getData() === FALSE) + return FALSE; + return $this->deviceMapperName; + } + + /** + * Get the description of the LUKS container. + * @return The LUKS container description, FALSE on failure. + */ + public function getDescription() { + if ($this->getData() === FALSE) + return FALSE; + return sprintf(gettext("LUKS encrypted device %s [%s, %s]"), + $this->getDecryptedName(), $this->getDeviceFile(), + binary_format($this->getSize())); + } + + /** + * Create the container. + * @param params An array containing the following fields: + * \em devicefile The device file where to create the LUKS container. + * \em passphrase The passphrase to unlock the device. + * @return TRUE if successful, otherwise FALSE. + */ + public function create($params) { + $cmd = sprintf("export LANG=C; echo -n \"%s\" ". + "| cryptsetup luksFormat %s - 2>&1", + $params['passphrase'], escapeshellarg($params['devicefile'])); + @OMVUtil::exec($cmd, $output, $result); + if ($result !== 0) { + $this->setLastError($output); + return FALSE; + } + $this->refresh(); + return TRUE; + } + + /** + * Remove the container. + * @return TRUE if successful, otherwise FALSE. + */ + public function remove() { + // First, close the device if it is open + if ($this->isOpen()) { + if (FALSE === $luks->close()) { + return FALSE; + } + } + // Get the payload offset (header size) + $cmd = sprintf("export LANG=C; cryptsetup luksDump %s ". + "| grep 'Payload offset' | awk '{print $3}'", + escapeshellarg($this->getDeviceFile())); + @OMVUtil::exec($cmd, $output, $result); + if ($result !== 0) { + $this->setLastError($output); + // Don't quit if we can't work out the header size, + // just assume a default of 2MiB (offset 4096) instead + $header_size = 4096; + } else { + $header_size = trim($output[0]); + } + unset($cmd, $output, $result); + + // Get the storage device object. + $sd = OMVStorageDeviceFactory::get($this->getDeviceFile()); + if (is_null($sd) || !$sd->exists()) { + $this->setLastError($output); + return FALSE; + } + // Get the storage device backend of the given device. + $sdb = OMVStorageDevices::getBackend($sd->getDeviceFile()); + if (is_null($sdb)) { + $this->setLastError($output); + return FALSE; + } + switch ($sdb->getType()) { + case OMV_STORAGE_DEVICE_TYPE_SOFTWARERAID: + case OMV_STORAGE_DEVICE_TYPE_DEVICEMAPPER: + // Wipe existing filesystems. + $cmd = sprintf("export LANG=C; wipefs -a %s 2>&1", + $sd->getDeviceFile()); + @OMVUtil::exec($cmd, $output, $result); + if ($result !== 0) { + $this->setLastError($output); + return FALSE; + } + break; + default: + // Wipe existing filesystems. + $cmd = sprintf("export LANG=C; sgdisk --zap-all %s 2>&1", + escapeshellarg($sd->getDeviceFile())); + @OMVUtil::exec($cmd, $output, $result); + if ($result !== 0) { + $this->setLastError($output); + return FALSE; + } + break; + } + unset($cmd, $output, $result); + // Destroy the header by overwriting it + $cmd = sprintf("export LANG=C; dd if=/dev/urandom of=%s bs=512 count=%s 2>&1", + escapeshellarg($sd->getDeviceFile()), $header_size); + @OMVUtil::exec($cmd, $output, $result); + if ($result !== 0) { + $this->setLastError($output); + return FALSE; + } + $this->refresh(); + return TRUE; + } + + /** + * Open (unlock) the container. + * @param passphrase A passphrase that will unlock the LUKS device. + * @return TRUE if successful, otherwise FALSE. + */ + public function open($passphrase) { + $cmd = sprintf("export LANG=C; echo -n \"%s\" | cryptsetup luksOpen %s %s-crypt --key-file=- 2>&1", + $passphrase, $this->getDeviceFile(), $this->getDeviceName()); + @OMVUtil::exec($cmd, $output, $result); + if ($result !== 0) { + $this->setLastError($output); + return FALSE; + } + $this->refresh(); + return TRUE; + } + + /** + * Close (lock) the container. + * @return TRUE if successful, otherwise FALSE. + */ + public function close() { + $cmd = sprintf("export LANG=C; cryptsetup luksClose %s 2>&1", $this->getDecryptedName()); + @OMVUtil::exec($cmd, $output, $result); + if ($result !== 0) { + $this->setLastError($output); + return FALSE; + } + $this->refresh(); + return TRUE; + } +} + + +/** + * Class for handling an open LUKS container, + * i.e. from the decrypted device point of view. + */ +class OMVStorageDeviceLUKS extends OMVStorageDeviceDM { + /** + * Get the base (slave) device file of the container + * @return A device file string (/dev/sdb), otherwise FALSE. + */ + public function getLuksEncryptedDeviceFile() { + if(FALSE === ($slaves = $this->getSlaves())) + return FALSE; + if(count($slaves)!=1) + return FALSE; + return $slaves[0]; // Should only be one slave, just return the first + } + + /** + * Get the canonical device file, e.g. /dev/disk/by-label/root -> /dev/sda + * @return A device file string (/dev/sda), otherwise FALSE. + */ + public function getCanonicalLuksEncryptedDeviceFile() { + if(FALSE === ($dev = $this->getLuksEncryptedDeviceFile())) + return FALSE; + return realpath($dev); + } + + /** + * Get the base (slave) device name of the container + * @param canonical If set to TRUE the canonical device file will + * be used. Defaults to FALSE. + * @return A device name (sdb), otherwise FALSE. + */ + public function getLuksEncryptedDeviceName($canonical = FALSE) { + $deviceName = str_replace("/dev/", "", !$canonical ? + $this->getLuksEncryptedDeviceFile() : $this->getCanonicalLuksEncryptedDeviceFile()); + return $deviceName; + } + + /** + * Checks if the container exists - synonym for isLuks(). + * @return TRUE if the container exists, otherwise FALSE. + */ + public function exists() { + return $this->isLuks(); + } + + /** + * Checks if the device exists and is a LUKS container + * @return TRUE if the device is a LUKS container, otherwise FALSE. + */ + public function isLuks() { + if(FALSE === ($dev = $this->getLuksEncryptedDeviceFile())) + return FALSE; + // Test whether the slave is a LUKS container or not + return OMVLuksContainer::isLuksContainer($dev); + } + + /** + * 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(), $this->getDeviceFile(), + binary_format($this->getSize())); + } +} + + +/** + * Implements the storage device backend for (open) LUKS containers. + */ +class OMVStorageDeviceBackendLUKS extends OMVStorageDeviceBackendDM { + function enumerate() { + $devs = parent::enumerate(); + if(FALSE === $devs) + return FALSE; + $result = array(); + foreach($devs as $devk => $devv) { + // Check if the given device is a LUKS container. + $object = new OMVStorageDeviceLUKS($devv); + if(!$object->isLuks()) + continue; + $result[] = $object->getDeviceFile(); + } + return $result; + } + + function isTypeOf($deviceFile) { + // Check if the given device is of type device mapper. + if(FALSE === parent::isTypeOf($deviceFile)) + return FALSE; + // Check if it is an open LUKS container. + $object = new OMVStorageDeviceLUKS($deviceFile); + return $object->isLuks(); + } + + function getImpl($args) { + return new OMVStorageDeviceLUKS($args); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Register new storage device backend. +// N.B. To work most effectively, this needs to be registered before the +// LVM and Device Mapper backends, otherwise open LUKS containers will be +// enumerated as plain Device Mapper devices. +/////////////////////////////////////////////////////////////////////////////// +OMVStorageDevices::registerBackend(new OMVStorageDeviceBackendLUKS()); +?> diff --git a/var/www/openmediavault/images/padlock-closed.png b/var/www/openmediavault/images/padlock-closed.png new file mode 100644 index 0000000..b291fad Binary files /dev/null and b/var/www/openmediavault/images/padlock-closed.png differ diff --git a/var/www/openmediavault/images/padlock-closed.svg b/var/www/openmediavault/images/padlock-closed.svg new file mode 100644 index 0000000..41b6056 --- /dev/null +++ b/var/www/openmediavault/images/padlock-closed.svg @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/var/www/openmediavault/images/padlock-open.png b/var/www/openmediavault/images/padlock-open.png new file mode 100644 index 0000000..d47d1e1 Binary files /dev/null and b/var/www/openmediavault/images/padlock-open.png differ diff --git a/var/www/openmediavault/images/padlock-open.svg b/var/www/openmediavault/images/padlock-open.svg new file mode 100644 index 0000000..f864dd5 --- /dev/null +++ b/var/www/openmediavault/images/padlock-open.svg @@ -0,0 +1,20 @@ + + + + + + diff --git a/var/www/openmediavault/js/omv/module/admin/storage/luks/Containers.js b/var/www/openmediavault/js/omv/module/admin/storage/luks/Containers.js new file mode 100644 index 0000000..986c3bf --- /dev/null +++ b/var/www/openmediavault/js/omv/module/admin/storage/luks/Containers.js @@ -0,0 +1,549 @@ +/** + * This file is part of OpenMediaVault. + * + * @license http://www.gnu.org/licenses/gpl.html GPL Version 3 + * @author Volker Theile + * @copyright Copyright (c) 2009-2015 Volker Theile + * + * OpenMediaVault is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * OpenMediaVault is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OpenMediaVault. If not, see . + */ +// require("js/omv/WorkspaceManager.js") +// require("js/omv/workspace/grid/Panel.js") +// require("js/omv/workspace/window/Form.js") +// require("js/omv/workspace/window/Grid.js") +// require("js/omv/workspace/window/plugin/ConfigObject.js") +// require("js/omv/Rpc.js") +// require("js/omv/data/Store.js") +// require("js/omv/data/Model.js") +// require("js/omv/data/proxy/Rpc.js") +// require("js/omv/util/Format.js") +// require("js/omv/window/Execute.js") + +/** + * @class OMV.module.admin.storage.luks.container.Create + * @derived OMV.workspace.window.Form + */ +Ext.define("OMV.module.admin.storage.luks.container.Create", { + extend: "OMV.workspace.window.Form", + requires: [ + "OMV.data.Store", + "OMV.data.Model", + "OMV.data.proxy.Rpc" + ], + + title: _("Create encrypted device"), + okButtonText: _("OK"), + hideResetButton: true, + width: 500, + rpcService: "LuksMgmt", + rpcSetMethod: "createContainer", + + getFormItems: function() { + var me = this; + return [{ + xtype: "combo", + name: "devicefile", + fieldLabel: _("Device"), + emptyText: _("Select a device ..."), + store: Ext.create("OMV.data.Store", { + autoLoad: true, + model: OMV.data.Model.createImplicit({ + idProperty: "devicefile", + fields: [ + { name: "devicefile", type: "string" }, + { name: "description", type: "string" } + ] + }), + proxy: { + type: "rpc", + appendSortParams: false, + rpcData: { + service: "LuksMgmt", + method: "getContainerCandidates" + } + }, + sorters: [{ + direction: "ASC", + property: "devicefile" + }] + }), + displayField: "description", + valueField: "devicefile", + allowBlank: false, + editable: false, + triggerAction: "all" + },{ + xtype: "passwordfield", + name: "passphrase", + fieldLabel: _("Passphrase"), + allowBlank: false, + triggerAction: "all" + },{ + xtype: "passwordfield", + name: "passphraseconf", + fieldLabel: _("Confirm passphrase"), + allowBlank: false, + submitValue: false + }]; + }, + + isValid: function() { + var me = this; + if (!me.callParent(arguments)) + return false; + var valid = true; + var values = me.getValues(); + // Check the passphrases match. + var field = me.findField("passphraseconf"); + if (values.passphrase !== field.getValue()) { + var msg = _("Passphrases don't match"); + me.markInvalid([ + { id: "passphrase", msg: msg }, + { id: "passphraseconf", msg: msg } + ]); + valid = false; + } + return valid; + }, + + doSubmit: function() { + var me = this; + OMV.MessageBox.show({ + title: _("Confirmation"), + msg: _("Do you really want to encrypt this device? Any existing data on it will be deleted."), + buttons: Ext.Msg.YESNO, + fn: function(answer) { + if(answer === "no") + return; + me.superclass.doSubmit.call(me); + }, + scope: me, + icon: Ext.Msg.QUESTION + }); + } +}); + + +/** + * @class OMV.module.admin.storage.luks.container.Unlock + * @derived OMV.workspace.window.Form + * @param uuid The UUID of the configuration object. + * @param devicefile The device file, e.g. /dev/sda. + */ +Ext.define("OMV.module.admin.storage.luks.container.Unlock", { + extend: "OMV.workspace.window.Form", + + rpcService: "LuksMgmt", + rpcSetMethod: "openContainer", + title: _("Unlock encrypted device"), + autoLoadData: false, + hideResetButton: true, + okButtonText: "Unlock", + width: 450, + + getFormConfig: function() { + return { + layout: { + type: "vbox", + align: "stretch" + } + }; + }, + + getFormItems: function() { + var me = this; + return [{ + xtype: "textfield", + name: "devicefile", + fieldLabel: _("Device"), + allowBlank: false, + readOnly: true, + value: me.devicefile + },{ + xtype: "passwordfield", + name: "passphrase", + fieldLabel: _("Passphrase"), + allowBlank: false + }]; + }, + + getRpcSetParams: function() { + var me = this; + var params = me.callParent(arguments); + return Ext.apply(params, { + devicefile: me.devicefile + }); + } +}); + + +/** + * @class OMV.module.admin.storage.luks.container.Detail + * @derived OMV.workspace.window.TextArea + */ +Ext.define("OMV.module.admin.storage.luks.container.Detail", { + extend: "OMV.workspace.window.TextArea", + + rpcService: "LuksMgmt", + rpcGetMethod: "getContainerDetails", + title: _("Encrypted device details"), + width: 550, + height: 450 +}); + + +/** + * @class OMV.module.admin.storage.luks.Containers + * @derived OMV.workspace.grid.Panel + */ +Ext.define("OMV.module.admin.storage.luks.Containers", { + extend: "OMV.workspace.grid.Panel", + requires: [ + "OMV.data.Store", + "OMV.data.Model", + "OMV.data.proxy.Rpc" + ], + uses: [ + "OMV.module.admin.storage.luks.container.Create", + "OMV.module.admin.storage.luks.container.Detail" + ], + + autoReload: true, + rememberSelected: true, + hideAddButton: true, + hideEditButton: true, + hidePagingToolbar: false, + disableLoadMaskOnLoad: true, + stateful: true, + stateId: "5abd703b-5ec7-4248-9138-452db85d17d5", + columns: [{ + xtype: "emptycolumn", + text: _("Device"), + sortable: true, + dataIndex: "devicefile", + stateId: "devicefile" + },{ + xtype: "binaryunitcolumn", + text: _("Size"), + sortable: true, + dataIndex: "size", + stateId: "size" + },{ + text: _("Unlocked"), + sortable: true, + dataIndex: "unlocked", + stateId: "unlocked", + width: 80, + resizable: false, + align: "center", + renderer: function(value, metaData, record) { + var iconCls; + switch (record.get("unlockatboot")) { + case 1: + case true: // Device is in crypttab + iconCls = (true == value) ? + "grid-cell-booleaniconcolumn-led-blue" : + "grid-cell-booleaniconcolumn-led-red"; + break; + default: // Device is not in crypttab + iconCls = (true == value) ? + "grid-cell-booleaniconcolumn-led-blue" : + "grid-cell-booleaniconcolumn-led-gray"; + break; + } + metaData.tdCls = Ext.baseCSSPrefix + + "grid-cell-booleaniconcolumn" + " " + + Ext.baseCSSPrefix + iconCls; + return ""; + } + },{ + text: _("Decrypted device"), + sortable: true, + dataIndex: "decrypteddevicefile", + stateId: "decrypteddevicefile", + renderer: function(value) { + if (!value || 0 === value.length) { + value = _("n/a"); + } + return value; + } + },{ + xtype: "booleantextcolumn", + text: _("Referenced"), + sortable: true, + dataIndex: "_used", + stateId: "_used" + }], + + initComponent: function() { + var me = this; + Ext.apply(me, { + store: Ext.create("OMV.data.Store", { + autoLoad: true, + model: OMV.data.Model.createImplicit({ + // Note, do not use 'devicefile' as idProperty, because + // it is not guaranteed that the devicefile is set. This + // is the case when a device is configured for mounting + // but does not exist (e.g. USB). + identifier: "uuid", // Populate 'id' field automatically. + idProperty: "id", + fields: [ + { name: "id", type: "string", persist: false }, + { name: "uuid", type: "string" }, + { name: "devicefile", type: "string" }, + { name: "size", type: "string" }, + { name: "unlocked", type: "boolean" }, + { name: "decrypteddevicefile", type: "string" }, + { name: "_used", type: "boolean" } + ] + }), + proxy: { + type: "rpc", + rpcData: { + service: "LuksMgmt", + method: "getContainersList", + options: { + updatelastaccess: false + } + } + }, + remoteSort: true, + sorters: [{ + direction: "ASC", + property: "devicefile" + }] + }) + }); + me.callParent(arguments); + }, + + getTopToolbarItems: function() { + var me = this; + var items = me.callParent(arguments); + Ext.Array.insert(items, 1, [{ + id: me.getId() + "-create", + xtype: "button", + text: _("Create"), + icon: "images/add.svg", + iconCls: Ext.baseCSSPrefix + "btn-icon-16x16", + handler: Ext.Function.bind(me.onCreateButton, me, [ me ]), + scope: me, + disabled: false + },{ + id: me.getId() + "-unlock", + xtype: "button", + text: _("Unlock"), + icon: "images/padlock-open.svg", + iconCls: Ext.baseCSSPrefix + "btn-icon-16x16", + handler: Ext.Function.bind(me.onUnlockButton, me, [ me ]), + scope: me, + disabled: true + },{ + id: me.getId() + "-lock", + xtype: "button", + text: _("Lock"), + icon: "images/padlock-closed.svg", + iconCls: Ext.baseCSSPrefix + "btn-icon-16x16", + handler: Ext.Function.bind(me.onLockButton, me, [ me ]), + scope: me, + disabled: true + },{ + id: me.getId() + "-detail", + xtype: "button", + text: _("Detail"), + icon: "images/details.svg", + iconCls: Ext.baseCSSPrefix + "btn-icon-16x16", + handler: me.onDetailButton, + scope: me, + disabled: true + }]); + return items; + }, + + onSelectionChange: function(model, records) { + var me = this; + me.callParent(arguments); + // Process additional buttons. + var tbarBtnDisabled = { + "delete": true, + "unlock": true, + "lock": true, + "detail": true + }; + if (records.length <= 0) { + tbarBtnDisabled["delete"] = true; + tbarBtnDisabled["unlock"] = true; + tbarBtnDisabled["lock"] = true; + tbarBtnDisabled["detail"] = true; + } else if(records.length == 1) { + var record = records[0]; + // Set default values. + tbarBtnDisabled["delete"] = true; + tbarBtnDisabled["unlock"] = true; + tbarBtnDisabled["lock"] = true; + tbarBtnDisabled["detail"] = false; + // Disable/enable the unlock/lock buttons depending on whether + // the selected device is open. + if (true === record.get("unlocked")) { + tbarBtnDisabled["lock"] = false; + tbarBtnDisabled["delete"] = true; + } else { + tbarBtnDisabled["unlock"] = false; + tbarBtnDisabled["delete"] = false; + // Disable the 'Unlock' button if the device does not + // provide a UUID. + if(Ext.isEmpty(record.get("uuid"))) { + tbarBtnDisabled["unlock"] = true; + } + } + // If the device is in use, then also disable the lock + // button. + if (true === record.get("_used")) + tbarBtnDisabled["lock"] = true; + } else { + // Set default values. + tbarBtnDisabled["delete"] = false; + tbarBtnDisabled["unlock"] = true; + tbarBtnDisabled["lock"] = true; + tbarBtnDisabled["detail"] = true; + } + // Disable 'Delete' button if a selected device is in use or unlocked + for (var i = 0; i < records.length; i++) { + if (true == records[i].get("_used")) { + tbarBtnDisabled["delete"] = true; + } + if (true == records[i].get("unlocked")) { + tbarBtnDisabled["delete"] = true; + } + } + // Update the button controls. + Ext.Object.each(tbarBtnDisabled, function(key, value) { + this.setToolbarButtonDisabled(key, value); + }, me); + }, + + onCreateButton: function() { + var me = this; + Ext.create("OMV.module.admin.storage.luks.container.Create", { + listeners: { + scope: me, + submit: function() { + this.doReload(); + } + } + }).show(); + }, + + onUnlockButton: function() { + var me = this; + var record = me.getSelected(); + Ext.create("OMV.module.admin.storage.luks.container.Unlock", { + uuid: record.get("uuid"), + devicefile: record.get("devicefile"), + listeners: { + scope: me, + submit: function() { + this.doReload(); + } + } + }).show(); + }, + + onLockButton: function() { + var me = this; + var record = me.getSelected(); + var df = record.get("devicefile"); + // Execute RPC. + OMV.Rpc.request({ + scope: me, + callback: function(df, success, response) { + this.doReload(); + }, + relayErrors: false, + rpcData: { + service: "LuksMgmt", + method: "closeContainer", + params: { + devicefile: df + } + } + }); + }, + + onItemDblClick: function() { + var me = this; + me.onDetailButton(me); + }, + + onDetailButton: function() { + var me = this; + var record = me.getSelected(); + Ext.create("OMV.module.admin.storage.luks.container.Detail", { + rpcGetParams: { + devicefile: record.get("devicefile") + } + }).show(); + }, + + startDeletion: function(records) { + var me = this; + if(records.length <= 0) + return; + OMV.MessageBox.show({ + title: _("Delete encrypted device"), + msg: _("Do you really want to delete the encrypted device?
The encryption key will be destroyed and all data will be lost."), + icon: Ext.Msg.WARNING, + buttonText: { + yes: _("No"), + no: _("Yes") + }, + scope: me, + fn: function(answer) { + switch(answer) { + case "no": // Attention, switched buttons. + me.superclass.startDeletion.apply(this, [ records ]); + break; + default: + break; + } + } + }); + }, + + doDeletion: function(record) { + var me = this; + var df = record.get("devicefile"); + // Execute RPC. + OMV.Rpc.request({ + scope: me, + callback: me.onDeletion, + rpcData: { + service: "LuksMgmt", + method: "deleteContainer", + params: { + devicefile: df + } + } + }); + } +}); + + +OMV.WorkspaceManager.registerPanel({ + id: "containers", + path: "/storage/luks", + text: _("Encrypted Devices"), + position: 10, + className: "OMV.module.admin.storage.luks.Containers" +}); diff --git a/var/www/openmediavault/js/omv/module/admin/storage/luks/Luks.js b/var/www/openmediavault/js/omv/module/admin/storage/luks/Luks.js new file mode 100644 index 0000000..051ba4b --- /dev/null +++ b/var/www/openmediavault/js/omv/module/admin/storage/luks/Luks.js @@ -0,0 +1,31 @@ +/** + * This file is part of OpenMediaVault. + * + * @license http://www.gnu.org/licenses/gpl.html GPL Version 3 + * @author Volker Theile + * @copyright Copyright (c) 2009-2015 Volker Theile + * + * OpenMediaVault is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * OpenMediaVault is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OpenMediaVault. If not, see . + */ +// require("js/omv/WorkspaceManager.js") + + +OMV.WorkspaceManager.registerNode({ + id: "luks", + path: "/storage", + text: _("Encryption Management"), + icon16: "images/lock.png", + iconSvg: "images/lock.svg", + position: 22 +});