From 6da9bad2720701142b71c405f8d998afded5f1d5 Mon Sep 17 00:00:00 2001 From: dalto Date: Mon, 8 Nov 2021 17:26:08 -0600 Subject: [PATCH] [partition][zfs] Add support for zfs encryption --- .../partition/gui/CreatePartitionDialog.cpp | 10 ++- src/modules/zfs/ZfsJob.cpp | 87 +++++++++++++++---- src/modules/zfs/ZfsJob.h | 19 ++++ 3 files changed, 100 insertions(+), 16 deletions(-) diff --git a/src/modules/partition/gui/CreatePartitionDialog.cpp b/src/modules/partition/gui/CreatePartitionDialog.cpp index 0727ee935..e7d7751a1 100644 --- a/src/modules/partition/gui/CreatePartitionDialog.cpp +++ b/src/modules/partition/gui/CreatePartitionDialog.cpp @@ -243,7 +243,8 @@ CreatePartitionDialog::getNewlyCreatedPartition() // does so, to set up the partition for create-and-then-set-flags. Partition* partition = nullptr; QString luksPassphrase = m_ui->encryptWidget->passphrase(); - if ( m_ui->encryptWidget->state() == EncryptWidget::Encryption::Confirmed && !luksPassphrase.isEmpty() ) + if ( m_ui->encryptWidget->state() == EncryptWidget::Encryption::Confirmed && !luksPassphrase.isEmpty() + && fsType != FileSystem::Zfs ) { partition = KPMHelpers::createNewEncryptedPartition( m_parent, *m_device, m_role, fsType, fsLabel, first, last, luksPassphrase, PartitionTable::Flags() ); @@ -254,6 +255,13 @@ CreatePartitionDialog::getNewlyCreatedPartition() m_parent, *m_device, m_role, fsType, fsLabel, first, last, PartitionTable::Flags() ); } + // For zfs, we let the zfs module handle the encryption but we need to make the passphrase available to that module + if( m_ui->encryptWidget->state() == EncryptWidget::Encryption::Confirmed && !luksPassphrase.isEmpty() + && fsType == FileSystem::Zfs ) + { + Calamares::JobQueue::instance()->globalStorage()->insert( "encryptphrase", luksPassphrase ); + } + if ( m_device->type() == Device::Type::LVM_Device ) { partition->setPartitionPath( m_device->deviceNode() + QStringLiteral( "/" ) diff --git a/src/modules/zfs/ZfsJob.cpp b/src/modules/zfs/ZfsJob.cpp index 113f9afd8..af57fa5cc 100644 --- a/src/modules/zfs/ZfsJob.cpp +++ b/src/modules/zfs/ZfsJob.cpp @@ -17,6 +17,8 @@ #include "JobQueue.h" #include "Settings.h" +#include + ZfsJob::ZfsJob( QObject* parent ) : Calamares::CppJob( parent ) { @@ -30,6 +32,56 @@ ZfsJob::prettyName() const return tr( "Create ZFS pools and datasets" ); } +ZfsResult +ZfsJob::CreateZpool( QString deviceName, QString poolName, QString poolOptions, bool encrypt, QString passphrase ) const +{ + // zfs doesn't wait for the devices so pause for 2 seconds to ensure we give time for the device files to be created + QString command; + if ( encrypt ) + { + command = "sleep 2 ; echo \"" + passphrase + "\" | zpool create " + poolOptions + + " -O encryption=aes-256-gcm -O keyformat=passphrase " + poolName + " " + deviceName; + } + else + { + command = "sleep 2 ; zpool create " + poolOptions + " " + poolName + " " + deviceName; + } + + // We use a qProcess instead of runCommand so the password will not end up in the logs + QProcess process; + + process.setProcessChannelMode( QProcess::MergedChannels ); + cDebug() << Logger::SubEntry << "Running zpool create"; + + process.start( "sh", QStringList() << "-c" << command ); + + if ( !process.waitForStarted() ) + { + return { false, tr( "zpool create process failed to start" ) }; + } + + if ( !process.waitForFinished( 5000 ) ) + { + return { false, tr( "Process for zpool create timed out" ) }; + } + + QString output = QString::fromLocal8Bit( process.readAllStandardOutput() ).trimmed(); + + if ( process.exitStatus() == QProcess::CrashExit ) + { + return { false, tr( "The output from the crash was: " ) + output }; + } + + auto exitcode = process.exitCode(); + if ( exitcode != 0 ) + { + cWarning() << "Failed to run zpool create. The output was: " + output; + return { false, tr( "Failed to create zpool on " ) + deviceName }; + } + + return { true, QString() }; +} + Calamares::JobResult ZfsJob::exec() { @@ -78,15 +130,20 @@ ZfsJob::exec() continue; } - // Create the zpool - // zfs doesn't wait for the devices so pause for 2 seconds to ensure we give time for the device files to be created - auto r - = system->runCommand( { "sh", "-c", "sleep 2 ; zpool create " + m_poolOptions + " " + m_poolName + " " + deviceName }, - std::chrono::seconds( 10 ) ); - if ( r.getExitCode() != 0 ) + ZfsResult zfsResult; + if ( gs->contains( "encryptphrase" ) ) { - return Calamares::JobResult::error( tr( "zpool failure" ), - tr( "Failed to create zpool on " + deviceName.toLocal8Bit() ) ); + zfsResult + = CreateZpool( deviceName, m_poolName, m_poolOptions, true, gs->value( "encryptphrase" ).toString() ); + } + else + { + zfsResult = CreateZpool( deviceName, m_poolName, m_poolOptions, false ); + } + + if ( !zfsResult.success ) + { + return Calamares::JobResult::error( tr( "Failed to create zpool" ), zfsResult.failureMessage ); } // Create the datasets @@ -105,12 +162,12 @@ ZfsJob::exec() // Create the dataset. We set canmount=no regardless of the setting for now. // It is modified to the correct value in the mount module to ensure mount order is maintained - r = system->runCommand( { "sh", - "-c", - "zfs create " + m_datasetOptions - + " -o canmount=off -o mountpoint=" + datasetMap[ "mountpoint" ].toString() - + " " + m_poolName + "/" + datasetMap[ "dsName" ].toString() }, - std::chrono::seconds( 10 ) ); + auto r = system->runCommand( { "sh", + "-c", + "zfs create " + m_datasetOptions + " -o canmount=off -o mountpoint=" + + datasetMap[ "mountpoint" ].toString() + " " + m_poolName + "/" + + datasetMap[ "dsName" ].toString() }, + std::chrono::seconds( 10 ) ); if ( r.getExitCode() != 0 ) { cWarning() << "Failed to create dataset" << datasetMap[ "dsName" ].toString(); @@ -124,7 +181,7 @@ ZfsJob::exec() // If the list isn't empty, add it to global storage if ( !datasetList.isEmpty() ) { - Calamares::JobQueue::instance()->globalStorage()->insert( "zfs", datasetList ); + gs->insert( "zfs", datasetList ); } } diff --git a/src/modules/zfs/ZfsJob.h b/src/modules/zfs/ZfsJob.h index 87646a227..b2feb9e87 100644 --- a/src/modules/zfs/ZfsJob.h +++ b/src/modules/zfs/ZfsJob.h @@ -20,6 +20,11 @@ #include "DllMacro.h" +struct ZfsResult { + bool success; + QString failureMessage; +}; + /** @brief Create zpools and zfs datasets * */ @@ -43,6 +48,20 @@ private: QString m_datasetOptions; QList m_datasets; + + /** @brief Creates a zpool based on the provided arguments + * + * Creates a zpool + * @p deviceName is a full path to the device the zpool should be created on + * @p poolName is a string containing the name of the pool to create + * @p poolOptions are the options to pass to zpool create + * @p encrypt is a boolean which determines if the pool should be encrypted + * @p passphrase is a string continaing the passphrase + * + */ + ZfsResult CreateZpool(QString deviceName, QString poolName, QString poolOptions, bool encrypt, QString passphrase = QString() ) const; + + }; CALAMARES_PLUGIN_FACTORY_DECLARATION( ZfsJobFactory )