Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

install: Add support for LUKS #75

Merged
merged 2 commits into from
Mar 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
65 changes: 49 additions & 16 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 @@ -52,13 +53,6 @@ pub(crate) enum Filesystem {
Btrfs,
}

impl Default for Filesystem {
fn default() -> Self {
// Obviously this should be configurable.
Self::Xfs
}
}

impl Display for Filesystem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.to_possible_value().unwrap().get_name().fmt(f)
Expand Down Expand Up @@ -161,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 @@ -291,11 +286,41 @@ 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_kargs) = 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 kargs = vec![
format!("luks.uuid={uuid}"),
format!("luks.options=tpm2-device=auto,headless=true"),
];
(rootdev, Some(kargs))
}
};

// TODO: make this configurable
let bootfs_type = Filesystem::Ext4;
Expand All @@ -305,19 +330,22 @@ 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_kargs
.into_iter()
.flatten()
.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 @@ -339,7 +367,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