Skip to content

Commit

Permalink
install: Add support for LUKS
Browse files Browse the repository at this point in the history
This currently requires systemd 253 for
systemd/systemd@03f36e9

Signed-off-by: Colin Walters <walters@verbum.org>
  • Loading branch information
cgwalters committed Mar 12, 2023
1 parent 83d144a commit 4a6f3b7
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 17 deletions.
24 changes: 18 additions & 6 deletions lib/src/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,7 @@ fn skopeo_supports_containers_storage() -> Result<bool> {
}

pub(crate) struct RootSetup {
luks_device: Option<String>,
device: Utf8PathBuf,
rootfs: Utf8PathBuf,
rootfs_fd: Dir,
Expand All @@ -594,6 +595,11 @@ impl RootSetup {
fn get_boot_uuid(&self) -> Result<&str> {
require_boot_uuid(&self.boot)
}

// Drop any open file descriptors and return just the mount path and backing luks device, if any
fn into_storage(self) -> (Utf8PathBuf, Option<String>) {
(self.rootfs, self.luks_device)
}
}

/// If we detect that the target ostree commit has SELinux labels,
Expand Down Expand Up @@ -637,7 +643,11 @@ pub(crate) fn reexecute_self_for_selinux_if_needed(
pub(crate) fn finalize_filesystem(fs: &Utf8Path) -> Result<()> {
let fsname = fs.file_name().unwrap();
// fstrim ensures the underlying block device knows about unused space
Task::new_and_run(format!("Trimming {fsname}"), "fstrim", ["-v", fs.as_str()])?;
Task::new_and_run(
format!("Trimming {fsname}"),
"fstrim",
["--quiet-unsupported", "-v", fs.as_str()],
)?;
// Remounting readonly will flush outstanding writes and ensure we error out if there were background
// writeback problems.
Task::new(format!("Finalizing filesystem {fsname}"), "mount")
Expand Down Expand Up @@ -815,15 +825,16 @@ pub(crate) async fn install(opts: InstallOpts) -> Result<()> {

install_to_filesystem_impl(&state, &mut rootfs).await?;

// Drop all data about the root except the path to ensure any file descriptors etc. are closed.
let rootfs_path = rootfs.rootfs.clone();
drop(rootfs);

// Drop all data about the root except the bits we need to ensure any file descriptors etc. are closed.
let (root_path, luksdev) = rootfs.into_storage();
Task::new_and_run(
"Unmounting filesystems",
"umount",
["-R", rootfs_path.as_str()],
["-R", root_path.as_str()],
)?;
if let Some(luksdev) = luksdev.as_deref() {
Task::new_and_run("Closing root LUKS device", "cryptsetup", ["close", luksdev])?;
}

installation_complete();

Expand Down Expand Up @@ -960,6 +971,7 @@ pub(crate) async fn install_to_filesystem(opts: InstallToFilesystemOpts) -> Resu
let kargs = vec![rootarg, RW_KARG.to_string(), bootarg];

let mut rootfs = RootSetup {
luks_device: None,
device: backing_device.into(),
rootfs: fsopts.root_path,
rootfs_fd,
Expand Down
54 changes: 45 additions & 9 deletions lib/src/install/baseline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

use std::borrow::Cow;
use std::fmt::Display;
use std::io::Write;
use std::process::Command;
use std::process::Stdio;

Expand Down Expand Up @@ -154,6 +155,7 @@ pub(crate) fn install_create_rootfs(
state: &State,
opts: InstallBlockDeviceOpts,
) -> Result<RootSetup> {
let luks_name = "root";
// Verify that the target is empty (if not already wiped in particular, but it's
// also good to verify that the wipe worked)
let device = crate::blockdev::list_dev(&opts.device)?;
Expand Down Expand Up @@ -284,11 +286,38 @@ pub(crate) fn install_create_rootfs(

crate::blockdev::udev_settle()?;

match opts.block_setup {
BlockSetup::Direct => {}
// TODO
BlockSetup::Tpm2Luks => anyhow::bail!("tpm2-luks is not implemented yet"),
}
let base_rootdev = format!("{device}{ROOTPN}");
let (rootdev, root_blockdev_karg) = match opts.block_setup {
BlockSetup::Direct => (base_rootdev, None),
BlockSetup::Tpm2Luks => {
let uuid = uuid::Uuid::new_v4().to_string();
// This will be replaced via --wipe-slot=all when binding to tpm below
let dummy_passphrase = uuid::Uuid::new_v4().to_string();
let mut tmp_keyfile = tempfile::NamedTempFile::new()?;
tmp_keyfile.write_all(dummy_passphrase.as_bytes())?;
tmp_keyfile.flush()?;
let tmp_keyfile = tmp_keyfile.path();
let dummy_passphrase_input = Some(dummy_passphrase.as_bytes());

Task::new("Initializing LUKS for root", "cryptsetup")
.args(["luksFormat", "--uuid", uuid.as_str(), "--key-file"])
.args([tmp_keyfile])
.args([base_rootdev.as_str()])
.run()?;
// The --wipe-slot=all removes our temporary passphrase, and binds to the local TPM device.
Task::new("Enrolling root device with TPM", "systemd-cryptenroll")
.args(["--wipe-slot=all", "--tpm2-device=auto", "--unlock-key-file"])
.args([tmp_keyfile])
.args([base_rootdev.as_str()])
.run_with_stdin_buf(dummy_passphrase_input)?;
Task::new("Opening root LUKS device", "cryptsetup")
.args(["luksOpen", base_rootdev.as_str(), luks_name])
.run()?;
let rootdev = format!("/dev/mapper/{luks_name}");
let karg = format!("luks.uuid={uuid}");
(rootdev, Some(karg))
}
};

// TODO: make this configurable
let bootfs_type = Filesystem::Ext4;
Expand All @@ -298,19 +327,21 @@ pub(crate) fn install_create_rootfs(
let boot_uuid = mkfs(bootdev, bootfs_type, Some("boot"), []).context("Initializing /boot")?;

// Initialize rootfs
let rootdev = &format!("{device}{ROOTPN}");
let root_filesystem = opts
.filesystem
.or(state.install_config.root_fs_type)
.ok_or_else(|| anyhow::anyhow!("No root filesystem specified"))?;
let root_uuid = mkfs(rootdev, root_filesystem, Some("root"), [])?;
let root_uuid = mkfs(&rootdev, root_filesystem, Some("root"), [])?;
let rootarg = format!("root=UUID={root_uuid}");
let bootsrc = format!("UUID={boot_uuid}");
let bootarg = format!("boot={bootsrc}");
let boot = MountSpec::new(bootsrc.as_str(), "/boot");
let kargs = vec![rootarg, RW_KARG.to_string(), bootarg];
let kargs = root_blockdev_karg
.into_iter()
.chain([rootarg, RW_KARG.to_string(), bootarg].into_iter())
.collect::<Vec<_>>();

mount::mount(rootdev, &rootfs)?;
mount::mount(&rootdev, &rootfs)?;
lsm_label(&rootfs, "/".into(), false)?;
let rootfs_fd = Dir::open_ambient_dir(&rootfs, cap_std::ambient_authority())?;
let bootfs = rootfs.join("boot");
Expand All @@ -332,7 +363,12 @@ pub(crate) fn install_create_rootfs(
mount::mount(&espdev, &efifs_path)?;
}

let luks_device = match opts.block_setup {
BlockSetup::Direct => None,
BlockSetup::Tpm2Luks => Some(luks_name.to_string()),
};
Ok(RootSetup {
luks_device,
device,
rootfs,
rootfs_fd,
Expand Down
25 changes: 23 additions & 2 deletions lib/src/task.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{
ffi::OsStr,
io::Seek,
io::{Seek, Write},
process::{Command, Stdio},
};

Expand Down Expand Up @@ -57,6 +57,11 @@ impl Task {

/// Run the command, returning an error if the command does not exit successfully.
pub(crate) fn run(self) -> Result<()> {
self.run_with_stdin_buf(None)
}

/// Run the command with optional stdin buffer, returning an error if the command does not exit successfully.
pub(crate) fn run_with_stdin_buf(self, stdin: Option<&[u8]>) -> Result<()> {
let description = self.description;
let mut cmd = self.cmd;
if !self.quiet {
Expand All @@ -70,7 +75,23 @@ impl Task {
output = Some(tmpf);
}
tracing::debug!("exec: {cmd:?}");
let st = cmd.status()?;
let st = if let Some(stdin_value) = stdin {
cmd.stdin(Stdio::piped());
let mut child = cmd.spawn()?;
// SAFETY: We used piped for stdin
let mut stdin = child.stdin.take().unwrap();
// If this was async, we could avoid spawning a thread here
std::thread::scope(|s| {
s.spawn(move || stdin.write_all(stdin_value))
.join()
.map_err(|e| anyhow::anyhow!("Failed to spawn thread: {e:?}"))?
.context("Failed to write to cryptsetup stdin")
})?;
child.wait()?
} else {
cmd.status()?
};
tracing::trace!("{st:?}");
if !st.success() {
if let Some(mut output) = output {
output.seek(std::io::SeekFrom::Start(0))?;
Expand Down

0 comments on commit 4a6f3b7

Please sign in to comment.