Skip to content

Commit 7873787

Browse files
authored
Merge pull request #75 from cgwalters/luks
install: Add support for LUKS
2 parents 6531d53 + b8f2b6d commit 7873787

File tree

3 files changed

+90
-24
lines changed

3 files changed

+90
-24
lines changed

lib/src/install.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@ fn skopeo_supports_containers_storage() -> Result<bool> {
576576
}
577577

578578
pub(crate) struct RootSetup {
579+
luks_device: Option<String>,
579580
device: Utf8PathBuf,
580581
rootfs: Utf8PathBuf,
581582
rootfs_fd: Dir,
@@ -594,6 +595,11 @@ impl RootSetup {
594595
fn get_boot_uuid(&self) -> Result<&str> {
595596
require_boot_uuid(&self.boot)
596597
}
598+
599+
// Drop any open file descriptors and return just the mount path and backing luks device, if any
600+
fn into_storage(self) -> (Utf8PathBuf, Option<String>) {
601+
(self.rootfs, self.luks_device)
602+
}
597603
}
598604

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

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

818-
// Drop all data about the root except the path to ensure any file descriptors etc. are closed.
819-
let rootfs_path = rootfs.rootfs.clone();
820-
drop(rootfs);
821-
828+
// Drop all data about the root except the bits we need to ensure any file descriptors etc. are closed.
829+
let (root_path, luksdev) = rootfs.into_storage();
822830
Task::new_and_run(
823831
"Unmounting filesystems",
824832
"umount",
825-
["-R", rootfs_path.as_str()],
833+
["-R", root_path.as_str()],
826834
)?;
835+
if let Some(luksdev) = luksdev.as_deref() {
836+
Task::new_and_run("Closing root LUKS device", "cryptsetup", ["close", luksdev])?;
837+
}
827838

828839
installation_complete();
829840

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

962973
let mut rootfs = RootSetup {
974+
luks_device: None,
963975
device: backing_device.into(),
964976
rootfs: fsopts.root_path,
965977
rootfs_fd,

lib/src/install/baseline.rs

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
88
use std::borrow::Cow;
99
use std::fmt::Display;
10+
use std::io::Write;
1011
use std::process::Command;
1112
use std::process::Stdio;
1213

@@ -52,13 +53,6 @@ pub(crate) enum Filesystem {
5253
Btrfs,
5354
}
5455

55-
impl Default for Filesystem {
56-
fn default() -> Self {
57-
// Obviously this should be configurable.
58-
Self::Xfs
59-
}
60-
}
61-
6256
impl Display for Filesystem {
6357
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6458
self.to_possible_value().unwrap().get_name().fmt(f)
@@ -161,6 +155,7 @@ pub(crate) fn install_create_rootfs(
161155
state: &State,
162156
opts: InstallBlockDeviceOpts,
163157
) -> Result<RootSetup> {
158+
let luks_name = "root";
164159
// Verify that the target is empty (if not already wiped in particular, but it's
165160
// also good to verify that the wipe worked)
166161
let device = crate::blockdev::list_dev(&opts.device)?;
@@ -291,11 +286,41 @@ pub(crate) fn install_create_rootfs(
291286

292287
crate::blockdev::udev_settle()?;
293288

294-
match opts.block_setup {
295-
BlockSetup::Direct => {}
296-
// TODO
297-
BlockSetup::Tpm2Luks => anyhow::bail!("tpm2-luks is not implemented yet"),
298-
}
289+
let base_rootdev = format!("{device}{ROOTPN}");
290+
let (rootdev, root_blockdev_kargs) = match opts.block_setup {
291+
BlockSetup::Direct => (base_rootdev, None),
292+
BlockSetup::Tpm2Luks => {
293+
let uuid = uuid::Uuid::new_v4().to_string();
294+
// This will be replaced via --wipe-slot=all when binding to tpm below
295+
let dummy_passphrase = uuid::Uuid::new_v4().to_string();
296+
let mut tmp_keyfile = tempfile::NamedTempFile::new()?;
297+
tmp_keyfile.write_all(dummy_passphrase.as_bytes())?;
298+
tmp_keyfile.flush()?;
299+
let tmp_keyfile = tmp_keyfile.path();
300+
let dummy_passphrase_input = Some(dummy_passphrase.as_bytes());
301+
302+
Task::new("Initializing LUKS for root", "cryptsetup")
303+
.args(["luksFormat", "--uuid", uuid.as_str(), "--key-file"])
304+
.args([tmp_keyfile])
305+
.args([base_rootdev.as_str()])
306+
.run()?;
307+
// The --wipe-slot=all removes our temporary passphrase, and binds to the local TPM device.
308+
Task::new("Enrolling root device with TPM", "systemd-cryptenroll")
309+
.args(["--wipe-slot=all", "--tpm2-device=auto", "--unlock-key-file"])
310+
.args([tmp_keyfile])
311+
.args([base_rootdev.as_str()])
312+
.run_with_stdin_buf(dummy_passphrase_input)?;
313+
Task::new("Opening root LUKS device", "cryptsetup")
314+
.args(["luksOpen", base_rootdev.as_str(), luks_name])
315+
.run()?;
316+
let rootdev = format!("/dev/mapper/{luks_name}");
317+
let kargs = vec![
318+
format!("luks.uuid={uuid}"),
319+
format!("luks.options=tpm2-device=auto,headless=true"),
320+
];
321+
(rootdev, Some(kargs))
322+
}
323+
};
299324

300325
// TODO: make this configurable
301326
let bootfs_type = Filesystem::Ext4;
@@ -305,19 +330,22 @@ pub(crate) fn install_create_rootfs(
305330
let boot_uuid = mkfs(bootdev, bootfs_type, Some("boot"), []).context("Initializing /boot")?;
306331

307332
// Initialize rootfs
308-
let rootdev = &format!("{device}{ROOTPN}");
309333
let root_filesystem = opts
310334
.filesystem
311335
.or(state.install_config.root_fs_type)
312336
.ok_or_else(|| anyhow::anyhow!("No root filesystem specified"))?;
313-
let root_uuid = mkfs(rootdev, root_filesystem, Some("root"), [])?;
337+
let root_uuid = mkfs(&rootdev, root_filesystem, Some("root"), [])?;
314338
let rootarg = format!("root=UUID={root_uuid}");
315339
let bootsrc = format!("UUID={boot_uuid}");
316340
let bootarg = format!("boot={bootsrc}");
317341
let boot = MountSpec::new(bootsrc.as_str(), "/boot");
318-
let kargs = vec![rootarg, RW_KARG.to_string(), bootarg];
342+
let kargs = root_blockdev_kargs
343+
.into_iter()
344+
.flatten()
345+
.chain([rootarg, RW_KARG.to_string(), bootarg].into_iter())
346+
.collect::<Vec<_>>();
319347

320-
mount::mount(rootdev, &rootfs)?;
348+
mount::mount(&rootdev, &rootfs)?;
321349
lsm_label(&rootfs, "/".into(), false)?;
322350
let rootfs_fd = Dir::open_ambient_dir(&rootfs, cap_std::ambient_authority())?;
323351
let bootfs = rootfs.join("boot");
@@ -339,7 +367,12 @@ pub(crate) fn install_create_rootfs(
339367
mount::mount(&espdev, &efifs_path)?;
340368
}
341369

370+
let luks_device = match opts.block_setup {
371+
BlockSetup::Direct => None,
372+
BlockSetup::Tpm2Luks => Some(luks_name.to_string()),
373+
};
342374
Ok(RootSetup {
375+
luks_device,
343376
device,
344377
rootfs,
345378
rootfs_fd,

lib/src/task.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::{
22
ffi::OsStr,
3-
io::Seek,
3+
io::{Seek, Write},
44
process::{Command, Stdio},
55
};
66

@@ -57,6 +57,11 @@ impl Task {
5757

5858
/// Run the command, returning an error if the command does not exit successfully.
5959
pub(crate) fn run(self) -> Result<()> {
60+
self.run_with_stdin_buf(None)
61+
}
62+
63+
/// Run the command with optional stdin buffer, returning an error if the command does not exit successfully.
64+
pub(crate) fn run_with_stdin_buf(self, stdin: Option<&[u8]>) -> Result<()> {
6065
let description = self.description;
6166
let mut cmd = self.cmd;
6267
if !self.quiet {
@@ -70,7 +75,23 @@ impl Task {
7075
output = Some(tmpf);
7176
}
7277
tracing::debug!("exec: {cmd:?}");
73-
let st = cmd.status()?;
78+
let st = if let Some(stdin_value) = stdin {
79+
cmd.stdin(Stdio::piped());
80+
let mut child = cmd.spawn()?;
81+
// SAFETY: We used piped for stdin
82+
let mut stdin = child.stdin.take().unwrap();
83+
// If this was async, we could avoid spawning a thread here
84+
std::thread::scope(|s| {
85+
s.spawn(move || stdin.write_all(stdin_value))
86+
.join()
87+
.map_err(|e| anyhow::anyhow!("Failed to spawn thread: {e:?}"))?
88+
.context("Failed to write to cryptsetup stdin")
89+
})?;
90+
child.wait()?
91+
} else {
92+
cmd.status()?
93+
};
94+
tracing::trace!("{st:?}");
7495
if !st.success() {
7596
if let Some(mut output) = output {
7697
output.seek(std::io::SeekFrom::Start(0))?;

0 commit comments

Comments
 (0)